📄 4.txt
字号:
第4章 系统初始化当你想要运行程序时,你需要把程序的文件名敲入shell—或者更为流行的,在如GNOME或者KDE等之类桌面环境中点击相应的图标,这样就能将其装载进内核并运行。但是,首先必须有其他的软件来装载并运行内核;这通常是诸如LOADLIN或者LILO之类的内核引导程序。更进一步说,我们还需要其他的软件来装载运行内核引导程序—称之“内核引导程序的引导程序”,而且看起来似乎运行内核引导程序的引导程序也需要内核引导程序的引导程序的引导程序,等等,这个过程是无限的。这个无限循环的过程必然最终在某个地方终止,这就是硬件。因此,在最低的层次上,启动系统的第一步是从硬件中获得帮助。该硬件总是运行一些短小的内置程序—软件,但是这些软件是被固化在只读存储器中,存储在已知地址中。因此,在这种情况下就不需要软件引导程序了—它能够运行更大更复杂的程序,直到内核自身装载成功为止。按照这种方式,系统自己的引导过程(bootstrap)会引发系统的启动,当然这只是术语“系统引导(booting)”的一个比喻。虽然不同体系结构的引导过程的具体细节差异很大,但是它们的原则都基本相同。前面的工作都完成以后,内核就已经成功装载了。随后内核可以初始化自身以及系统的其他部分。本章首先将简单介绍基于x86 PC机的典型自启动方式,接着回顾一下每一步工作在什么时机发生,最后我们还要介绍的是内核的相应部分。4.1 引导PC机本节简要介绍x86 PC是如何引导的。本节的目的不是让你精通PC是怎样引导的(这超出了本书的范围),而是向你展示特定体系结构一般的引导方式,为下文中的内核初始化进行铺垫。首先,机器中的每个CPU都要自行初始化,接着可能要用几分之一秒的时间来执行自测试。在多处理器的系统中,这个过程会更复杂些—但是实际上也并不多。在双处理器的Pentium系统中,一个CPU总是作为主CPU存在,另外一个CPU则是辅CPU。主CPU执行启动过程中的剩余工作,随后内核才会激活辅CPU。在多处理器的Pentium Pro系统中,CPU必须根据Intel定义的算法“抢夺标志”来动态决定由哪个CPU启动系统。取得标志的CPU启动系统,随后内核激活其他的CPU。无论是哪种情况,启动程序的剩余部分只与一个CPU有关。这样,在随后的一段时间内,我们可以认为该系统中只有一个CPU是可用的,而不考虑其他的CPU,或者说这些CPU被暂时隐藏了。另一方面,内核还需要明确地激活所有其他的CPU—这一点你可以在本章后续部分看到。接下来,CPU从0xfffffff0地址单元中取得指令并执行,这个地址非常接近于32位CPU的最后可用的地址。因为大多数PC都没有4GB的RAM,所以通常在这个地址上并没有实际内存。内存硬件可以虚拟使用它。对那些确实有4GB内存的机器来说,它们也只是仅仅损失了供BIOS使用的顶端地址空间末尾的少量内存(实际上BIOS在这里只保留了64K的空间—这种损失在4GB的机器中是可以忽略的)。该地址单元中存储的指令是一条跳转指令,这条指令跳转到基本输入输出系统(BIOS)代码的首部。BIOS内置在主板中,它主要负责控制系统的启动。请注意CPU实际上并不真正关心BIOS是否存在,这样就使得在诸如用户定制的嵌入系统之类的非PC体系结构的计算机中使用Intel的CPU成为可能。CPU执行在目标地址中发现的任何指令,在这里使用跳转指令转移到BIOS只是PC体系结构的一部分(实际上,跳转指令自己是BIOS的一部分,但是这不是考虑这个问题的最方便的方法)。BIOS使用内置的规则来选择启动设备。通常情况下,这些规则是可以改变的,方法是在启动过程开始时按下一个键(例如,在我的系统中是Del键)并通过一些菜单选项浏览选择。但是,通常的过程是BIOS首先试图从软盘启动,如果失败了,就再试图从主硬盘上启动。如果又失败了,就再试图从CD-ROM上启动。为了使问题更具体,这里讨论的情况假定是最普通的,也就是启动设备是硬盘。从这种启动设备上启动,BIOS读取第一个扇区的信息—首512个字节,称之为主引导记录(MBR)。接下来发生的内容有赖于Linux是怎样在系统上安装的。为使讨论形象具体,我们假定LILO是内核的载入程序。在典型的设置中,BIOS检测MBR中的关键数字(为了确认该数据段的确是MBR),并在MBR中检测引导扇区的位置。这一扇区包含了LILO的开始部分,然后BIOS将其装入内存,开始执行。注意我们现在已经实现了从硬件和内置软件的范围到实际软件范围的转变,从有形范围到无形范围,也就是说从你可以接触的部分到不可接触的部分。下面就是LILO的责任了。它把自己其余的部分装载进来,在磁盘上找到配置数据,这些数据指明从什么地方可以得到内核,启动时要通过什么选项。LILO接着装载内核到内存并跳转到内核。通常,内核以压缩形式存储,只有少量足以完成解压缩任务的指令,也就是自解压可执行文件,是以非压缩形式存储的。因此,内核的下一步工作是自解压缩内核镜像。到这里,内核就已经完成了装载的过程。下面是对所进行步骤的简要描述:1) CPU初始化自身,接着在固定位置执行一条指令。2) 这条指令跳转到BIOS中。3) BIOS找到启动设备并获取MBR,该MBR指向LILO。4) BIOS装载并把控制权转交给LILO。5) LILO装载压缩内核。6) 压缩内核自解压,并把控制权转交给解压的内核。正如你所见到的,引导过程每一步都将你带入更大量更复杂的代码块中,一直到最后成功地运行了内核为止。依赖于你计算层次的方式,CPU成为内核引导程序的引导程序的引导程序的引导程序(CPU装载BIOS,BIOS装载LILO,LILO装载压缩内核,压缩内核装载解压内核;但是你可以考虑是否这些步骤都满足引导程序的定义)。4.2 初始化Linux内核在内核成功装入内存(如果需要就解压缩)以及一些关键硬件,例如已经在低层设置过的内存管理器(MMU,请参见第8章)之后,内核将跳转到start_kernel(19802行)。这个函数完成其余的系统初始化工作—实际上,几乎所有的初始化工作都是由这个函数实现的。因此,start_kernel就是本节的核心。 start_kernel19802:__init标示符在gcc编译器中指定将该函数置于内核的特定区域。在内核完成自身初始化之后,就试图释放这个特定区域。实际上,内核中存在两个这样的区域,.text.init和.data.init—第一个是代码初始化使用的,另外一个是数据初始化使用的(可以在进程间共享的代码和字符串常量之类的“文本(Text)”是在可执行程序中的“纯区域”中使用的一个术语)。另外你也可以看到__initfunc和__initdata标志,前者和__init类似,标志初始化专用代码,后者则标志初始化专用数据。19807:如前所述,即使在多处理器系统中,在启动时也只使用一个CPU。Intel称之为引导程序处理器(bootstrap processor,简称为BSP),它在内核代码的某些地方有时也称之为BP。BSP首次运行这一行时,跳过后面的if语句,并减小boot_cpu标志,从而当其他CPU运行到此处时,都要运行if语句。等到其他CPU被激活执行到这里时,BSP已经在idle循环中了(本章稍后会更详细地讨论这个问题),initialize_secondary(4355行)负责把其他CPU加入到BSP中。这样,其他CPU就不用执行start_kernel的剩余部分了—这也是一件好事,因为这意味着不用再对许多硬件进行冗余初始化等工作了。顺便说一下,这种奇异的小小的改动只有对于x86是必需的;对于其他平台,调用smp_init完全可以处理SMP设置的其他部分。因此,其他平台的initialize_secondary的定义都是空的。19816:打印内核标题信息(20099行),这里显示了有关内核如何编译的信息,包括在什么机器上编译,什么时间编译,使用什么版本的编译器,等等。如果中间任何一步发生了错误,在寻找机器不能启动的原因时查明内核的来源是一个有用的线索。19817:初始化内核自身的部分组件—内存、硬件中断、调度程序,等等。尤其是setup_arch函数(19765行)完成体系结构相关的设置,此后在command_line(传递到内核的参数,在下面讨论)、memory_start和memory_end(内核可用物理地址范围)中返回结果。下面这些函数都希望驻留在内存低端,它们使用memory_start和memory_end来传递该信息。在函数获得所希望的值后,返回值指明了新的memory_start的值。19823:分析传给内核的各种选项。parse_options函数(19707行,在随后的“分析内核选项”一节中讨论)也设置了argv和envp的初值。19833:内核运行过程中也可以自行对所进行的工作进行记录,周期性地对所执行的指令进行抽样,并使用所获得的结果更新表格。这在定时器中断过程中通过调用x86_do_profile(1896行)来实现,该部分将在第6章中介绍。如图4-1中说明的那样,这个表格把内核划分为几个大小相同的范围,并简单跟踪在一次中断的时间内每个范围中运行多少条指令。这种记录当然是非常粗糙的—甚至不是依据函数和行号进行划分的,而只是使用近似的地址—但是这样代价很低,且快速、短小,而且有助于专家判断最关键的问题。每个表格条目所涉及到地址的多少—还有问题发生地点的不确定性—可以通过简单修改prof_shift(26142行)来调节。profile_setup(19076行,在本章中后面讨论)可以让你在启动的时候设置prof_shift的值,这样比为修改这个数字而重新编译内核要清晰方便得多。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -