电脑(现代计算机)的整个启动过程可以概括为:
为便于理解启动过程,有必要先了解一些相关的名词,如主板固件类型(第一节),硬盘分区方式(第二节),然后在前两节的基础上简单了解操作系统是如何被引导启动的,以及多系统引导的管理(该节尚未完成)。
目录
一、主板固件(BIOS与UEFI)
1.什么是BIOS
1.1 BIOS产生:
1.2 BIOS功能:
1.3 BIOS启动过程
2.什么是(U)EFI
2.1 产生:
2.2 (U)EFI启动过程
3.启用UEFI 的条件
二、硬盘分区结构(MBR与GPT)
1.什么是MBR:
1.1 含义
1.2 结构
1.3 关于MBR分区结构的小疑问
2.什么是GPT
2.1 含义
2.2 结构
2.3 关于GPT分区结构的小疑问
三、操作系统的引导过程
1.传统BIOS引导操作系统
2.UEFI启动操作系统
3.总结
四、多系统引导的管理(未完成)
五、linux init系统介绍
1、Sysvinit介绍
运行级别
Sysvinit执行顺序
Sysvinit优缺点
2、Upstart介绍
开发UpStart的缘由
UpStart的原理
3、Systemd介绍
Systemd的单元概念
Systemd的Target和运行级别
使用C/C++开发新的系统服务
Unit文件的编写
[Unit]参数
[install]参数
[service]参数
Systemd命令行工具的使用
Systemd命令和sysvinit命令的对照表
systemd电源管理命令
可以把UEFI 和 BIOS都理解成一种固件(即Firmware,固化在特定芯片里的程序,是硬件设备最底层的软件),其主要功能是为计算机提供最底层的、最直接的硬件设置和控制(当然包括启动操作系统)。而UEFI是传统BIOS升级替代品,功能更强,并兼容传统BIOS。
由于计算机启动是一个很矛盾的过程,即必须先运行程序,然后计算机才能启动,但是计算机不启动就无法运行程序!因此需要想法把一小段程序装进内存,然后计算机才能正常运行,这段程序称为BIOS(基本输入输出系统),保存在计算机主板上的一个芯片里(称为BIOS ROM),如下图。
BIOS芯片
通常BIOS会被认为是随着IBM PC(大名鼎鼎的商用电脑)的出现而产生的,实际上BIOS要早于IBM PC,早在1975年,BIOS的概念就在CP/M系统上出现。后来随着计算机硬件的发展,除了上面提到的主板BIOS(也叫系统BIOS)以外,其他硬件也有了BIOS程序(固件),如显卡BIOS,硬盘BIOS等,但我们一般提到的BIOS固件专门指主板ROM芯片里的BIOS程序(即系统BIOS)。
BIOS固定地加载存储器最前面扇区(LBA0)里的引导代码(即BootLoader),然后由BootLoader启动操作系统。
由于BIOS各种缺点(不支持2.2TB硬盘作为启动盘,安全与扩展性差等),英特尔公司从2000年开始,发明了可扩展固件接口(Extensible Firmware Interface),用以规范BIOS的开发。而支持EFI规范的BIOS也被称为EFI BIOS。之后为了推广EFI,业界多家著名公司共同成立了统一可扩展固件接口论坛(UEFI Forum),英特尔公司将EFI 1.1规范贡献给业界,用以制订新的国际标准UEFI规范。
UEFI芯片
UEFI是一种规范,不同厂商根据该规范对UEFI的实现并烧录到芯片里,该实现程序就称为UEFI固件(固件的英文为Firmware,可理解为固化在特定芯片里的软件程序,是硬件设备最底层的软件)。
从上面的启用条件发现,虽然要求支持UEFI的固件必须支持读取GPT磁盘类型中的FAT(12/16/32)文件系统格式,并可读取该FAT中的文件和执行*.efi的可执行文件。但是UEFI 规范并没有提到固件不能识别其他文件系统类型(如苹果Mac的HFS+)并从中加载启动装载程序(苹果Mac在这方面有与众不同的个性)。
目前硬盘有MBR与GPT两种分区方式。
MBR磁盘分区是一种使用广泛的分区结构,它也被称为DOS分区结构(什么?感觉它与微软有关?是的),但它并不仅仅应用于Windows系统平台,也应用于Linux,基于X86的UNIX等系统平台。最早在1983年在IBM PC DOS 2.0中提出(微软最早与IBM合作的操作系统,但更早期的Unix系统没有这个概念,更多需要阅读操作系统发展简史)。它是存在于驱动器开始部分的一个特殊的启动扇区,磁盘的第一个扇区。这个扇区包含了已安装的操作系统的BootLoader和驱动器的分区信息。
注:'MBR'与'MBR分区结构'的区别:前者(狭义)专指主引导记录(磁盘第一扇区),后者(广义)是整个硬盘的组成结构(包括主引导记录扇区与其他扇区)。
广义的MBR包含整个扇区(引导程序、分区表及分隔标识,即下图红色部分),狭义的MBR仅指引导程序(bootloader),这里统一按广义MBR的定义进行讨论。
MBR分区结构
①主引导代码(BootLoader):占446字节,由操作系统或特殊工具写入。
②硬盘分区表DPT:占64字节,用16字节表示一个分区信息(其中),因此只能容纳4个分区。后来随着磁盘空间越来越大,又发明了扩展分区和逻辑分区(为区分把原来的普通分区改称为主分区了)。
③结束标志:占MBR扇区最后2个字节,一直为"55 AA"(也称幻数Magic Number),表示该磁盘可启动。
注:引导代码(也叫魔术代码、神奇代码)种类较多这里不展开讨论,在第三节'操作系统的启动'会再次提起bootloader。
GUID分区表简称GPT,使用GUID分区表的磁盘称为GPT磁盘,是源自EFI标准的一种较新的磁盘分区表结构的标准。相对于MBR更为先进,因为GPT分区表头可自定义分区数量(而MBR最大4个)并且支持2TB以上的磁盘空间,在Windows系统中微软限定GPT最大128个分区。GPT分区结构只能被支持UEFI的计算机识别(BIOS无法识别)。
GPT分区结构
注:扇区与LBA区别,在GPT分区中,每一个数据读写单元成为LBA(逻辑块地址),一个“逻辑块”相当于传统MBR分区中的一个“扇区”,之所以会有区别,是因为GPT除了要支持传统硬盘,还需要支持以NAND FLASH为材料的SSD硬盘(不像磁盘那样有磁片,而磁片又划分磁道和扇区来保存数据,因此闪存材料需要采用模拟扇区来保持统一性),这些硬盘的一个读写单元是2KB或4KB,所以GPT分区中干脆用LBA来表示一个基础读写块,当GPT分区用在传统硬盘上时,通常,LBA就等于扇区号,有些物理硬盘支持2KB或4KB对齐,此时LBA所表示的一个逻辑块就是2KB的空间。 为了方便,我们后面仍然将逻辑块称为扇区。
支持UEFI的操作系统:Linux(2000年开始)。Apple从 Mac OS X 10.4(代号Tiger)开始支持Intel版的EFI。微软的64位版Windows基本都支持UEFI。
Windows对UEFI的支持
简单的概括传统BIOS与UEFI主板引导操作系统过程如下:
BIOS:设备加电 -> BIOS初始化/自检 -> boot loader引导(扇区代码) -> OS操作系统。
UEFI:设备加电 -> UEFI平台 -> efi应用(硬盘文件) -> OS操作系统。
理论上传统BIOS仅支持MBR硬盘启动(在操作系统支持的情况下,GPT类型的磁盘仅可做数据盘)。
具体启动过程:
MBR启动操作系统
传统的 BIOS比较低级,只读取指定磁盘最前面第一个扇区(MBR)里的代码,MBR里面的引导代码(BootLoader)如何启动操作系统BIOS是一无所知的。微软的BootLoader只是一条跳转语句,跳转到标记为活动分区的PBR(分区引导记录,类似于主引导记录,位于分区最前面的扇区)执行。由于BIOS并没有具体规定用户的第一个分区(我们平时说的C盘)从哪个位置(相对于MBR)开始,因此可以利用MBR后面的扇区空间保存更多的引导代码(实现更多的功能),比如GRUB4DOS占用16个扇区,因此改变BootLoader代码就可以实现多系统的引导,这样的软件也比较多,这里不一一列举了。也有人把MBR里的引导代码称为引导的第一阶段,其他扇区的引导代码称为第二阶段(实现更多功能,比如识别文件系统等)
EFI基本启动流程(如下图):首先EFI初始化,EFI平台加载,设备驱动与EFI应用程序加载,boot manager启动,OS loader启动,OS启动。
UEFI启动流程
但由于UEFI规范只有要求标准,并没有具体的呈现方式,因此每家主板以及操作系统的引导过程也略有不同,这里以Windows系统为例。
即UEFI引导管理器加载FAT分区里的efi文件来启动操作系统。
在 EFI 系统启动后,GUID 分区表(GPT)就会被识别,之后 EFI 系统读取全局NVRAM 变量,启动 Boot Loader 程序(根据启动顺序加载efi可执行文件),efi会加载操作系统内核。对于分区表格式为 MBR 分区表 的磁盘,EFI 系统会 先启动 CSM 兼容模式后按传统 BIOS 的步骤加载操作系统的内核。
UEFI启动操作系统(Windows)
在UEFI安装完操作系统后,Windows至少使用两个分区,一个叫做ESP分区(EFI SYSTEM PARTITION),用于存放启动文件,另一个则是BIOS下正常的系统分区,不同的是,BIOS下引导文件是winload.exe,UEFI下引导文件式winload.efi,两者都是pecoff格式的,但UEFI用的是各种固件接口,而BIOS使用的是中断。有时还会有一个MSR分区,不过这个分区并不重要,实验可以删除。
默认情况下,UEFI固件加载的启动文件路径是EFI\BOOT\bootx64.efi(bootia32.efi),而Windows会强制写入的启动项则会加载EFI\MICROSOFT\BOOT\bootmgfw.efi,虽然这两个文件其实是一模一样的文件,但这样在BDS阶段,固件就会自动引导Windows启动管理器,从而在多系统的情况下Windows能优先启动,Linux系统引导文件一般是grubx64.efi。
UEFI之所以比BIOS强大,是因为UEFI本身已经相当于一个微型操作系统,其带来的便利之处在于:
而这些都是BIOS做不到的,因为BIOS下启动操作系统之前,必须从硬盘上指定扇区读取系统启动代码(包含在主引导记录中),然后从活动分区(只限微软)中引导启动操作系统。对扇区的操作远比不上对分区中文件的操作更直观更简单,所以在BIOS下引导安装Windows操作系统,我们不得不使用一些工具对设备进行配置以达到启动要求。而在UEFI下,这些统统都不需要,不再需要主引导记录,不再需要活动分区,不需要任何工具,只要复制安装文件到一个FAT32(主)分区/U盘中,然后从这个分区/U盘启动,安装Windows就是这么简单。
BIOS和UEFI的启动过程区别(本文已在前面用红色字体提过,总结如下):
Linux操作系统的启动首先从BIOS开始,接下来进入bootloader,由bootloader载入内核,进行内核初始化。内核初始化的最后一步就是启动pid
为1
的init进程
。init以守护进程方式存在,是系统的第一个进程,,是所有其他进程的祖先。
Init系统能够定义、管理和控制 init进程的行为。它负责组织和运行许多独立的或相关的始化工作(因此被称为init系统),从而让计算机系统进入某种用户预订的运行模式。
Linux初始化init系统包括:Sysvinit
、Upstart
和Systemd
,它们在Ubuntu系统下的演化如下:
Ubuntu 6.10
及以前版本使用Sysvinit。Ubuntu 14.10
及以前版本使用Upstart,通过与Sysvinit并存。Ubuntu 15.04
开始默认使用Systemd,不能与Sysvinit或Upstart并存Sysvinit就是System V风格的init系统,顾名思义,它源于System V系列UNIX。
Sysvinit用术语runlevel
来定义"预订的运行模式",默认的运行模式定义在/etc/inittab
文件的initdefault
项。如果没有默认的运行模式,那么用户将进入系统控制台,手动决定进入何种运行模式。
Sysvinit中运行模式描述了系统各种预订的运行模式。通常会有8种运行模式,即运行模式0-6和S
。其中0
表示关机,1
表示单用户模式,3
为命令行模式,5
为GUI模式,6
表示重启,1和S
等往往用于系统故障之后的排错和恢复。可以看出每一种运行模式所作的初始化工作是不一样的。
首先,运行rc.sysinit
以便执行一些重要的系统初始化任务。
然后,Sysvinit开始运行/etc/rc.d/rc
脚本。根据不同的runlevel,rc脚本将执行/etc/rc.d/rcX.d
(X就是runlevel)目录下的所有启动脚本。当所有的初始化脚本执行完毕。该目录下有多个脚本,为了保证系统正常关闭,脚本是要按照顺序执行的在该目录下所有以K
开头的脚本都将在关闭系统时调用,字母K
之后的数字定义了它们的执行顺序。
然后,Sysvinit运行/etc/rc.d/rc.local
脚本。rc.local是Linux 留给用户进行个性化设置的地方。
Sysvinit的优点:
Sysvinit的缺点:
当Linux内核进入2.6时代时,系统支持热插拔功能,一旦新外设连接到系统,内核便可以自动实时地发现它们,并初始化这些设备,进而使用它们。这为便携式设备用户提供了很大的灵活性。
Sysvinit启动时必须一次性把所有可能用到的服务都启动起来,即使该设备没有连接,因此会造成浪费,比如为了管理打印任务,系统需要启动CUPS等服务。
UpStart基于事件机制,比如U盘插入USB接口后,udev得到内核通知,发现该设备,这就是一个新的事件。UpStart在感知到该事件之后触发相应的等待任务,比如处理/etc/fstab 中存在的挂载点。采用这种事件驱动的模式,upstart 完美地解决了即插即用设备带来的新问题。
UpStart相对于Sysvinit具有如下的优势:
Upstart的基本概念和设计清晰明确。UpStart主要的概念是job和event。Job就是一个工作单元,用来完成一件工作,比如启动一个后台服务,或者运行一个配置命令。每个Job都等待一个或多个事件,一旦事件发生,upstart就触发该 job 完成相应的工作。
Job包括包括TaskJob
,SeriveJob
和AbstractJob
。其中SeriveJob代表后台服务进程,一旦开始运行就成为一个后台进程,由init进程管理。
事件是个非常抽象的概念,下面我罗列出一些常见的事件,希望可以帮助您进一步了解事件的含义:
系统初始化的过程是在工作和事件的相互协作下完成的,可以大致描述如下:
系统初始化时,init 进程开始运行,init 进程自身会发出不同的事件,这些最初的事件会触发一些工作运行。每个工作运行过程中会释放不同的事件,这些事件又将触发新的工作运行。如此反复,直到整个系统正常运行起来。
UpStart是兼容SysvInit的runlevel的,通过触发执行/etc/init/rc.conf
来执行/etc/rc$.d/
目录下的所有脚本。
Systemd提供了和Sysvinit以及LSBinitscripts兼容的特性。系统中已经存在的服务和进程无需修改。这降低了系统向systemd 迁移的成本,使得Systemd替换现有初始化系统成为可能。
Systemd的启动速度更快,提供了比UpStart更激进的并行启动能力,采用了socket/D-Bus Activation等技术启动服务,提供按需启动的能力,只有在某个服务被真正请求的时候才启动它,当该服务结束,systemd 可以关闭它,等待下次需要时再次启动它。
Systemd还提供如下等特性:
系统初始化需要执行的任务非常多。每一个任务都被Systemd 抽象为一个配置单元,即unit。当前单元类型如下:
systemd使用目标(target)替代了运行级别的概念,提供了更大的灵活性,如您可以继承一个已有的目标,并添加其它服务,来创建自己的目标。通过target文件夹的命令也可以看出对应的runlevel:
Sysvinit运行级别 | Systemd目标 | 备注 |
---|---|---|
0 | poweroff.target | 关闭系统 |
1,s | rescue.target | 单用户模式 |
2,4 | multi-user.target | 多用户,非图形化 |
3 | multi-user.target | 多用户,非图形化 |
5 | graphical.target | 多用户,图形化 |
6 | reboot.target | 重启 |
使用C/C++开发新的系统服务可能需要关注如下的内容:
服务配置单元文件以.service
为文件名后缀,默认时存放在/lib/systemd/system/
目录下,然后链接到/etc/systemd/system/
对应的目录下。下面以sshd的为例/etc/system/system/sshd.service
:
1 2 3 4 5 6 7 8 9 10 11 12 |
[Unit] Description=OpenSSH server daemon [Service] EnvironmentFile=/etc/sysconfig/sshd #设置环境变量 ExecStartPre=/usr/sbin/sshd-keygen ExecStart=/usrsbin/sshd –D $OPTIONS ExecReload=/bin/kill –HUP $MAINPID KillMode=process Restart=on-failure RestartSec=42s [Install] WantedBy=multi-user.target #系统以该形式运行时,服务方可启动 |
文件分为三个小节,其中[Unit]
段和[Install]
段是所有Unit文件通用的,用于配置服务的描述、依赖和随系统启动方式,而[Service]
断则是服务类型的Unit文件(后缀为.service)特有的,用于定义服务的具体管理和操作方法。
在/etc/systemd/system 目录下还可以看到诸如*.wants 的目录,放在该目录下的配置单元文件等同于在[Unit]
小节中的 wants关键字,即本单元启动时,还需要启动这些单元。比如您可以简单地把您自己写的 foo.service 文件放入 multi-user.target.wants 目录下,这样每次都会被默认启动了。
上面的这些配置,除了Description外,其他都可以被添加多次。比如After参数,可以使用多个After参数,也可以在一行内使用空格分割,写多个依赖模块。
服务生命周期控制相关的参数
服务上下文配置相关的参数
systemd 的主要命令行工具是systemctl
,可以替换service
、chkconfig
以及telinit
命令的使用。
Sysvinit命令 | Systemd命令 | 备注 |
---|---|---|
service foo start | systemctl start foo.service | 用来启动一个服务 (并不会重启现有的) |
service foo stop | systemctl stop foo.service | 用来停止一个服务 (并不会重启现有的) |
service foo restart | systemctl restart foo.service | 用来停止并启动一个服务 |
service foo reload | systemctl reload foo.service | 当支持时,重新装载配置文件而不中断等待操作 |
service foo condrestart | systemctl condrestart foo.service | 如果服务正在运行那么重启它 |
service foo status | systemctl status foo.service | 汇报服务是否正在运行 |
ls /etc/rc.d/init.d/ | systemctl list-unit-files --type=service | 用来列出可以启动或停止的服务列表 |
chkconfig foo on | systemctl enable foo.service | 在下次启动时或满足其他触发条件时设置服务为启用 |
chkconfig foo off | systemctl disable foo.service | 在下次启动时或满足其他触发条件时设置服务为禁用 |
chkconfig foo | systemctl is-enabled foo.service | 用来检查一个服务在当前环境下被配置为启用还是禁用 |
chkconfig –list | systemctl list-unit-files --type=service | 输出在各个运行级别下服务的启用和禁用情况 |
chkconfig foo –list | ls /etc/systemd/system/*.wants/foo.service | 用来列出该服务在哪些运行级别下启用和禁用 |
chkconfig foo –add | systemctl daemon-reload | 当您创建新服务文件或者变更设置时使用 |
telinit 3 | systemctl isolate multi-user.target (OR systemctl isolate runlevel3.target OR telinit 3) | 改变至多用户运行级别 |
命令 | 操作 |
---|---|
systemctl reboot | 重启机器 |
systemctl poweroff | 关机 |
systemctl suspend | 待机 |
systemctl hibernate | 休眠 |
systemctl hybrid-sleep | 混合休眠模式(同时休眠到硬盘并待机) |