一、对硬件数据结构的初始化过程。 1、系统首先调用 printk() 函数在屏幕上打印 Linux 内核版本号和编译内核所使用的 gcc
编译器版本号、启用时间等,如果这个过程失败,将显示一个参考信息给用户。 2、调用 arch/i386/kernel/setup.c 中的
setup_arch() 函数,初始化系统主板上各个集成电路控制器,最后在 command line、memory_start 和 memory_end
中返回结果。
获取外设的参数。将硬盘、鼠标、显示器、根设备的主从设备号、高级电源管理以及总线类型等参数, 写入相关的内存单元。 如果设置了
RAM盘,则把各参数写入相应的内存单元。 设置 init_task.mm 代码结构在内存中的起点和终点以及数据段的终点。 把命令行参数拷贝到
save_command_line 变量。分析、排除 “mem=”形式的命令。获取 CPU类型,以判断是否支持扩展分页,即允许页框大小为
4MB的页。调整内存边界参数 start_mem 。 调用 reguest_region() 函数为主板上的
I/O芯片申请I/O内存使用空间。这些集成芯片是 timer定时计数器、DMA控制器1、DMA控制器2以及协处理器 fpu。
3、调用 arch/i386/init.c 中的 paging_init() 函数初始化内核页表。它实现的功能如下:
调整 memory_start 按下一个可用页边界对齐。 对临时页目录表项中的第 0 个目录项清零,这样就将从 0 开始的最初 4MB
的线性地址和物理地址消除,以便使用户可使用0 到 4194303
之间的线性地址空间。 初始化页目录的第0项到768项以及各个目录项对应的页表表项。
4、调用 arch/i386/kernel/trap.c 中的 trap_init()
函数中对中断描述符表IDT进行初始化。为了使用异常处理,trap_init() 函数将处理异常的函数地址的选择符写入 IDT的陷阱门描述符中。这些门的设置时由
set_trap_gate() 和set_system_gate() 函数来完成的。 5、调用 arch/i386/kernel/irq.c 中的
init_IRQ() 函数。设置基准时钟和中断门。
把主从 8259 中断控制器的硬中断号所对应的中断门全部填入中断描述符表。中断门属性字 0x8EE0。 为主从 8259 控制器请求 I/O
地址,地址分别为 0×20 和 0xA0 。 为 8253 定时器/计数器设置计时初始值为 11933 = 0x2E9C,使系统时钟周期为
10ms(100Hz)。 调用 setup_x86_irq(),把 irq2 和 irq3
所对应的中断服务程序的偏移量装入相对应的中断门描述符中。irq2 用于主从控制器之间的级联。irq3 用于连接外部协处理器。
6、调用 kernel/sched.c 中的 sched() ,为进程调度程序的执行做准备。通过 init_bh() 函数设置用于内核例程处理程序的数组
bh_base[]。即分别把定时器队列 TIMER_BH 、设备队列TQUEVE_BH 和即时队列 IMMEDIATE_BH 填加到数组
bh_base[]中。
7、调用 arch/i386/kernel/time.c 中的 time_init() 函数,初始化系统时钟和日期。这个过程是从
CMOS中读取实时时间,并在屏幕上显示,然后调用 stup_x86_irq()
函数把时间中断服务程序的偏移量(中断号:0×20)装入相应的中断门描述符中。 8、调用 init/main.c 中的 parse_options()
函数,对命令行选项进行分析。这些选项是在命令启动时,由内核引导程序(主要是
arch/i386/boot/bootseet.c)装入,并存放在empty_zero_page 页的后 2K
字节中。它首先检查命令行参数,并设置相应的变量。然后把环境变量送入数组 envp_init[] ,把参数送入数组 argv_init[] 中。 9、调用
drivers/char/tty_io.c 中的 console_init() 函数初始化控制台。调用drivers/char/console.c 中的
con_init() 初始化显示器。 10、调用 kernel/module.c 中的 init_modules() 函数,初始化内核模块
kernel_module 。 11、通过 if 语句来判断 prof_shift ,如果该值等于 0 ,说明 prof_buffer
表中没有空间,因此不执行x86_do_profile() 函数对 profile_buffer 执行初始化,否则就对 profile_buffer
执行初始化。 12、调用 mm/slab.c 中的 keme_cache_init() 函数,初始化高速通用缓存及 slab 分配器。 13、调用
sti() 开中断。激活硬件中断系统,以便接收时钟中断。紧接着调用 calibrate_delay()测试机器的 BogoMIPS
值(这个值的含义是:内核发出读取设备信号后,需要等待多少时间才能得到从设备返回的请求信息或者说是 CPU 在一秒钟内执行一个短延时循环的近似次数)。
14、调用 arch/i386/mm/init.c 中的 mem_init() 函数,初始化页描述符,系统中所有的页框描述符存放在 mem_map[]
数组中,页框描述符的初始化是由 free_area_init() 函数完成的。 mem_init()
函数确定系统现有的页框总数,并计算出保留给硬件、内核代码和内核数据的页框数,以及内核初始化期间使用过,但随后又被释放掉的页框数。最后,mem_init()
在结构数组 mem_map[]中标记已被占用的页框,计数没有使用的页框。该函数返回时,变量 nr_free_pages
中包含了动态内存中没有使用的页框总数。 15、调用 mm/slab.c 中的 kmem_cache_sizes_init()
函数,初始化通用高速缓存的大小。 16、调用 /fs/proc/root.c 中的 proc_root_init()函数
,初始化根文件系统的各种数据结构。 17、调用 /kernel/fork.c 中的 uidcache_init()函数
,创建用户进程标志符缓存。 18、调用 /kernel/fork.c 中的 filescache_init()函数,创建文件缓存。 19、调用
fs/dcache.c 中的 dcache_init() 函数,创建目录项高速缓存 dentry_cache 。 20、调用 mm/mmap.c 中的
vma_init() 函数,创建 vm_area_struct 结构和 mm_struct 结构。 21、调用 fs/buffer.c 中的
buffer_init() 函数,创建 buffer_head SLAB 缓存。 22、调用 kernel/signal.c 中的
signals_init() 函数,创建信号队列。 23、调用 fs/inode.c 中的 inode_init() 函数,初始化 inode
节点,即:将数组 hash_table[]全部清零,在把指向第一个 i 节点的全局变量置为空。 24、调用 fs/file_table.c 中的
file_table_init() 函数,创建 filp SLAB结构。 25、调用 ipc/util.c 中的 ipc_init()
,初始化信号量、消息和共享内存。 26、调用 fs/dquot.c 中的 dquot_init_hash()函数,创建磁盘配额缓存。 27、调用
include/i386/bugs.h 中的 check_bugs() 函数,测试 CPU的各种属性,检查协处理器状态。 28、调用 printk()
函数,在屏幕上打印出:“POSIX conformance testing by UNIFIX \n”。 29、调用 init/main.c 中的
smp_init() 函数,激活对称多处理方式中的其他的 CPU。 30、调用 arch/i386/kernel/process.c 中的
kernel_thread() 函数,创建一个内核态线程,以便执行 init函数,这里应该注意,kernel_thread()是由汇编语言写成的,它使用了
int 0×80 系统调用创建一个新的内核态线程。该系统调用返回后通过比较 ESP 和 ESI 两个寄存器的值来判断父、子进程,如果是复进程则执行 0
号进程,如果是子进程则执行 init() 函数。 31、设置 idle 进程的 need_resched 标志位,调用 schedule 使 CPU
处理更多的进程。 32、系统到这里,内核初始化过程已经全部完成,下面系统将进入调用 init() 函数完成系统运行状态切换等工作,这部分工作下面部分再谈。
二、内核解析与 init 进程 1、内核词法解析 在 start_kernel() 中的 parse_options()
函数是一个词法分析程序,它的作用是将内核引导程序传递给内核的启动选项进行分析并加以处理,然后将其存放到不同的数组中,以备随后的 init 进程使用。 在
parse_options() 函
|