系统的启动是指从计算机加电到显示用户登录提示的整个过程。我们将在这儿对整个流程以及关系到的一些内容做讨论。过程主要可以分为两个阶段:载入内核和打算运行环境,我们分别进行讨论。本部份的讨论只基于i386硬件构架,但大部份内容是有共通性的。
图一启动过程综述
载入内核(将内核载入显存,并将控制权传递给它)
计算机加电到BootLoader开始工作,硬件浓度远小于软件浓度,所以这儿暂不提到,假如实在有关心的同学,请先别着急,我们将在上期里讨论它。
这一阶段是BootLoader的主战场。它必须将可执行的内核映像和内核启动所需的额外数据信息从储存介质上载入显存,这并不是件简单的工作,由于不仅从硬碟载入,可能就会须要从网路引导服务器这样的外部介质上载入。各类错综芜杂的文件系统类型也给载入带来了巨大的挑战。
BootLoader可能还须要改变CPU的运行特权级别,之后就可以让内核投入运行了。
除此之外,BootLoader还要完成一些其它功能,例如从BIOS中获取系统信息,或则从启动时的命令行参数中提取信息等。有的BootLoader还要饰演引导选择工具的角色,便捷用户选择不同的操作系统。
BootLoader的职责:
l判定究竟要载入哪些,这可以要求用户进行选择
l载入内核和它可能须要用到的相关数据,例如initrd或则其它参数
l为内核打算好运行环境,例如,让CPU步入特权模式
l让内核投入运行
BootLoader的历史演变:
初期的Linux只支持软驱引导磁道和Shoelace两种BootLoader。Shoelace是从Minix承继出来的、文件系统相关的BootLoader。它只支持Minix文件系统。当时Linux只使用Minix一种文件系统,所以这样做并没哪些问题。而且,Minix文件系统存在不能保存创建、修改和访问时间信息;文件名宽度限制在14个字节等问题。随着Linux的发展,这种与传统Unix文件系统大相径庭的缺陷越来越让人无法忍受,它早已不适宜作为Linux的主要文件系统了。
为了支持其它文件系统的实现,Linux引入了VFS(虚拟文件系统)。这个措施很快就导致了热烈的反响,一大批新的文件系统实现出现了。其中一个Minix文件系统的变体,扩充文件系统Xiafs(依据它的作者命名)突破了Minix文件系统的文件名宽度限制,将此厚度一举提升到全部30个字符。当时文件系统之间的竞争着实激烈,很难看出谁会胜出,甚至搞不清楚会不会有一个最终的“赢家”。
虽然不确定性很大,而且有一点却是清楚的:不管最后哪种文件系统会遭到追捧,然而不仅Minix作为根文件系统,谁也不能从硬碟上启动,由于Shoelace只支持Minix文件系统。LILO应运而生了。因为支持多种文件系统(当时内核支持的主流文件系统早已有Minix,扩充文件系统ext,Xiafs。还有人在移植BSD的FFS,根本看不下来哪些时侯是个尽头)在实现和维护上难度太大,而BootLoader也不应当成为人们试验新的文件系统的试金石,所以LILO采取了和文件系统无关的设计。
这些设计经受住了时间的考验,被证明是十分成功的。虽然在明天,LILO仍然可以从内核支持的绝大部份文件系统的硬碟上启动。并且,因为ext2历经了如此长的时间仍然没有大的演化,成为了事实上的标准,所以跟文件系统相关的BootLoader又逐渐流行了上去。
虽然ext2早已能满足大部份人的日常须要,并且文件系统的设计者们还是在研发以日志机制为特点的新的文件系统,而且早已取得了相当大的进展。考虑到当前又有可能出现多种文件系统的实现同时并存的情况,因而对与文件系统无关的BootLoader的需求可能会再度显得强劲。
初始化基本的操作环境
一旦内核开始运行,它会初始化内部的数据结构,检查硬件,但是激活相应的驱动程序,为应用软件的打算运行环境。期间包含一个重要操作——应用软件的运行环境必需要有一个文件系统,所以内核必须首先装载root文件系统。因为我们的目的是介绍基本流程,所以相关的硬件初始化细节就不再讨论,相关内容在下一期刊物中会有详尽介绍。
硬件初始化完成后,内核着手创建第一个进程——初始进程。说是创建,虽然也不尽然,该进程也许是整个硬件上电初始化过程的延续,只不过执行到这儿,进程的逻辑早已完备,所以我们就根据进程的创建方法给它进行了“规格化”——我们把这个初始进程也称作“硬件进程”,它会抢占进程描述符表的第一个位置,所以可以用task[0][k1]或INIT_TASK表示。该进程因而会再创建一个新进程去执行init()函数linux启动应用程序 命令,虽然,这个新进程才是系统第一个实际有用的进程,它会负责接着执行下一个阶段的初始化操作;而初始进程(INIT_TASK)自己则会开始执行idle循环,也就是说linux运维最佳实践,内核初始化完成以后,初始进程惟一的任务就是在没有任何其它进程须要执行的时侯,消耗空闲的CPU时间(因而初始进程也被称为idle进程)。
下一阶段的初始化工作要比前一阶段轻松一点,由于现今是由一个真正进程来接手负责完成它们了,而前一阶段都是由“硬件进程”手工去做的。在此阶段,这个由INIT_TASK创建的新进程须要初始化总线、网络并启动系统中的各类系统内核后台线程,之后再初始化外设、设置文件格式,在这以后,它要为步入系统做最后的打算——初始化文件系统,安装root文件系统,打开/dev/console设备,重定向stdin、stdout和stderr到控制台,之后搜索文件系统中的init程序,并使用execve()系统调用加载执行init程序。系统此后步入了用户态。
装载root文件系统
为了装载文件系统,内核须要:1、知道root文件系统坐落那种储存介质上;2有访问该种介质的驱动程序。
最常见的情况是,root文件系统是ext2文件系统,坐落IDE硬碟上。这些情况下须要的操作很简单:将设备号作为参数给内核就可以了,IDE的设备驱动程序一般都是编译进内核的。
假如内核没有相关介质的驱动程序,问题都会显得更为复杂。而这些情况并不罕见,例如Linux安装盘使用的“通用”内核通常还会遇到这些情况。假如内核把所有支持的硬件的设备驱动程序都包含进来,还会弄成一个庞然大物;并且一些驱动程序在检查硬件的时侯会影响其它设备。
这个问题可以通过initrd机制解决,它容许在装载实际的root文件系统之前先使用RAM文件系统。不仅上述两个诱因,引入initrd还可以解决内核的动态合成问题。(详见参考资料一。)
不过,我们应当注意到,initrd在整个启动过程中并不是从来就有的,它可以说是一个插件,为了解决以上问题,而被加入启动过程,像图一所示,Linux系统在启动时也可以不选择它。
为何要引入initrd?
Linux启动过程中肯定要载入内核镜像,在此过程中有些要素必须考虑:
首先,内核镜像不能太大。因为遭到各类硬件和兼容性的限制,Linux的内核镜像不能太大,而且这并不容易做到。Linux内核的核心部份本身就不小了;并且还必须加入会使用到的驱动程序。
其次,要支持尽可能多的硬件设备。我们在启动过程中有一件重要工作:挂载root文件系统,由于进一步的数据和应用软件都在其上,所以我们的内核必须才能访问root文件系统。对于通常用户,假如她们使用IDE硬碟上的ext2文件分区作为root文件系统,不会有哪些问题。由于不管是IDE硬碟还是ext2文件系统,它们的驱动肯定会包含在内核镜像自身上面。并且,确实存在一些特殊情况:例如说我们希望发行Linux系统的安装光碟,这么对光碟的驱动,就不一定包含在内核上面了。(有人可能要奇怪了,咦,光碟中的内核镜像不都早已读进来了吗,如何内核还访问不了光碟呢?注意,读入内核镜像的是BootLoader,内核并不具备BootLoader的功能。)假如没有光碟的驱动,我们又如何把光碟里的软件包安装到用户的计算机里呢?把驱动程序预先编译到内核里?听上去还不错,但是假如我们不仅光碟还有一些其它的安装介质,这么所有这种驱动都会让内核镜像庞大不堪。
但是,还有更严重的问题,各类不同的驱动程序很有可能会发生冲突,非常是先前ISA设备占市场主导地位的时侯,这些冲突简直无法防止。
那时的解决办法是发行商提供预先编译好的支持各类设备的不同内核,把每位内核放进一张软驱,随发行包一起交给用户,用户自己选择装有合适内核的软驱进行引导。或则给用户提供制做引导盘的工具,让用户在安装前制做自己的启动盘。其实,哪一种办法都不能让人满意。
惟一的希望在于使用模块化机制。在内核启动的时侯调用相应的模块加载驱动程序,之后访问root文件系统。无论是通过内核对设备做进一步的剖析还是直接从用户哪里得到配置信息,先配置再加载模块的办法,都能有效地防止冲突的发生。
不仅在安装的时侯须要在挂载root文件系统之前调用相应的模块之外,在完成安装的系统上,我们可能一直须要在挂载root文件系统之前调用一些模块。这主要是为给计算机进行配置——一般都要针对不同的计算机进行内核配置。
理想情况下,用户根据自己的实际情况配置编译文件linux启动应用程序 命令,重新编译内核,一步步完成这些工作。并且没有几个用户喜欢这些繁琐而且极易出现错误的工作。并且编译和生成内核须要相应的工具,而且大部份用户不须要这种工具。
在安装的过程中可以直接编译一个整体式的内核,但这并不能挺好的解决问题:首先,所有的编译工具还是须要的,其次,编译过程中出现差错造成难以完成任务的机率太大了。所以,我们依然要使用模块机制:模块机制很可靠,出了错误也只不过不加载对应的模块而已,不会使整个任务失败。而载入模块,像上面说的,也是在挂载root文件系统之前就要得到模块的。
基于以上理由,Linux引入了initrd机制。
initrd做哪些
initrd容许系统在启动的时侯载入一个RAM盘,这个RAM盘可以被当成一个root文件系统,程序可以在其上运行。(有两重含意,第一,程序在里面;第二,程序的文件系统环境也在里面。)在此以后小型linux系统,可以从别的设备上挂载一个新的root文件系统,原本的root文件系统(initrd)都会被联通到一个目录起来,最终被卸载掉。
为何要使用RAM盘呢?首先,使用RAM盘能便捷的支持之后可能发生的变化;另外,也是为了保持BootLoader工作尽可能的简单。在系统引导时,不仅内核镜像之外,BootLoader把所有相关的信息作为一个文件读入显存,内核在启动上将该文件作为一段连续的显存块看待。也就是把它当成RAM盘来使用了。正由于这般,这些机制被叫做“初始RAM盘(initialRAMDisk)”,简写成initrd。
initrd主要拿来把系统的启动界定为两个阶段:初始启动的内核只需保留最精简的驱动程序最小集,随后,在启动必须加载附加的模块时,从initrd中加载。
initrd进行的操作
使用initrd的时侯,典型的系统启动的流程变为:
1)BootLoader读入内核镜像以及initrd文件
2)内核将initrd文件转成“普通”的RAM盘,而且释放掉initrd文件占用的显存。
3)initrd被当成root文件系统,以可读可写(read-write)形式安装。
4)/linuxrc被执行(它可以是任何可执行文件,包括脚本在内;它以uid0身分执行,基本上能完成所有init程序可以做的工作)
5)linuxrc安装“实际”的root文件系统
6)linuxrc通过pivot_root系统调用将root文件系统放置在root目录下。
7)常用的启动流程(例如调用/sbin/init)开始执行。
8)卸载initrd文件系统。
注意,这是一个典型流程。虽然initrd机制可以通过两种方法使用:要么就是作为一个普通的root文件系统使用,这样的话第5、第6两个步骤可以被略过,直接执行/sbin/init(我们的试验系统就是借助这些技巧);要么作为一个过渡环境使用,通过它内核可以继续装载“实际”的root文件系统。