Linux 的初始化与启动过程

我们运行程序只需要点击应用程序的图标就可以了,但在这之前,我们必须启动我们的系统。在一切之前,我们必须有某些程序去引导我们系统的内核,这些程序就是内核引导程序了,例如LILO、GRUB、U-Boot、RedBoot。而这些引导程序同样需要被其他程序加载和运行,这样说下去,茫茫人生何处才是尽头啊?想必大家可以想到的—-硬件!这么长的过程复杂、崎岖!正所谓万事开头难,但不怕,我们来一起走过去吧!

X86的引导过程如图:

cpu自身的初始化:这是引导的第一步,如果在多处理器系统上,那么每个cpu都要自身初始化。cpu初始化后,cpu从某个固定的位置(应该是0Xfffffff0)取指,这条指令是跳转指令,目的地是BIOS的首部代码,但是cpu并不在乎BIOS是否存在,它仅仅只是执行这个地址的指令而已!

BIOS:BIOS是只读存储器(ROM),被固化于主板上。其工作主要有两个,就是上图的加电自检即是POST(post on self test)与加载内核引导程序。

那么他们是具体完成什么工作的呢?

1) 加电自检:完成系统的硬件检测,其中包括内存检测、系统总线检测等工作。

2) 加载内核引导程序:在POST完成后,就要加载内核引导程序了,那它保存在哪里呢?磁盘里!哈哈,BIOS会读取0磁头,0磁道,一扇区的512个字节,这个扇区有叫做MBR(主引导记录),MBR中保存了内核引导程序的开始部分,BIOS将其装入内存执行。512个字节的MBR有些什么呢?这里有必要说说MBR!MBR分区表以80为起始,以55AA为结束,共64个字节。具体的MBR知识自己百度!

MBR:1) 446个字节的引导程序代码

2) 64个字节的分区表,有多少个分区呢。。?这还真不知道!分为4个分区表,一个可启动分区和三个不可启动分区。

3) 2个字节的0XAA55,用于检查MBR是否有效。

需要注意的是,内核引导程序被加载完后,POST部分的代码会被从内存中清理,只留部分在内存中留给目标操作系统使用。

内核引导程序:内核引导程序分两部分:主、次引导程序。主引导程序的主要工作就是收索,寻找活动的分区,将活动的分区引导记录中的次引导程序加载到内存中并且执行。而这个次引导程序就是负责加载内核的并且将控制权交给内核。上面提过内核引导程序有LILO、GRUB、U-Boot、RedBoot。其中前面两个为pc中的,而后面两个是嵌入式的。

内核:内核以压缩的形式存在,不是一个可执行的内核!所以内核阶段首先要做的是自解压内核映像。这里说说编译内核后形成的内核压缩的映像vmlinuz。编译生成vmlinux后,一般会对其进行压缩为vmlinuz,使其成为zImage–小于512KB的小内核,或者成为bzImage–大于512KB的大内核。

vmlinuz结构如图:

做了这么多工作终于把linux的内核给引导出来了。!!下面我们来初始化这个千呼万唤始出来的linux内核!

内核初始化:内核会调用一系列的初始化函数去对所有的内核组件进行初始化,由start_kernel()—…..—> rest_init() —-..—-> kernel_init() —-….–> init_post() ——到—> 第一个用户init进程结束。

start_kernel():其完成大部分内核初始化的工作。相关的代码去查阅内核的源代码吧!

rest_init():start_kernel() 调用rest_init() 进行后面的初始化工作。

kernel_init():此函数主要完成设备驱动程序的初始化,并且调用 init_post() 启动用户空间的init进程。

init_post():初始化的尾声,第一个用户空间的init 横空出世!其PID始终为1。

init: 内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。找到/sbin/init 后init会根据/etc/inittab (网上的资料都这样说的,我在Ubuntu3.8的内核里找不到,但在Fedora中可以找到!是发行版本不同吧?(求指教)这里附上图片!)文件完成其他一些工作,例如:getty进程接受用户的登录,设置网络等。这里详细说说吧。

fedora19的etc有inittab文件:

系统中所有的进程形成树型结构,而这棵树的根就是在内核态形成的,系统自动构造的0号进程,它是所有的进程的祖先。大致是在vmlinux的入口 startup_32(head.S)中为pid号为0的原始进程设置了执行环境,然后原是进程开始执行start_kernel()完成Linux内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。继而调用 fork(),创建第内核init进程:

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); // 参数CLONE_FS | CLONE_SIGHAND表示0号线程和1号线程分别共享文件系统(CLONE_FS)、打开的文件(CLONE_FILES)和信号处理程序 (CLONE_SIGHAND)。

这个进程就是著名的pid为1的init进程(内核态的),它会继续完成剩下的初始化工作比且创建若干个用于高速缓存和虚拟主存管理的内核线程,如kswapd和bdflush等,然后execve(/sbin/init)(生成用户态的init进程pid=1,因为没有调用fork(),所以pid还是1!), 成为系统中的其他所有进程的祖先。回过头来看pid=0的进程,在创建了init进程后(内核态的),pid=0的进程调用 cpu_idle()在主cpu中演变成了idle进程。而内核态pid=1的init进程同样会在各个从cpu上生成idel进程。 init在演变成/sbin/init之前,会执行一部分初始化工作,其中一个就是smp_prepare_cpus(),初始化SMP处理器,在这过程中会在处理每个从处理器时调用

task =copy_process(CLONE_VM, 0, idle_regs(&regs), 0, NULL, NULL, 0); init_idle(task, cpu);

即从init中复制出一个进程,并把它初始化为idle进程(pid仍然为0)。从处理器上的idle进程会进行一Activate工作,然后执行cpu_idle()。

执行/sbin/init,这样从内核太过度到用户态,按照配置文件/etc/inittab 要求完成启动的工作,并且创建若干个不编号为1,2,3…号的终端注册进程getty,其作用就是设置其进程组的标识号,监视配置到系统终端的接口电路,当有信号来到的时候,getty会执行execve()生成注册进程login,用户可以注册登录了,如果登录成功,则login会演化为shell进程,若login不成功则关闭打开的终端线路,用户1号进程会创建新的getty。到这里init的流程基本完成了。奉上大图一张!

init的过程:

init 的流程讲完了,这里粗略说说init还做了什么事。先来认识下运行级别。

运行级别: linux可以在不同的场合启动不同的开机启动程序,这就叫做运行级别。根据不同的运行级别启动不同的程序。例如在用作服务器的时候要开启Apache,,而桌面就不需要。

活在当下,别在怀念过去或者憧憬未来中浪费掉你现在的生活。

Linux 的初始化与启动过程

相关文章:

你感兴趣的文章:

标签云: