Linux内核初始化过程 - 第二阶段

   Linux内核的初始化过程之所以复杂,是因为它同时支持静态加载动态加载内核模块。动态加载内核模块提高了系统的灵活性,但是因此需要考虑更多的方面。设备驱动程序可以静态地编译到内核中,也可以作为一个内核模块动态地装载各卸载。此外,由于支持热插拔设备,因此还需考虑在热插拔情况下的初始化工作


   系统启动初始化时,一旦进入start_kernel(),则说明低级的初始化已完成,为这个函数的执行建立起了一个环境,创建了必要的条件。当然,这个函数还要继续进行内核的初始化,实际上甚至可以说内核的初始化才真正开始。但是这种初始化与此之前的初始化毕竟不同,是较高层次上的初始化。这也是为什么从这里开始的代码基本上是C代码,而在此之前过都是汇编代码的原因。


一、对硬件数据结构的初始化过程。


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。

在系统初始化的第二附体,setup_arch()也许是最重要的函数,正如其函数名所暗示的那样,它所设置的是系统的architecture,即总体的结构。完成了setup_arch(),内核就有了一个框架,内核中各个部件或模块就有了运行的环境。
但是这个阶段的初始化还远未完成,路还很长,我们回到start_kernel()中再往下看。

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 0x80 系统调用创建一个新的内核态线程。该系统调用返回后通过比较 ESP 和 ESI 两个寄存器的值来判断父、子进程,如果是复进程则执行 0 号进程,如果是子进程则执行 init() 函数。


31、设置 idle 进程的 need_resched 标志位,调用 schedule 使 CPU 处理更多的进程。


32、系统到这里,内核初始化过程已经全部完成,下面系统将进入调用 init() 函数完成系统运行状态切换等工作,这部分工作下面部分再谈。


二、内核词法解析与init进程


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值