linux 2.6源代码情景分析笔记之系统启动

来源:互联网 发布:c语言ln函数 编辑:程序博客网 时间:2024/05/11 01:35

最开始时,ram芯片中包含的是随机数据。当开始启动时,cpu的一个引脚上会产生一个reset逻辑值。此后处理器的一些寄存器设置成固定的数值,并执行在物理地址0xfffffff0处找到的代码。硬件把这个地址映射到某个只读、持久的存储芯片中,该芯片通常称为rom(read-only memory只读内存)。rom所存放的程序集在80x86体系中通常叫做基本输入输出系统(basic input/output system,BIOS),因为它包括几个中断驱动的低级过程。所有操作系统启动时,都要通过这些过程对计算机硬件设备初始化。当计算机进入保护模式,就不再使用BIOS,而是为计算机上的每个硬件设备提供各自的设备驱动程序。因为bios过程必须运行在实模式下,所以两者之间并不能共享函数。

BIOS使用实模式的地址,因为在计算机加电启动时只有这些可以使用。一个实模式的地址由一个seg段和一个off偏移量组成。相应的物理地址如此计算:seg*16+off.所以,cpu寻址电路不需要全局描述符表,局部描述符表或者页表把逻辑地址转换成物理地址。对GDT,LDT和页表进行初始化的代码必须在实模式下运行。

bios启动过程:
1、对计算机硬件执行一系列的测试,用来检测现在都有什么设备以及这些设备是否正常工作。这个阶段通常称为post(power-on self-test,上电自检)。在这个阶段中会显示一些信息。
2、初始化硬件设备。这个阶段在现代基于pci的体系结构中相当重要,因为它可以保证所有的硬件设备操作不会引起irq线与i/o端口的冲突。在此阶段后,会现实系统中所安装的所有pci设备的列表。
3、搜索一个操作系统来启动。实际上,根据bios的设置,这个过程可能要试图访问系统中的软盘、硬盘和CD-ROM的第一个扇区(引导扇区)。
4、只要找到一个有效的设备,就把第一个扇区的内容拷贝到ram中,从物理地址0x00007c00开始的位置,然后跳转到这个地址处,开始执行刚才装载近来的代码。

引导程序是由biso用来把操作系统的内核映像装载到ram中所调用的一个程序。
在bios中,初始引导仅仅是其功能的一部分,而是采用初始引导程序加引导扇区的方案,让各种操作系统通过引导扇区进一步提供其自身的引导手段。
在linux中,并不使用bios所提供的设备驱动,而是绕开bios,从硬件接口(寄存器)和中断响应开始彻底地实现自己的设备驱动层。故在linux中,bios的作用仅仅是初始引导,以及加电以后的自检以及向内核提供此过程中搜集到的一些信息。

在实际使用中,把一个硬盘分成若干“分区”,从而把一个物理的硬盘划分成若干个逻辑磁盘。每个逻辑磁盘的第一个扇区仍然是引导扇区。分别用于相应逻辑磁盘中的操作系统映像。这些引导扇区在物理上当然不再是整个硬盘的第一个扇区了。在此情况下,整个硬盘的第一个扇区不属于任何一个逻辑磁盘,或者说已经超脱于所有这些逻辑磁盘之外。bios依然将它当作整个硬盘的引导扇区,加电时还是从这个扇区“引导”,所以此扇区也称为“主引导记录块(扇区)”MBR。MBR中含有关于盘区划分的信息,还有一段简短的程序,一共512字节。mbr中的程序并不直接引导操作系统,而是根据盘区划分的信息从一个预定的“活跃”逻辑磁盘中读入其引导扇区,再由这个引导扇区自己采取进一步的行动。所以可以把mbr的作用看作是为bios中的初始化引导程序提供了一种类似于间接寻址的手段。

linux的引导装入程序叫LInux LOader(LILO),或者装载mbr上,或者被装在每个磁盘分区的引导扇区上。lilo引导装入程序被分为两部分,mbr或者分区引导扇区包括一个小的引导装入程序,由bios把这个小程序装入地址0x00007c00开始的ram中。这个小程序将自身移到0x00096a00,建立实模式栈(0x00098000-0x000969ff),并把lilo的第二部分装入到从地址0x00096c00开始的ram中。第二部分又依次从磁盘读取可用操作系统的映射表,并提供给用户一个提示符,因此用户可以选择一个操作系统。引导装入程序就可以把相应分区的引导扇区拷贝到ram中并执行它,或直接把内核映像拷贝到ram中。
BOOTSEG         = 0x07C0                /* original address of boot-sector */
代码先将自身搬运到0x7c00处,跳转到此处,开始执行引导扇区代码。然后代码将自身运到0x90000,跳转到哪里继续执行
        int     $0x13
然后用int 13从磁盘上读入setup和内核映像。
INITSEG  = DEF_INITSEG          # 0x9000, we move boot here, out of the way
DELTA_INITSEG = SETUPSEG - INITSEG      # 0x0020
然后将setup的映像读入地址0x90200地方。
jmpi    0x100000,__BOOT_CS
解压后的zimage或者bzimage都放在0x100000。此时进入32位保护模式的段寻址方式,进入内核后,就不再使用bios了。跳转到0x100000,开始执行内核,内核进入初始化阶段。

linux/arch/i386/boot/compressed/head.S
在setup()结束后,此函数就被移动到物理地址0x00100000处或者0x00001000处,这取决于内核映像是被高装载到ram中还是低装载到ram中。
解读函数:

startup_32:
        cld
        cli
        movl $(__BOOT_DS),%eax
        movl %eax,%ds
        movl %eax,%es
        movl %eax,%fs
        movl %eax,%gs

        lss stack_start,%esp
其中的boot_ds内存中已经有谈及。初始化了段寄存器和一个临时队栈。

        xorl %eax,%eax
1:      incl %eax               # check that A20 really IS enabled
        movl %eax,0x000000      # loop forever if it isn't
        cmpl %eax,0x100000
        je 1b

测试A20是否真的置位。前面在切换到保护模式之前,已经将A20引脚正式置位,不然每个物理地址第21位都会被cpu看作0.此时再验证。

        xorl %eax,%eax
        movl $_edata,%edi
        movl $_end,%ecx
        subl %edi,%ecx
        cld
        rep
        stosb

用0填充_edata和_end符号标识的内核未初始化数据区。在linux填充ram时分几个部分。符号_text对应于物理地址0x00100000,表示内核代码的第一个字节的地址。内核代码的结束位置由另外一个类似的符号_etext表示。内核数据分两组,初始化的数据和没初始化的数据。初始化的数据在_etext后开始,在_edata处结束。紧接着是未初始化的数据并以_end结束。这些符号在编译内核时产生,可以在system.map文件中找到这些符号的线性地址。

        subl $16,%esp   # place for structure on the stack
        movl %esp,%eax
        pushl %esi      # real mode pointer as second arg
        pushl %eax      # address of structure as first arg
        call decompress_kernel
        orl  %eax,%eax
        jnz  3f
        popl %esi       # discard address
        popl %esi       # real mode pointer
        xorl %ebx,%ebx
        ljmp $(__BOOT_CS), $0x100000

decompress_kernel是解压内核映像的。完成解压后内核被放在合适的位置,如果是低装载的在0x00100000,如果是高装载,则在这个压缩映像之后的临时缓冲区中,解压后的映像就被移动到从物理地址0x00100000开始的最终位置。
然后跳转到0x00100000.

原创粉丝点击