计算机及Linux操作系统开机启动过程详解

从按下开机键开始的计算机启动过程:

(主要包括 从主板加载BIOS程序并执行、从磁盘加载启动区程序并执行、从磁盘加载操作系统程序并执行 三步,是依次递进的,详情参阅 全网最硬核详解计算机启动过程-公众号低并发编程

加载BIOS程序:按下开机键,主板ROM的BIOS被(被谁?)加载到到内存 0xffff0 处,CPU 将 PC 寄存器的值强制初始化为 0xffff0(一跳)。

执行BIOS程序:(该代码主要完成 硬件检测及初始化、中断向量表建设、启动区加载)

阶段1(0xffff0 处的内容):该入口地址处存的是一个跳转指令,跳转的目的地是内存 0xfe05b 位置,该位置存了BIOS的真正内容。执行该跳转(二跳)。

阶段2(0xfe05b 处的内容):执行硬件检测、硬件初始化、建立中断向量表等工作后,找到磁盘上的启动区(或称引导区,Master Boot Record,MBR)加载到内存 0x7c00 位置,并跳转到该位置(三跳)。

执行启动区程序(0x07c00 处的内容):从磁盘加载OS内核到内存,与上面不同这里内存位置不是固定的了,并跳转到OS内核代码处(四跳)。

执行OS内核程序:包括开启分段机制、进入保护模式、开启中断机制等,执行完后系统由OS接手管理。具体过程见下文“操作系统启动过程”部分。

整体过程概要:

补充:

BIOS程序位于主板ROM(Read-Only Memory),启动时被加载到内存;启动区、OS位于磁盘,被先后加载到内存。BIOS、启动区在内存的位置是固定的(为啥是这三个值?早期定死的);而OS在内存位置不是固定的。BIOS程序、启动区程序加载到内存的位置是固定的(0xffff0、0x07c00)而OS的则不定;四跳包括加载BIOS、启动区、OS的三跳及BIOS执行时的一个跳转指令。

启动区(Master Boot Record,MBR):若一个磁盘上0盘0道1扇区的内容(512B)的末两个字节为0x55、0xaa,则这该扇区会被BIOS识别为启动区,该磁盘会被当做可启动盘。往一个磁盘烧录OS后之所以可以当做启动盘就是因为往该位置写入了这些特殊数据。若装了多系统,则启动时会列出并让用户选择要启动的系统,这些系统就是根据上述条件被识别得到。

启动区结构(详见这篇文章):512B包含 加载OS的代码(446B)、该磁盘上的分区表(64B)、启动区标记0x55和0xaa(2B)。为何磁盘最多只能有4个主分区?为何每个分区甚至每个硬盘最多管理2TB数据(因用4个字节表示该分区的扇区数,最大大小为 2^(4*8) * 512B=2TB)?原因都在于分区表的结构上。

往磁盘烧录一个OS时内部也必须会先烧录个MBR,故可认为MBR是OS的一部分。

启动区是BIOS启动方式中的概念,还有一种启动方式是UEFI,后者不用MBR。

可见,一个程序只要其虚拟内存以0x7c00作为段地址,且按上述条件烧录到磁盘,则就可以被BIOS识别为启动区加载到内存执行。因此,如果该程序逻辑中不是去加载OS而是直接输出数据,则该程序自身就是一个简洁的"操作系统"。示例(更多详情可参阅 自制操作简易系统-公众号低并发编程):

;将此段程序烧录成硬盘启动区即可作为一个“操作系统“

;BIOS把启动区加载到内存的该位置
;所以需设置地址偏移量
section mbr vstart=0x7c00

;直接往显存中写数据
mov ax,0xb800 ;这条就是第一条指令
mov gs,ax
mov byte [gs:0x00],'h'
mov byte [gs:0x02],'e'
mov byte [gs:0x04],'l'
mov byte [gs:0x06],'l'
mov byte [gs:0x08],'o'
mov byte [gs:0x0a],' '
mov byte [gs:0x0c],'w'
mov byte [gs:0x0e],'o'
mov byte [gs:0x10],'r'
mov byte [gs:0x12],'l'
mov byte [gs:0x14],'d'

jmp $

;512字节的最后两字节是启动区标识
times 510-($-$$) db 0
db 0x55,0xaa
最简洁的“操作系统”

在OS内核加载到内存前,系统会认为内存只有1MB的大小(即实模式内存),且基于1MB内存进行用途分配(这个分配成为了一个事实的标准),示例图如下:

为何是1M?因为最早的Intel 8086寻址空间是1MB(其是16位机但支持20位寻址),后面的80286等32位、64位机虽支持更大的寻址空间,但仍是向旧兼容的,即启动时仍只有1M的寻址空间且内存用途分配与上面一样(例如32位机下是把引导区加载到内存0xff_ff_ff_f0处)、之后才能访问更多的空间,为加以区别就分别以实模式、保护模式命名之

关于实模式与保护模式可参阅这篇 文章。顾名思义,就是实际内存、受保护的内存,前者直接操作物理内存、后者是虚拟内存(先后经历段内存、页内存的发展过程)。

这里我们用到了1MB内存中BIOS区域、启动区区域,可以看出,还有黑白、彩色等图形视频区域,要往屏幕输出信息时只需往这片区域存数据,显卡会将这片内存的数据展示到屏幕上(具体如何做到的?涉及到硬件、硬件驱动),上面的简洁“操作系统”的输出正是通过往黑白区域内存写数据实现的。

 

 

  

 

下面是操作系统启动的过程详解

===========================================

Linux开机执行内核后会启动init进程,该进程根据runlevel(如x)执行/etc/rcx.d/下的程序,其下的程序是符号链接,真正的程序放在/etc/init.d/下。开机启动的程序(服务等)皆为此套路。

init是第一个用户进程。最主要的功能就是准备软件执行的环境,包括系统的主机名,网络设定,语系设置,系统文件格式及其他服务的启动。

 

Linux开机启动程序详解

我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤。

加载内核
LILO启动之后,如果你选择了Linux作为准备引导的操作系统,第一个被加载的东西就是内核。请记住此时的计算机内存中还不存在任何操作系统,PC(因为它们天然的设计缺陷)也还没有办法存取机器上全部的内存。因此,内核就必须完整地加载到可用RAM的第一个兆字节之内。为了实现这个目的,内核是被压缩了的。这个文件的头部包含着必要的代码,先设置CPU进入安全模式(以此解除内存限制),再对内核的剩余部分进行解压缩。

执行内核
内核在内存中解压缩之后,就可以开始运行了。此时的内核只知道它本身内建的各种功能,也就是说被编译为模块的内核部分还不能使用。最基本的是,内核必须有足够的代码设置自己的虚拟内存子系统和根文件系统(通常就是ext2文件系统)。一旦内核启动运行,对硬件的检测就会决定需要对哪些设备驱动程序进行初始化。从这里开始,内核就能够挂装根文件系统(这个过程类似于Windows识别并存取C盘的过程)。内核挂装了根文件系统之后,将启动并运行一个叫做init的程序。

init进程
init进程是非内核进程中第一个被启动运行的,因此它的进程编号PID的值总是1。init读它的配置文件/etc/inittab,决定需要启动的运行级别(Runlevel)。从根本上说,运行级别规定了整个系统的行为,每个级别(分别由0到6的整数表示)满足特定的目的。如果定义了initdefault级别,这个值就直接被选中,否则需要由用户输入一个代表运行级别的数值。

关于Linux init系统发展过程(sysvinit -> upstart -> systemd),可参阅:https://www.cnblogs.com/sparkdev/p/8448237.html

 

ReadHat系(如CentOS)的runlevel定义如下: 
  # 0 - 停机(千万别把initdefault设置为0,否则系统永远无法启动)
  # 1 - 单用户模式
  # 2 - 多用户,没有 NFS
  # 3 - 完全多用户模式(标准的运行级)
  # 4 – 系统保留的
  # 5 - X11 (x window)
  # 6 - 重新启动 (千万不要把initdefault 设置为6,否则将一直在重启 )

输入代表运行级别的数字之后,init根据/etc/inittab文件中的定义执行一个命令脚本程序。缺省的运行级别取决于安装阶段对登录程序的选择:是使用基于文本的,还是使用基于X-Window的登录程序。
rc命令脚本程序我们已经知道,当运行级别发生改变时,将由/etc/inittab文件定义需要运行哪一个命令脚本程序。这些命令脚本程序负责启动或者停止该运行级别特定的各种服务。由于需要管理的服务数量很多,因此需要使用rc命令脚本程序。其中,最主要的一个是/etc/rc.d/rc,它负责为每一个运行级别按照正确的顺序调用相应的命令脚本程序。我们可以想象,这样一个命令脚本程序很容易变得难以控制!为了防止这类事件的发生,需要使用精心设计的方案。

对每一个运行级别来说,在/etc/rc.d子目录中都有一个对应的下级目录。这些运行级别的下级子目录的命名方法是rcX.d(还有一个rcS.d目录,其下的脚本也为指向/etc/init.d中的脚本的链接,但是会在/etc/rc.d/rc[0~6].d中的脚本执行前首先被执行),其中的X就是代表运行级别的数字。比如说,运行级别3的全部命令脚本程序都保存在/etc/rc.d/rc3.d子目录中。在各个运行级别的子目录中,都建立有到/etc/rc.d/init.d子目录中命令脚本程序的符号链接,但是,这些符号链接并不使用命令脚本程序在/etc/rc.d/init.d子目录中原来的名字。如果命令脚本程序是用来启动一个服务的,其符号链接的名字就以字母S打头;如果命令脚本程序是用来关闭一个服务的,其符号链接的名字就以字母K打头。
许多情况下,这些命令脚本程序的执行顺序都很重要。如果没有先配置网络接口,就没有办法使用DNS服务解析主机名!为了安排它们的执行顺序,在字母S或者K的后面紧跟着一个两位数字,数值小的在数值大的前面执行。比如:/etc/rc.d/rc3.d/S50inet就会在/etc/rc.d/rc3.d/S55named之前执行(S50inet配置网络设置,55named启动DNS服务器)。存放在/etc/rc.d/init.d子目录中的、被符号链接上的命令脚本程序是真正的实干家,是它们完成了启动或者停止各种服务的操作过程。当/etc/rc.d/rc运行通过每个特定的运行级别子目录的时候,它会根据数字的顺序依次调用各个命令脚本程序执行。它先运行以字母K打头的命令脚本程序,然后再运行以字母S打头的命令脚本程序。对以字母K打头的命令脚本程序来说,会传递Stop参数;类似地对以字母S打头的命令脚本程序来说,会传递Start参数。编写自己的rc命令脚本在维护Linux系统运转的日子里,肯定会遇到需要系统管理员对开机或者关机命令脚
本进行修改的情况。


从以上也可看出,事实上不同的运行级别 只是加载的服务不同。
有两种方法可以用来实现修改的目的:

●如果所做的修改只在引导开机的时候起作用,并且改动不大的话,可以考虑简单地编辑一下/etc/rc.d/rc.local脚本。这个命令脚本程序是在引导过程的最后一步被执行的。

●如果所做的修改比较细致,或者还要求关闭进程使之明确地停止运行,则需要在/etc/rc.d/init.d子目录中添加一个命令脚本程序。这个命令脚本程序必须可以接受Start和Stop参数并完成相应的操作。

第一种方法,编辑/etc/rc.d/rc.local脚本,当然是两种方法中比较简单的。如果想在这个命令脚本程序中添加内容,只需要使用喜欢的编辑器程序打开它,再把打算执行的命令附加到文件的末尾就可以了。这对一两行的修改来说的确很便利。

如果确实需要使用一个命令脚本程序,这时必须选择第二个方法。编写一个rc命令脚本程序的过程并不像想象中那么困难。我们下面就给出一个例子,看看它是怎样实现的(顺便说一句,你可以把我们的例子当作范本,按照自己的需要进行修改和添加)。
假设你打算每隔60分钟调用一个特殊的程序来弹出一条消息,提醒自己需要从键盘前面离开休息一会儿,命令脚本程序将包括下面几个部分:
●关于这个命令脚本程序功能的说明(这样就不会在一年之后忘记它);

●在试图运行它之前验证这个命令脚本程序确实存在;

●接受start和stop参数并执行要求的动作。

参数给定后,我们就可以编写命令的脚本程序。这个程序很简单,大家可以自己编写一下,我在这里就不给出了。
编写好新的命令脚本程序之后,再从相关的运行级别子目录中加上必要的符号链接,来控制这个命令脚本程序的启动或者停止。在我的印象中,只想让它在运行级别3或者运行级别5中启动,原因是我认为只有这两个运行级别才是日常工作的地方。最后,希望这个命令脚本程序在进入运行级别6(重启动)的时候被关闭。

激活或者禁止服务项目有的时候会发现,在引导的时候并不需要某个特定的服务被启动。如果你正在考虑使用Linux替换Windows NT的文件和打印服务器,就更是如此。我们已经知道,在特定的运行级别子目录中给符号链接改个名称,就可以让该服务不被启动,如把其名称的第一个字母由S改为K。一旦熟练掌握了命令行和符号链接,就会发现这是激活或者禁止服务的最快办法。

在学习这个改名方法的时候,可能会觉得图形化的操作界面ksysv比较容易掌握。虽然它原来是设计使用在KDE环境里的,但在Red HatLinux 7.2下缺省安装的GNOME环境里也运行得很好。如果想启动它,只需简单地打开一个xterm窗口,并输入ksysv命令就可以了。屏幕上会出现一个窗口,其中列出了能够修改的全部参数,需要时还包括在线帮助。警告:如果是在一个现实中的系统上学习本文的知识,要多多运用常识。当试着对启动脚本程序进行修改的时候,要记住所做的修改可能会造成你的系统不能正常工作,而且无法采用重启动的方法恢复。不要在正常运转的系统上实验新的设置,对你准备修改的文件要全部进行备份。最重要的是,在手边要准备一张引导盘以防不测。

 

注意

 
debian系(如Ubuntu)的runlevel级别定义如下:
0 – Halt,关机模式
1 – Single,单用户模式
2 - Full multi-user with display manager (GUI)
3 - Full multi-user with display manager (GUI)
4 - Full multi-user with display manager (GUI)
5 - Full multi-user with display manager (GUI)
6 – Reboot,重启
可以发现2~5级是没有任何区别的。他们为多用户模式,这和一般的linux不一样。
我们可以对比下rc[?].d(debian系的rc[?].d在/etc/而非/etc/rc.d/目录下,后者不存在)下的内容验证上面的说法:

 

ubuntu与传统的linux略有不同,它使用upstart完成系统的启动(但表面上仍维持init程序的形式),系统启动后处于哪一种级别传统Linux由init读取/etc/inittab文件中的缺省级别设置来确定,一般图形界面的系统是进入级别3。

但是ubuntu与传统的不太一样,默认情况下是找不到/etc/inittab文件的,而且运行级别也有差别。同时系统的默认级别设定也不是在inittab文件中,而是写在/etc/init/rc- sysinit.conf文件中。
打开此文件,可以找到下面一句:
 
env DEFAULT_RUNLEVEL=2
 
这表明系统当前默认是进入级别2。
 
另外,在此文见中还有一段以if [ -r /etc/inittab ] 开始的代码,这里保留了使用inittab指定系统默认运行级别的功能,也就是说,如果用户手动创建了/etc/inittab,那么init将以 /etc/inittab中指定的默认运行级别进行系统的启动。比如说用户希望系统以级别3为默认运行级别,则只需在inittab文件中加入如下一行:
 
id:3:initdefault:
 
在经过/etc/init/rc-sysinit.conf确定运行级别后,init将进一步运行/etc/init.d/rc,然后根据级别进入 /etc/rc[?].d启动或关闭相应的服务。
 
 
posted @ 2016-07-10 16:13  March On  阅读(18282)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)