Linux内核加载流程

先看一下一位帅哥的详细介绍:

http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=3129477

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
无论是Linux还是Windows,在加电后的第一步都是先运行BIOS(Basic Input/Output System)程序——不知道是不是所以的电脑系统都是如此。BIOS保存在主板上的一个non-volatile(即非易失)存储器,如PROM,EPROM,Flash等。——以前的BIOS一般都是只读的,现代的系统中,允许刷新BIOS程序。它的任务就是简单的初始化和识别系统硬件设备,如CPU,内存,输入/输出设备,外部存储设备等。然后找到bootloader的位置,并加载bootloader,将PC的控制权交给bootloader,完成后面的复杂的系统初始化任务。

但是在系统启动之前,系统如何启动BIOS呢?所以系统启动的过程,也被称为自举。虽然没有“先有鸡还是先有蛋”那么复杂,但是这里也有一个矛盾。PC是这样解决这个问题的。将CPU设计成加电以后,就从一个特殊的固定的地址开始执行指令,那么BIOS的位置就放在这里,也就是存储BIOS的ROM的起始地址就是这个固定的地址,用以保证BIOS程序可以在加电时被直接执行。

这里有两个问题:
1. BIOS的存储器地址如何决定的?
2. 现在多处理器的情况下,BIOS是如何执行的?

下面以Intel CPU为例,简单说明一下流程:
Intel在初始化的时候将CPU分为两类,即BSP(Bootstrap Processor)与APs(Application Processors)。从名字上既可看出两类CPU的作用。在启动的时候,首先由硬件动态选择一个总线上的CPU为BSP,那么剩下的CPU则都为AP。由BSP执行BIOS程序,初始化环境以及APs,然后还是有BSP执行操作系统的初始化代码。 Intel CPU的第一条语句的固定地址为0xFFFF FFF0,然后BIOS的存储器被hard-map到这个内存地址。这样当CPU开始执行时,实际上执行的就是BIOS程序。

由于BIOS的存储器不会太大,所以程序一般不会太复杂,那么不大可能实现加载操作系统的操作,只能完成简单的初始化工作。这时,只能借助于外部存储器了。可是外部存储器的读取是依赖于文件系统的。而BIOS程序既然比较简单,那么是不可能去支持文件系统的,更何况有各种各样的文件系统,不可能去一一支持。这时,还是只能依赖于硬编码,必须定义一个固定的外部存储器的地址——硬盘的第一个扇区的512字节——被称为MBR(Master Boot Record)——为什么是512字节呢?按照我的理解,一般情况下一个扇区都被设置为512字节,而硬盘操作的最小单元即为一个扇区。虽然可以设置更大的扇区,但是作为一个统一的程序来说,使用惯例512是一个不错的选择。

BIOS的最后一项任务就是将MBR读入到内存中,且起始地址固定为0x7c00,然后对MBR的最后两个字节进行验证,必须为0x55和0xAA,以保证这512字节为MBR。验证通过后,则跳转到0x7c00处开始执行。这样MBR就开始执行。——这里,我有两个问题,为什么是7c00和0x55和0xAA呢?目前没有找到当初选择这两个值的解释。我依稀记得选择0x55,0xaa是因为这个值比较特殊,利于校验,但是为什么利于校验却不记得了。

MBR保存了分区表(MBR并不存在于任何一个分区中,而是处于分区之上),以及一个用于装载操作系统启动程序的小程序。MBR首先会确定活动分区,然后使用BIOS将这个活动分区的启动扇区——仍然是第一个扇区512字节,最后跳转到加载该启动扇区的内存地址处。这样就将PC的控制器转移到这个启动扇区的程序手中(即真正的bootloader)。一般来说,这个启动程序也要求被加载到0x7c00这个地址。可是这个地址之前已经加载了MBR,如果再加载这个启动程序,那么必然冲突。所以MBR实际上在开始的时候,先对自己做了relocate,将自己拷贝到另外一个地址,然后从那个地址开始执行,这样就避免了冲突。

下面就进入了真正的bootloader了,对于Linux来说,一般就是LILO和GRUB,下面以最常用的GRUB为例。

GRUB的启动分为三个阶段stage1,stage1.5和stage2,这三个阶段也被分为三个文件(在某些情况下,可以没有stage1和stage1.5)。其中stage1可以嵌入到MBR中,即MBR的头446个字节(后面为分区表64字节,0x55和0xAA两个校验字节),也可以存储在活动分区的第一个扇区512字节, 然后由MBR来加载。所以stage1最多为512字节,如果存储在MBR中,则只能最大为446字节。stage1中保存了stage1.5的地址,并负责加载stage1.5的前512字节。之所以stage1只能加载512字节,是为了遵循MBR的规则。

进入stage1.5,由于只加载了前512字节,所以stage1.5首先要负责把剩余部分代码,由自己加载到内存中。对于stage1.5来说,它可以识别和支持文件系统。可以查看/boot/grub目录下,有多个后缀为stage1.5文件,其前缀即为支持的文件系统,也就是说要支持一个文件系统,就有一个对应的stage1.5文件。至于加载哪个文件,已经硬编码在stage1中。这个文件系统为stage2所在的文件系统。stage2文件是真正保存在文件系统中的。这样通过对应的stage1.5文件,就可以正确加载stage2文件。为什么会有stage1.5这个阶段呢?主要是当stage2不连续或者需要在stage2前,对文件系统做些特殊处理。如果没有这样的需求,完全可以避免stage1.5。

stage2文件为最主要的加载代码,这时由于已经stage1.5已经支持文件系统了,所以stage2可以比较大。stage2来实现GRUB的各种功能,这里就不列举了,感兴趣的同学可以自己查看GRUB的手册。stage2首先需要找到GRUB的配置文件,来决定如何加载操作系统。对于GRUB的配置与本文的主题联系并不紧密,我个人也对其兴趣不大。

GRUB不仅要复杂加载kernel,还要负责加载Initial Ram Disk,又被成为initrd。其目的主要是为了保证一个小体积的内核。initrd为一个简单的文件系统,它包含了一些内核必要的文件和模块。这样,首先将initrd挂载为一个根系统,然后kernel利用这个基本的系统,来检测环境,加载更多的必要的模块。在完成所有的加载后,这时kernel已经完全准备就绪。那么initrd对于kernel来说,已经不需要了。这时,kernel会将initrd从根/上卸载,并挂载上真正的根系统,并执行正常的启动程序。




参考:
1.  Wiki
2. 《Linux内核分析及编程》——倪继利
3. 《Linux内核完全剖析》——赵炯
4. 《Linux内核源代码情景分析》——毛德操  胡希明
5. 《Intel 64 and IA32 Architectures Software Developer‘s Manual》 Volume 3A
6. 《Understanding The Linux Kernel》 Denial P. Bovet & Macro Cesati
7. GUN GRUB Manual

Linux内核初始化流程笔记 (2012-03-13 22:01)
标签:   kernel   初始化  分类:  内核启动


作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用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具体部分的学习,做了铺垫。

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


----------------------------------------------------------------华丽丽的三八线----------------------------------------------------------------


再看一下一位美女的简洁介绍:

http://blog.chinaunix.net/space.php?uid=26712095&do=blog&id=3076752

第一阶段:BIOS启动引导阶段

在该过程中实现硬件的初始化以及查找启动介质

从MBR中装载启动引导管理器(GRUB)并运行该启动引导管理

 

第二阶段:GRUB启动引导阶段

装载stage1

装载stage1.5

装载stage2

读取/boot/grub.conf文件并显示启动菜单

装载所选的kernel和initrd文件到内存中

 

第三阶段:内核阶段

运行内核启动参数

解压initrd文件并挂载initd文件系统,装载必须的驱动

挂载根文件系统

 

第四阶段:Sys V init初始化阶段

启动/sbin/init程序

运行rc.sysinit脚本,设置系统环境,启动swap分区,检查和挂载文件系统

读取/etc/inittab文件,运行在/et/rc.d/rc<#>.d中定义的不同运行级别的服务初始

化脚本

打开字符终端1-6号控制台/打开图形显示管理的7号控制台

 

启动流程和细节详解

BIOS ==> bootloader ==> kernel & initrd.img ==> /sbin/init

 

1.bootloader (grub)

 /boot/grub/grub.conf  <-- grub的配置文件,决定使用哪个kernel和initrd.img

 在/boot/grub目录中有两个stage文件,其中:

      stage1  <--- 大小是512字节,这个文件会被写进MBR中

      stage2  <--- stage1被引导之后,会调用这个文件

 

如果grub出现问题可能会有两种情况:

  a.无法进入grub,屏幕左上角只出现一个光标。出现这种情况基本上MBR已经被破坏,需要进入rescue模式进行修复。

      --> chroot环境,执行 "grub-install /boot所在分区" 进行修复

      --> 无grub相关命令时,安装grub.rpm包进行恢复

      --> /boot目录下无相关的vmlinuz和initrd.img文件时,安装kernel.rpm包进行修复

      --> 无grub.conf时需要手工修复

  b.grub成功加载,屏幕出现“grub>”,表示配置文件grub.conf找不到。                                                       

      --> 手工修复

 

2.kernel & initrd.img

    在加载vmlinuz和initrd时出现:kernel panic情况,基本是grub.conf设置参数出问题。

      --> 检查grub.conf编写是否出现问题

      --> 也有可能是分区设置或者硬盘损坏等问题,需要进入rescue模式检查

 

3./sbin/init

/sbin/init的配置文件是/etc/inittab

按照这个配置文件,系统会依次执行以下脚本:

 

---> /etc/rc.d/rc.sysinit                  

 定义hostname,重新挂载各分区,加载各模块

 

---> /etc/rc.d/rc[0-6].d/目录下以S开头的脚本   

启动各runlevel的服务

 

---> mingetty /dev/tty[1-6]               

 启用终端


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值