Linux内核初始化流程笔记

好文章,转载一下!

博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net 
 
本文的copyleft归[email protected]所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
如前文http://http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=3129477的流程,bootloader将kerenel加载到内存中。

这里加载的kernel镜像,并不是真正的可执行文件,而是一个压缩的镜像文件。主要有两种类型zImage和bzImage(即为Big zImage)。其中zImage小于512KB,而bzImage可以为一个大的压缩镜像文件。zImage可以用于在没有bootloader的情况下,直接启动kernel。而 目前一般都是使用bzImage。以我目前的Fedora13为例,执行 file /boot/vmlinuz-2.6.33.3-85.fc13.i686.PAE
  1. /boot/vmlinuz-2.6.33.3-85.fc13.i686.PAE: Linux kernel x86 boot executable bzImage, version 2.6.33.3-85.fc13.i686.PAE (mock, RO-rootFS, root_dev 0x902, swap_dev 0x3, Normal VGA
为啥要压缩呢?因为在这一时刻,CPU是工作在实模式下,可访问的内存空间只有1M,所以镜像文件要尽量的小,最好能够小于1M。可是bzImage的大小往往会大于1M,怎么办?比如我的系统中:
  1. [root@fgao-vm-fc13 boot]# ls -lh vmlinuz-2.6.33.3-85.fc13.i686.PAE
  2. -rwxr-xr-x. 1 root root 3.4M May 7 2010 vmlinuz-2.6.33.3-85.fc13.i686.PAE
这个压缩后的镜像就已经高达3M了,远超过实模式下的1M寻址空间。

解决方法很简单,同样是把kernel的镜像也分为两部分。第一部分为kernel运行在实模式下boot sector(512字节)和kernel setup(合计32K),而第二部分也是大部分代码都是运行在包含模式下。按照Linux的文档,推荐的内存布局如下:


内存地址1M以下包括的kernel代码
1. boot sector:最前面的512字节,该部分代码目前已经无用。原来是用于在没有bootloader的情况下,启动kernel。目前kernel的启动必须依赖于bootloader。
2. setup:boot sector后面的代码,kernel启动的真正入口,由bootloader直接跳转到这里。
3. stack/heap:实模式下,内核需要的栈和堆,大小为32K。

关于kernel header的描述,可以查看Document/x86/boot.txt。写得很详细

剩下的kernel代码被放置在0x100000即1M内存处,那么毫无疑问,这部分代码是无法在实模式下运行的,只能运行在保护模式下。


既然实模式下内核代码的前512字节已经作废,所以真正的入口为setup,即arch/x86/boot下面的head.S。
  1. # End of setup header #####################################################
  2. .section ".entrytext", "ax"
  3. start_of_setup:
  4. #ifdef SAFE_RESET_DISK_CONTROLLER
  5. # Reset the disk controller.
  6. movw $0x0000, %ax # Reset disk controller
  7. movb $0x80, %dl # All disks
  8. int $0x13
  9. #endif

这里为真正的入口点,从汇编的伪指令也很容易看出——即红色那行。

head.S的代码的注释很清晰,建立stack/heap,检查签名——验证setup,清BSS段,然后call main函数——正常是不会返回的。当返回的时候,打印一些出错信息。

由head.S调用的main,需要注意的是,这时仍然是在实模式下——因为没有人启用CPU的保护模式呢。head.S的主要任务是建立了一个C语言可运行的基本环境,然后由C代码去做进一步处理。

进入实模式下的main,位于arch/x86/boot/main.c。代码注释非常清楚,另一方面这太过于底层,基本上看一遍注释即可。它的工作就是为进入保护模式作准备,然后调用go_to_protected_mode——这个仍然是C函数,先做切换到保护模式下的必要工作,然后调用protected_mode_jump——汇编去做真正的切换。

关于如何protected_mode_jump如何处理,如何enable CPU的保护模式。太多大神做过这些方面的说明了。另外,本文也只是我的一个笔记,对于这部分的细节,我暂时也没有兴趣。在protected_mode_jum的最后一条语句,将直接跳转到保护模式下kernel的入口地址。这个地址是由header filed中的code32_start定义的,即为保护模式的kernel代码的入口地址。根据Linux的文档,这个地址是由bootloader使用,且可以bootloader来决定保护模式的kernel代码的加载地址。不管bootloader是否会更改保护模式的kernel代码的加载地址,反正在protected_mode_jump中,会使用bootloader确定的地址,然后跳转到保护模式下的kernel入口——这个入口在vmlinux.lds.S定义。

对于32位的PC来说,入口为startup_32,位于head_32.S,再次进入汇编代码,不过也终于进入保护模式了。startup_32干了啥,还是看注释就清楚了,基本上还是一些准备工作,然后解压内核,并将解压后的内核仍然放在0x100000地址上,然后再次跳转到0x100000处,执行解压后的kernel代码。

这时kernel的入口仍然是startup_32,但是却非之前的startup_32。前面的startup_32是位于arch/x86/boot/compressed/head_32.S,而现在这个startup_32为kernel解压后的程序,其代码位于arch/x86/kernel/head_32.S。它的任务还是做一些准备工作,设置GDT,清BSS,初始化内存的page table,建立中断表,等等。。。然后其调用i386_start_kernel->start_kernel。

start_kernel位于init/main.c,这个终于与平台无关了,且进入了C代码。到此,Linux内核的初始化流程基本结束。进入start_kernel后,真正的kernel已经启动,且进入了保护模式。后面的学习,就可以一步一步的看kernel是如何管理内存,进程调度,网络处理等等。

本文只能算是一个笔记,因为大部分都是查阅了别人的文章,几乎没有原创。并且内容如流水账一般,只是简单的将内核初始化流程走了一遍。底层的细节太多了,暂时也没有精力去关注了。

当大致了解了kernel的加载,启动的流程后,很多东西就没有那么神秘了。因为最起码知道了kernel简单的来龙去脉,为以后针对kernel具体部分的学习,做了铺垫。

推荐大家阅读一下我参考的文章:

猜你喜欢

转载自blog.csdn.net/wwxxff28/article/details/37809471
今日推荐