2.6.27.7启动过程概述

arch/x86/boot/head.S:_start

                                    ->.byte 0xeb

                                        .byte start_of_setup-1f

 |

V

arch/x86/boot/head.S:start_of_setup

                                    ->calll main

 |

V

arch/x86/boot./main.c:main

                                    ->go_to_protected_mode();

 |

V

arch/x86/boot./pm.c:go_to_protected_mode()

                                   ->protected_mode_jump()

 |

V

arch/x86/boot/pmjump.S:protected_mode_jump

                                        ->jmpl *%eax   # Jump to the 32-bit entrypoint

 |

V

arch/x86/kernel/head_32.S:startup_32

                                            ->i386_start_kernel

 |

V

arch/x86/kernel/head32.c:void __init i386_start_kernel(void)

                                            ->start_kernel();

 |

V

init/main.c:start_kernel()

 

系统启动完成。

附:ULk 附录A译文(基于2.6.11)

系统启动过程这部分附录内容解释了在用户接通计算机电源之后,具体发生了什么事情  也就是Linux内核映像如何被拷贝到内存并执行的。  简而言之,我们将讨论内核也就是整个系统,如何启动的。如何[bootstrapped] 

传统上来说,[bootstrap]的意思为一个人打算穿上鞋站起来。在操作系统术语中,这个词代表着至少  要把操作系统的一部分(或者全部)载入到内存,并且使处理器开始执行它。  它也代表着对内核数据结构的初始化和一些用户进程的创建过程,并且将控制权转移到其中的某一个用户进程。

计算机的[bootstrap]是一个冗长乏味的工作,因为在初期(系统上电),几乎每个硬件设备,包块RAM,都处在随机的不可预知的状态。而且[bootstrap]过程十分依赖计算机的体系结构。就像本书中通常一样。我们介绍80X86的启动过程。

A.1.史前时代:BIOS 刚刚上电之后,计算机实际上是无用的,因为RAM芯片中的值是随机的并且没有操作系统在运行。为了开始 boot。一种特殊的硬件电路(RESET电路),向CPU的RESET引脚发出RESET信号。CPU接收到RESET信号,会将一些寄存器设定固定值 (包括CS 和EIP),然后从物理地址0xfffffff0开始执行。这个地址被映射到ROM。存储在ROM中0xfffffff0地址的程序通常被称为Basic Input/Output System (BIOS)。在X86体系结构中,因为BIOS包含了几个中断驱动的低级的过程,这个过程可以被任何的操作系统用于在boot阶段来控制硬件设备。一些 操作系统例如MS-DOS,依赖BIOS实现所有的系统调用。

一旦进入了保护模式,Linux就不再使用BIOS了,而是为每个硬件提供自己的设备驱动程序。事实上,BIOS程序必须执行在实模式下,因此无法共享一些功能(函数),即使共享会带来好处。(这里不太理解)

BIOS使用实模式的地址空间,因为在计算机开机之后,只有他们才是可用的。实模式地址由段地质和段内的偏移组成对应的物理地址为段地址*16 +段内偏移,因此CPU的寻址单元将逻辑地址转换位物理地址的时候,不需要全局描述符表。局部描述符表,页表。很明显,初始化全局描述符表,局部描述符表 和页表的代码必须在实模式下。

Linux在[bootstrap]阶段需要使用BIOS,来找到内核映像在磁盘中(或者其他外部设备)的位置。BIOS bootstrap 实际上完成了下面四项的工作: 1,执行一系列检测硬件的命令,目的是确定哪些设备安装在了计算机上,它们是否工作正常。这个阶段通常被称为Power-On Self-Test (POST) 在这个阶段中,一些信息,如BIOS的版本号,被通过显示器显示出来。

2,初始化硬件设备。这个阶段对于现代基于PCI体系的系统非常关键,因为其 允许所有的硬件设备在没有配置之前使用中断请求和输入/输出端口。在这个阶段结束之后,会表示出来已经安装的PCI设备。

3,查找一个操作系统来启动,实际上依赖BIOS的设定。BIOS也许尝试(预定义的顺序,或者通过用户定制)软盘的第一个扇区,硬盘的第一个扇 区,光盘的第一个扇区。 4,只要找到一个有效的设备,bios就把第一个扇区的内容拷贝到内存0x7c00。并且挑砖到该地址开始执行。

本附录的其他内容将带你领略Linux系统的基本的启动状态到系统的完全运行的过程。

A.2远古时代:BootLoader(启动程序)启动程序由BIOS调用,它将操作系统的内核映像载入内存。让我们来简要勾勒一下在IBM的PC 上,BootLoader是如何工作的为了使用软盘启动,存储在软盘第一个扇区的内容被拷贝到内存并且执行。这些指令将内核映像的其它扇区拷贝到内存。

而从硬盘启动就会有一些不同。硬盘的第一个扇区被称为MBR,MBR包含分区表和一个小程序。这个小程序将把包含用户想要启动操作系统的分区的第一 个扇区拷贝到内存。例如WIN98,通过在分区表中的活动标志来选择哪个分区是活动的。也就是说,只有操作系统的内核映像载活动分区中,那个它才有可能被 启动。

Linux使用更加灵活的方式,因为它替换了原来MBR中的原始程序,而是用成熟的BootLoader程序。BootLoader允许用户选择想 要启动哪个操作系统。 2.4版本以前的内核,一如既往的包含了一个小的BootLoader程序,在第一个512字节中。因此把从软盘把内核拷贝到内存,从而可以启动系统。然 而,从2.6版本开始,内核不再包含此BootLoader,因此如果想要从一个软盘启动,那么就需要把合适的BootLoader存储在软盘的第一个分 区。现在,从软盘启动和从硬盘启动。光盘启动基本都一致了。

A2.1从硬盘启动为了从硬盘启动,需要一个two-stage的BootLoader. 如LILO ,GRUB要比LILO先进,因为它可以识别几种文件系统,因此可以从文件中读取部分启动程序。当然,存在一些特殊的BootLoader可以支持 Linux支持的所有的体系结构。

LILO或者被安装在MBR,或者被安装在任何活动分区的BootSector,两种情况的最终结果都是一样的。当Loader执行的时候,用户必须选择启动哪个系统。

实际上。LILO BootLoader程序超过了一个扇区(512字节)因此它分为两部分,安装在MBR或者某个分区的BootSector中的小启动程序。这个小启动程 序由BIOS拷贝到内存0X7C00。然后它将自己搬运到0X96A00。设置实模式的栈(0X98000 --0X969FF) 然后再把另外一部分的LILO BootLoader程序拷贝到0X96C00,并调转到0X96C00开始执行。后面的这个程序从硬盘读取可以启动的 的操作系统,并由用户选择。  最终,LILLO BootLoadre或者把对应的某个分区的BootSector拷贝到内存,或者把内核映像拷贝到内存。  LILO实际上做了下面的工作:  1,调用BIOS的例程,表示Loading 信息。  2,调用BIOS例程,把内核映像前512字节拷贝到0X90000地址。而setup()函数的地址为0X90200。  3,调用BIOS例程,将内核映像的其他部分从硬盘 拷贝到内存0X10000(或者0X10000,对于大内核)。  4,跳转到setup()函数。 

A.3中古时代:setup()函数 setup()汇编语言函数的代码被链接器配置到内核映像文件内的偏移0X200处。因此BootLoader可以轻松的找到它,并且把它拷贝到内存的0X90200地址。 setup()函数必须要初始化计算机的所有硬件设备,并且为内核的执行构建好环境。虽然BIOS已经初始化了所有硬件设备,但是Linux并不依靠BIOS,而是使用自己的方式重新初始化硬件设备,因而来提供程序的可移植性和健壮性。

setup()实际上做了下面的工作 1,对于ACPI(什么是ACPI,不了解)兼容的 系统,调用BIOS的例程在物理内存中建立一张描述系统物理内存的表 在比较老的系统中,只是简单的调用BIOS的例程来获取系统可用内存的数量。 2,设定键盘的重复延迟和比率(当用户按下一个按键一段时间,键盘控制器会把按键对应的键位码一遍又一遍的发送给CPU)。 3,初始化图形适配卡。 4,初始化硬盘控制器,并且获得硬盘参数(具体都有什么,不了解)。 5,检查IBM微通道总线(MCA)。 6,检查PS/2指点设备。 7,检查高级电源管理(APM,不太了解)的BIOS支持。 8,如果BIOS支持增强的磁盘驱动服务(EDD,不太了解),那么调用适当的BIOS例程在内存中建立一张用来描述可用硬盘的表。(表中包含的信息可以 通过读取 sysfs特殊文件系统的 firmware/edd目录)。 9,如果内核映像被装载到0X10000,那么就把内核移动到0X1000。相反,如果内核映像被装载到0X100000,则不进行移动。这个过程是必需 的,因为为了能使用软盘保存内核映像和缩短boot时间,存储在硬盘上的内核映像是被压缩过的。 而解压缩过程需要内存中,在内核的映像的后面存在空闲空间来作为临时的缓冲区。 10,设定8042键盘控制器的A20引脚。A20引脚作为一个hack在80286及其后来的系统中被引入。作用是为了保持和早期8088的系统保持兼 容。不幸的是,在进入到保护模式之前,必须要正确设定A20引脚。否则第20根以后的地址线将总是被CPU当作0,设定A20引脚是一个凌乱的过程。 11,建立一个临时的中断描述符表。(IDT)和临时的全局描述符表(GDT)。 12,如果有浮点单元,重新设定。 13,关中断。除了IRQ2(我想是两个8259A级联,使用IRQ2)。 14,设置CR0中的PE标志位。从实 模式切换到保护模式。这时,CR0的PG标志位依然没有被设定,所以分页依然没有启用。 15,跳转到汇编语言函数startup_32( )。arch/i386/boot/compressed/head.S

A.4文艺复兴时代:startup_32( ) 函数 存在两个版本的startup_32( ) 函数,我们现在所指的是arch/i386/boot/compressed/head.S 文件中的startup_32( )函数。 setup()函数执行结束之后,startup_32()函数已经被移动到了0X100000(loaded high)地址,或者0X1000(loaded low)地址。

startup_32( ) 函数实际上完成下面工作: 1,初始化各个段寄存器和一个临时的栈。 2,清除EFLAG寄存器中的所有位。 3,将内核未初始化数据清0(未初始化数据从_edata 开始,到_end结束)。 4,调用decompress_kernel( )函数解压内核映像。首先表示"Uncompressing Linux..."信息,当解压完成后,the "O K, booting the kernel." 信息被表示。 解压后的内核被放在0x00100000(loaded low),或者内核映像的后面(loaded high).然后将解 压后的内核搬运到最终的位置:0x00100000。 5,跳转到0x00100000。

解压后的内核映像以arch/i386/kernel/head.S 文件中的startup_32( ) 函数作为开始。 使用两个同名字的函数不会产生实际的问题,除了使读者赶到困惑之外,因为这两个函数的执行都是通过跳转到初始地址来实现的。

后一个startup_32( ) 函数,为0号进程设置好执行环境。此函数中进行了下面的操作: 1,初始化段寄存器为最终的值。 2,填充内核的bss段(BSS段,不太了解)为0。 3,初始化内核临时页表(保存在swapper_pg_dir 和pg0),把逻辑地址映射为相同的物理地址。 4,在CR3寄存器中,保存全局页表目录(PGD),并把CR0中的PE标志位置1,启动分页。 5,为0号进程设定好内核模式栈。 6,清除EFLAG标志寄存器中的所有位。 7,调用setup_idt( )函数,将中断向量表填充为中断向量。 8,将从BIOS得到的系统参数和传递给系统的参数,保存在第一页中。 9,设别处理器的类型。 10,将全局描述符表的地址和中断描述符表的地址分别载入GDTR 和 IDTR寄存器。 11,跳转到start_kernel( )函数。

A.5现代:start_kernel()函数

start_kernel()函数完成对内核的初始化。几乎内核的所有组件都是被start_kernel()函数初始化的。

这里仅仅介绍其中的几个:

1,调度器通过sched_init函数初始化。

2,内存区域通过函数build_all_zonelists()函数初始化。

3,buddy系统分配器通过page_alloc_init()和mem_init()函数初始化。

4,中断向量表通过trap_init()函数初始化。

5,TASKLET_SOFTIRQ和HI_SOFTIRQ通过softirq_init()函数初始化。

6,系统时间和日期通过time_init()函数初始化。

7,slab分配器通过kmem_cache_init()函数初始化。

8,cpu时钟通过calibrate_delay()函数决定。

9,通过kernel_thread()函数,穿件1号进程。1号进城创建其它内核线程并且执行/sbin/init程序。

除了在start_kernel()函数开始表示"Linux version 2.11...”的信息外,在这个过程中,很对信息通过内核线程和init程序被表示出来。最终,通常的login提示在控制台(或者图形界面)表示。告 诉用户,linux内核已经开始运行了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值