Linux内核学习笔记——系统启动


千里之行,始于足下!系统启动往往被认为是正式学习Linux内核的开始,要使用一个系统,无论Windows还是Linux,首先要做的就是将它启动。这里我们就一起学习一下当用户打开计算机电源之后所发生的事。也就是说,我们要研究Linux内核映像是如何被拷贝到内存中的,又是如何被执行的。在操作系统中,启动指把一部分操作系统装载到主存中并让处理器执行它,也表示内核数据结构的初始化、一些用户进程的创建以及把控制权转移到其中某个进程。

相关阅读:Linux内核学习笔记——预备知识

计算机启动是一个冗长乏味的任务,因为在最开始时每个硬件设备(包括RAM)都处于一种随机、不可预知的状态。计算机在最初加点的那一刻是毫无用处的,你可以把它当做一堆废铜烂铁的组合,因为此时RAM芯片中包含的是随机数据,此时还没有操作系统运行在上面。顺便提一下,RAM是随机存取存储器,内容可随意存取,且存取速度与存储单元位置无关,断电时丢失存储内容。

在开始启动时,有一个特殊的硬件电路在CPU的一个引脚上产生一个RESET逻辑值。RESET产生以后,就把处理器的一些寄存器(包括cs和eip寄存器)设置为固定的值,并执行在物理地址0xfffffff0处找到的代码。硬件把这个地址映射到一个只读、持久的存储芯片中,即ROM。ROM中存放的程序集在80x86体系中通常叫作基本输入/输出系统,也就是我们熟悉的、大名鼎鼎的BIOS。所有操作系统在启动时,都要通过这些过程对计算机硬件设备进行初始化。Linux在启动阶段必须使用BIOS,此时linux必须要从磁盘或其他外部设备中获取内核映像。

BIOS启动过程实际执行4个操作:

1)对计算机硬件执行一系列测试,用来检测现在都有什么设备以及这些设备是否正常工作,这一过程称为上电自检

2)初始化硬件设备

3)搜索一个操作系统来启动,根据BIOS的设置,这个过程可能要试图访问系统中的软盘、硬盘和CD/DVD-ROM的第一个扇区(引导扇区),至于访问次序则可以由用户自己定义,一般在开机自检后按F2(有些厂家计算机是F8或F12,屏幕上会有提醒)会进入相应的设置界面进行设置,比如我们用光盘安装系统时,设置访问首选项为CD/DVD-ROM,则系统会先访问光驱,如果其中有系统安装盘,如Windows7或Ubuntu11.10,则执行引导扇区,进入安装界面;当然,如果光驱中没有光盘,则系统按顺序继续依次访问软盘、硬盘等,当系统访问到硬盘时就会正常启动已安装的系统

4)只要找到一个有效的设备,就把第一个扇区的内容拷贝到RAM中从物理地址0x00007c00开始的位置,然后跳转到这个地址处,开始执行刚才装载进来的代码BIOS完成上述工作之后,会调用引导装入程序(boot loader),用来把操作系统的内核映像装载到RAM中。

现在我们举例来看一下引导装入程序究竟是如何工作的:

以硬盘启动为例,对于一个硬盘来说,最多只能创建三个主分区,一个扩展分区。在扩展分区上又可以划分若干逻辑分区。对于一个常规的操作系统来说,一般只能安装在主分区中,并且安装在主分区中的操作系统远比安装在逻辑分区中的方便管理且安全得多。(注:Linux就可安装在逻辑分区中)硬盘的物理第一扇(0柱面,0面,1扇区)是硬盘主引导记录扇MBR,计算机启动时,首先就读取该扇,读出硬盘分区表,从中选择三个主分区中唯一一个具有活动标记的分区,引导该分区上的操作系统。也就是说,无论有几个主分区(≤3),其中必须有一个分区是活动的。该扇区中除了包括分区表外,还有一个小程序,这个小程序用来装载被启动的操作系统所在分区的第一个扇区。按照这种方法,只有那些内核映像存放在活动分区中的操作系统才可以被启动。Linux的处理方式避开了这种缺憾,因为Linux使用一个巧妙的引导装入程序取代这个MBR中不完善的程序。

众所周知的Linux引导装入程序(boot loader)是LILO,当然还有更为先进的GRUB。LILO或许被装在MBR上,代替那个装载活动引导扇区的小程序,或许被装载在每个磁盘分区的引导扇区上。这两种情况的结果是相同的:装入程序在启动过程中被执行,用户可以选择装入哪个操作系统(相信装过双系统的用户对于那个启动系统选择界面都不会陌生)。当然,并不是所有的电脑都能容忍修改主启动记录(MBR),这也是LILO的局限所在。

我们假定LILO引导装入程序将装载的是Linux内核映像,则LILO引导装入程序依赖BIOS例程,执行如下步骤:
1)调用一个BIOS过程显示“Loading”信息

2)调用一个BIOS过程从磁盘装入内核映像的初始部分,即将内核映像的第一个512字节从地址0x00090000开始存入RAM,将setup( )函数的代码从地址0x00090200开始存入RAM中

3)调用一个BIOS过程从磁盘中装载其余内核映像,并把内核映像放入从低地址0x00010000(适用于使用make zImage编译的小内核映像)或者从高地址0x00100000(适用于使用make bzImage编译的大内核映像)开始的RAM中。

4)跳转到setup( )代码:setup( )汇编语言函数的代码由链接程序放在内核映像文件的偏移量0x200处,因此引导装入程序可以轻松的确定setup( )代码的位置,并把它拷贝到从物理地址0x00090200开始的RAM中。

至此,接力棒就由引导装入程序传到了setup( )函数的手中。

setup( )初始化计算机的硬件设备,并为内核程序的执行建立环境,然后跳转到startup_32( )汇编语言函数继续执行


有两个所谓的startup32()函数,一个位于arch/i386/boot/compressed/head.S;另一个则是位于 arch/i386/kernel/head.S的startup32()函数。前者主要用于解压缩内核映像,setup()函数结束后,就直接跳到这个函数中;后者是内核真正的入口,此时内核已经被解压了,从这里开始,内核才开始了真正的初始化。setup()函数结束后,就会跳到0x1000(zImage)或者0x100000(bzImage),也就是startup32()函数的起点。

该函数会执行以下操作:

1.初始化寄存器和临时堆栈

2.用0填充_edata和_end符号标识的内核未初始化数据区

3.调用decompress_kernel()函数来解压缩内核映像.首先显示"Uncompressing Linux..."信息完成内核映像的解压之后,显示"OK,booting the kernel"信息.不管是zImage还是bzImage,最终解压好的内核都被放在0x100000开始的位置

4.跳转到0x100000处,开始真正的内核旅程

具体解压缩细节在此不再赘述,这也不是重点。我们所要知道的就是,前面的程序为真正的内核初始化提供必要的数据和建立了必要的运行环境,而接下来的startup32()函数和startkernel()函数,才是我们真正需要仔细了解的目标。

第二个startup_32( )函数为第一个Linux进程(进程0)建立执行环境,执行相关操作后最终跳转到start_kernel( )函数继续执行。start_kernel( )函数完成Linux内核的初始化工作,执行到最后,就会在控制台上出现熟悉的登录提示符(如果在启动时所启动的是X Window系统,那么登录提示符就会出现在一个图形窗口中,至于什么是X Window,上网去查!)

至此,启动阶段结束!

相关内容