Linux 启动过程详解

时间:2022-03-12 15:57:15

 
 

1. Linux启动过程

Linux的启动过程是在执行多级初始化过程中启动一个Linux的安装,它在许多方面类似于BSD和其他Unix风格的引导过程,从中衍生出来。

 

引导Linux安装设计多个阶段和软件组成,包括固件初始化,引导加载程序的执行,Linux内核映像的加载和启动,以及各种脚本和守护程序的执行,对于这些阶段和组件中的每一个,存在不同的变化和方法,例如:GRUBLILOSYSLINUXLoadin可用做引导加载程序,而启动脚本可以是传统的init风格,也可以通过现在的方法代替(如systemdupstart

 
 


 
 

2. 启动过程概述

Linux启动过程的早期阶段很大程度取决于计算机体系结构,IBM PC兼容硬件是Linux常用的一种架构,在这些系统上,BIOS起着很重要的作用,可能在其他系统上没有精确的类似物,在以下示例中,假设使用与IBM PC兼容的硬件:

 

  1. BIOS执行特定于实际硬件平台的启动任务,一旦枚举硬件并正确初始化引导所需的硬件,BIOS就会从配置的引导设备加载并执行引导代码。
  2. 引导加载程序通常向用户提供一个可能的引导选项菜单,并有一个默认选项,该选项在经过一段时间后被选中,一旦做出选择,引导加载程序就会将内核加载到内存中,并为其提供一些参数,并赋予其控制权。
  3. 内核如果被压缩,将自动解压缩,然后,它设置系统功能,如基本硬件和内存分页,并掉调用start_kernel(),它执行大部分系统设置(中断、内存管理和其余部分、设备和驱动程序初始化等),然后它分别启动空闲进程、调度程序和init进程,这些进程在用户空间执行。
  4. initshell(susv,bsd,runit)执行的脚本或二进制组件(systemd,upstart)执行的配置文件组成,init具有特定的级别(sysv,bsd)或目标(systemd),每个级别都由特定的服务集(守护进程)组成,它们提供何种非操作系统服务和结构,并形成用户环境,典型的服务器环境启动Web服务器、数据库服务和网络
  5. 典型的桌面环境以一和名为display manager的守护程序开始,该守护程序启动一个图形环境,该图形环境由一个提供图形堆栈的图形服务器和一个提供输入凭据和选择会话的登陆管理器组成, 会话是一组程序,例如UI元素(页面、桌面、小程序等),这些元素可以一起构成一个完整的桌面环境。

 
 


 
 

3. 引导加载阶段

引导加载程序阶段因计算机体系结构而异,由于早期阶段并非特定与操作系统,所以在实际模式下执行主引导记录(MBR)代码并加载第一阶段引导加载程序时,将考虑启动x86x86-64体系结构的基于BIOS的引导过程,在UEFI系统中,可以直接执行负载,比如Linux内核,因此,不需要引导加载程序,下面是一些流行的引导加载程序的摘要:

 

  • GRUB 1 - 包括在运行时读取公共文件系统以访问其配置文件的逻辑,这使GRUB 1能够从文件系统读取配置文件,而不是将其嵌入到MBR中,这允许它在运行时配置并以人类可读的格式指定磁盘和分区而不是依赖于偏移量,它还包含一个命令接口,如果GRUB配置错误或损坏,它可以更容易地修复和修改GRUB
  • GRUB 2 - 不同于GRUB 1,它有两个(可选三个)阶段,能够自动检测各种操作系统和自动配置,第一阶段加载程序(stage 1)由BIOS从主引导记录(MBR)加载和执行,或者由分区引导扇区的另一个引导加载程序加载和执行,它的工作是发现和访问各种文件系统,稍后可以从这些文件系统读取配置,可选的中间阶段加载器(stage 1.5)由第一阶段加载器加载执行,以防第二阶段加载器不是连续的,或者文件系统或硬件需要特殊处理才能访问第二阶段加载器,第二阶段加载器(stage 2)最后加载,并显示GRUB启动菜单,允许用户选择操作系统或检查和编辑启动参数,选择菜单项并给出可选参数后,GRUB将内核加载到内存并将控制权递给它,GRUB 2还能够链式加载另一个引导加载程序

 
 


 
 

4. 内核阶段

 

Linux内核处理所有操作系统进程,例如内存管理、任务调度、I/O、进程间通信和整个系统控制,它分两个阶段加载,在第一阶段,内核(作为压缩的镜像文件)被夹在到内存中并解压缩,然后设置一些基本功能,如基本内存管理,然后最后一次将控件切换到主内核和启动进程,一旦内核完全运行并且作为启动的一部分,在加载和执行之后,内核寻找要运行的init进程,它(单独地)设置用户空间和用户环境和最终登陆所需的进程,然后,内核本身被允许空闲,这取决来自其他进程的调用。

 

4.1 内核加载阶段

内核通常以镜像文件的形式加载,用zlib压缩成zlmage或bzImage格式,它头部的一个例程执行少量的硬件设置,将镜像完全解压到内存中,并在配置时记录任何RAM磁盘,然后,它同通过 /arch/ie86/boot/head和startup_32()(用于基于x86的处理器)进程执行内核启动

 

 

4.2 内核启动阶段

内核的启动函数(也称为swapper或进程0)建立内存管理(分页表和内存分页),检测CPU的类型和任何附加功能,比如浮点数功能,然后通过调用start_kernel()切换到非体系结构特定的Linux内核功能

 

start_kernel执行各种初始化函数,他设置中断处理(IRQ),进一步配置内存,启动init进程(第一个用户空间进程),然后同故宫cpu_idle()启动空闲任务,值得注意的是,内核启动进程还挂载了初始RAM磁盘("initrd"),该磁盘先前在引导阶段作为临时根文件系统加载,initrd允许直接从内存加载驱动程序模块,而不依赖其他设置(例如硬盘)和访问它们所需的驱动程序(例如SATA驱动程序),静态编译到内核中的一些驱动程序和initrd加载的其他驱动程序的这种分割允许使用更小的内核,稍后,通过调用pivot_root()切换根文件系统,该调用将卸载临时根文件系统,并在访问实际根文件系统之后使用实际根文件系统替换它,然后收回临时根文件系统使用的内存。

 

因此,内核初始化设备,将引导加载程序指定的根文件系统安装为只读,并运行Init(/sbin/init),它被指定为系统运行的第一个进程(PID=1),内核在安装文件系统时打印消息,它还可以选择性地运行initrd,以允许在安装根文件系统之前处理安装和设备相关的问题(RAM磁盘之类的)

 

 

Red Had认为,这一阶段的具体内核过程总结如下:

当内核加载时,它立即初始化和配置计算机的内存,并配置附加到系统上的各种硬件,包括所有处理器,I/O子系统和存储设备,然后,它在内存中预定的位置查找压缩后的initrd镜像,对其进行解压、挂载并加载所有必要的驱动程序,接下来,在卸载initrd磁盘镜像并释放磁盘镜像占用的所有内存之前,初始化于文件系统相关的虚拟设备,比如LVM或者RAID,然后内核创建一个根分区,以只读方式挂载根分区,并释放任何未使用的内存,此时,内核被加载到内存中并可运行,然后,由于没有用户应用程序允许对系统进行有意义的输入,因此无法对其进行太多操作,initramfs风格的引导与之类似,但与所表述的initrd引导不同。

 

此时,启动中断后,调度器可以控制系统的总体管理,提供抢占式的多任务处理,而init进程将继续在用户空间引导用户环境。

 
 


 
 

5. 早期的用户空间

initramfs,也成为早期用户空间,从Linux内核2.5.26版本开始就提供了,其目的是替换以前内核在启动过程中执行的尽可能更多的功能,早期用户空间的典型用途是检测需要哪些设备驱动程序来加载主用户空间文件系统从临时文件系统加载它们。

 
 


 
 

6. 初始化过程

 
 

6.1 SysV init

init是系统中所有进程的父进程,由内核执行,负责启动所有其他进程,它是所有进程的父进程,而init的父进程已经死亡,它负责在这些进程死亡时获取它们,由init管理的进程成为作业(jobs),由/etc/init目录中的文件定义

 

init的任务时在内核完全运行之后“让所有东西按照应有的方式运行”,本质上,它建立并操作整个用户空间,这包括检查和挂载文件系统,启动必要的用户服务,并最终在系统启动完成时切换到用户环境,它类似于Unix和BSD的init进程,但在某些情况下,它已经分化或定制了初始化进程。在一个标准的Linux系统中,init是用一个名为runlevel的参数执行的,该参数的值从0到6不等,该参数决定哪些子系统是可操作的,每个运行级别都有自己的脚本,这些脚本将设置或离开给定的运行级别时涉及的各种过程进行编码,引导过程中需要引用这些脚本,init脚本通常保存在名称为"/etc/rc..."的目录中,init的顶层配置文件位于/etc/inittab

 

在系统引导期间,它检查是否在/etc/inittab中指定了默认运行级别,如果没有,则请求运行级别通过系统控制台进入,然后,它继续为给定的运行级别运行所有相关的引导脚本,包括加载模块、检查根文件系统的完整性(根文件系统是只读挂载的)。然后重新挂载它以实现完全的读写访问,并设置网络。

 

在生成指定的所有进程之后,init进入休眠状态,并等待发生以下三种时间之一:开始结束或死亡的过程、电源故障信号或通过/sbin/telinit请求进一步更改运行级别。

 
 

6.2 Systemd

systemd的开发人员旨在替换从UNIX System V和Berkeley Software Distribution(BSD)操作系统继承而来的Linux init系统,与init一样,systemd也是一个守护进程,它管理其他守护进程,包括systemd,都是后台进程,Systemd是第一个启动的守护进程(在引导期间),也是最后一个终止的守护进程(在关闭期间)

 

最初开发systemd的软件工程师Lennart Poettering和Kay Sievers视图在几个方面超越init守护的效率,它们希望改进表示依赖关系的软件框架,允许在系统启动期间并行地进行更多处理,并减少shell的计算开销。

 

每个守护进程的Systemd初始化指令都记录在声明性配置文件中,而不是shell脚本中,对于进程间通信,systemd使Unix域套接字和D-Bus对正在运行的守护进程可用,Systemd还能够主动并行化。

 

以上资源摘自Wiki百科:https://en.wikipedia.org/wiki/Linux_startup_process

 
 


 
 

7. 详细的了解

要想通透的了解linux的启动过程,以下是必须了解的理论知识:

 
 

BIOS

(Basic Input/Output System)基本输入输出系统,也被称为System BIOS,ROM BIOS或PC BIOS,是非易失性的固件使用在开机过程中执行硬件的初始化(开机启动),并为操作系统和程序提供运行时服务。BIOS固件预安装在个人计算机的系统板上,是第一个打开电源时运行的软件。

大多数BIOS实现专门设计用于与特定计算机或主板模型一起使用,通过与构成互补系统芯片组的各种设备连接。最初,BIOS固件存储在PC主板上的ROM芯片中。在现代计算机系统中,BIOS内容存储在闪存中,因此可以在不从主板上移除芯片的情况下重写它。这允许对BIOS固件进行简单的最终用户更新,从而可以添加新功能或修复错误,但这也会使计算机感染BIOS rootkit。

 
 

FIRMWARE

固件,就是写入只读存储器中(ROM)的程序,例如计算机主板上的基本输入/输出系统BIOS,专业人士都叫他固件。由于早期固件芯片采用ROM设计,所以无法更改,随着技术的不断发展,可以使用EPROM(可擦写可编程只读存储器)或EEPROM(带电可擦可编程只读存储器)进行修改或升级。

 
 

POST

(Power-On Self-Test)加电自检是在计算机或其他数字电子设备开机后立即通过固件或软件程序执行的过程。POST的结果可以显示在作为设备一部分的面板上,输出到外部设备,或者存储以供将来通过诊断工具检索,例如自检可能检测到显示器不起作用,因此可以提供指示灯或扬声器以将错误代码显示为一系列闪烁、蜂鸣声或哔哔声...。在计算机的情况下,POST是设备的于启动序列的一部分,如果它们成功完成,则调用引导加载程序代码以加载操作系统。(POST是BIOS功能的一个主要部分)

 

Bootstrapping

(Booting)引导,是启动计算机的过程,特别是在启动其软件方面,该过程涉及一系列阶段,其中在每个阶段加载更小,更简单的程序,然后执行下一阶段更大、更复杂的程序,引导是一系列事件。例如,当计算机开机时,计算机首先执行一个存储在只读存储器(ROM)中相对小的程序,以及少量所需的数据,以访问RAM或磁盘,并将磁盘中的"操作系统"和"数据"加载到RAM中,以进行后续的操作。启动此序列的小程序被称为"引导加载程序"或"引导程序",这个小程序的唯一工作是加载其他数据和程序,然后从RAM中执行。

 
 

ROM

(Read-Only Memory)只读存储器,是计算机和其他电子设备中使用的一种非易失性存储器,存储在ROM中的数据只能慢慢修改,比较困难,或者根本不可能更改,因此它主要用来存储固件(不会经常更新的软件)。严格来说,只读存储器是指硬连接的存储器,制造后不能改变,在许多应用程序中,这样的内存永远无法更改是一个缺点,因为错误和安全的问题无法修复,并不能添加新特性。每个存储程序计算机可以使用一种非易失性存储的形式(即,在移除电源时保留其数据的存储器)来存储在通电时运行的初始程序(成为引导Bootstrapping)

 
 

RAM

(Random-access memory)随机存取存储内存,易失性存储器:当计算机关闭时,RAM磁盘会丢失存储的数据,除非将内存安排为备用电池电源。所谓随机存取,旨的是存储器中的数据被读取或写入时,所需要的时间与这段信息所在的位置或所写入的位置无关,相对的,读取或写入顺序访问(Sequential Access)存储设备中的信息时,其所需要的时间与位置就会有关系。它主要用来存放操作系统、各种应用程序、数据等。

根据存储单元的工作原理不同,RAM分为静态RAM和动态RAM。

 

静态随机存储器(SRAM)

特点是工作速度快,只要电源不撤除,写入SRAM的信息就不会消失,不需要刷新电路,同时在读出时不破坏原来存放的信息,一经写入可多次读出,但集成度较低,功耗较大。SRAM一般用来作为计算机中的高速缓冲存储器(Cache)

 

动态随机存储器(DRAM)

它将每一位数据存储在集成电路内的单独的微小电容器中。电容器可以充电或放电; 取这两种状态来表示的比特的两个值,通常称为0和1的电荷在电容器慢慢泄漏掉,所以无需干预,芯片上的数据会很快丢失,为防止这种情况,DRAM需要外部存储器刷新电路周期性地重写电容器中的数据,将它们恢复到原来的电荷。与不需要刷新数据的静态随机存取存储器(SRAM)相比,该刷新过程是动态随机存取存储器的定义特征。

 
 

MBR

(Master Boot Record)主引导记录,是一种特殊类型的引导扇区,在一开始就被划分出来,在512字节的主引导扇区中,MBR只占用了其中的446个字节,另外的64个字节交给了 DPT(Disk Partition Table硬盘分区表),最后两个字节“55,AA”是分区的结束标志。MBR扇区可能包含用于定位活动分区并调用其卷引导记录的代码。MBR保存有关如何在该介质上组织包含文件系统的逻辑分区的信息,MBR还包含可执行代码用作已安装操作系统的加载程序。通常是将控制权交给GRUB等其他引导加载程序,或者与每个分区的卷引导记录(VBR)一起使用。此MBR通常成为引导加载程序。MBR中分区表的组织将磁盘的最大可寻址存储空间限制为2TiB(2^32^x512字节),由于这种限制,基于MBR分区方案正在被新计算机中的GUID分区表(GPT)方案取代,GPT可以与MBR共存,以便为旧系统提供某种有限形式的向后兼容性。MBR不存在于非分区媒体上,例如,软盘或闪存之类的其他存储设备,因为这样的媒体被视为单个分区。

 
 

GTP

(GUID Partition Table)GTPS是用于布局的标准分区表的物理计算机存储设备,诸如硬盘驱动器或固态驱动器,使用全球唯一标识符(GUID),由于主引导记录(MBR)分区表的限制性,它构成了同意扩展固件接口(UEFI)标准的一部分。但它也用于某些BIOS系统,它使用32位进行逻辑块寻址(LBA)传统的512字节磁盘扇区。

所有现代个人计算机操作系统都支持GPT。一些(包括x86架构上的macOS和Microsoft Windows)仅支持在具有EFI固件的系统上从GPT分区启动,但FreeBSD和大多数Linux发行版可以从具有旧版BIOS固件接口和EFI的系统上的GPT分区启动。

 
 

VBR

(Volume Boot Record)卷引导记录,(也称为卷引导扇区,分区引导记录或分区引导扇区),它可以在分区数据存储设备(如硬盘)或为分区设备(如软盘)上找到,并包含存储在设备其他部分的引导程序(通常,但不一定是操作系统)的机器码。是尚未分区的数据存储设备的第一个扇区。或在已分区设备上,它是设备上单个分区的第一个扇区,整个设备的第一个扇区是包含分区表的主引导记录(MBR)。卷引导记录中的代码可以由机器的固件直接调用,也可以通过主引导记录(MBR)或引导管理器(GRUB)中的代码间接调用,MBR和VBR中的代码本质上以相同的方式加载。

通过引导管理器调用VBR称为链加载,将各个操作系统安装的引导代码的副本从VBR中复制,并存储在硬盘文件中,在引导加载程序询问用户引导哪个操作系统后,从文件加载相关的VBR内容。

 

扩展:Windows与Linux

例如一些双启动系统,流行的Linux和Windows操作系统,每个都包含在自己的分区中,Windows不支持多引导系统。但是大多数的Linux安装程序都适用于双启动(尽管需要一些分区知识)。但是在重新启动时,引导加载程序将仅识别两个操作系统中的一个,

安装Linux引导管理器/加载程序(通常是GRUB)作为主引导记录指向的主引导加载程序有一些优点,正确安装Linux引导加载程序可以找到Windows操作系统,但是Windows引导管理器无法识别Linux安装(Windows也不会与Linux文件系统交互)。

所以,通常建议将Windows安装到第一个主分区,Windows和Linux的引导加载程序通过计算分区来识别带有数字的分区,(注意,Windows和Linux都根据分区表中分区的顺序来计算分区,这可能与磁盘上分区的顺序不同)在硬盘末端添加或删除分区将对它之前的任何分区都没有影响,但是如果在硬盘驱动器的开头或中间添加或和删除分区,则后续分区的编号可能会更改,如果系统分区的编号发生更改,则需要重新配置引导加载程序,以便操作系统引导并正常进行。

必须将Windows安装到主分区(必须是第一个分区),Linux可以安装在硬盘驱动器上任何位置的分区中,也可以安装到逻辑分区(扩展分区内),如果Linux安装在扩展分区内的逻辑分区中,则它不受主分区中的更改影响。




GRUB2

GNU GRUB (GNU GRand Unified Bootloader, 通常称为GRUB)是GNU Project的一个引导加载程序包。GRUB是参考实现了的*软件基金会的多引导规范,它提供了用户的选择来引导多个操作系统安装在计算机上,或者选择一个特定的内核在一个特定的操作系统的分区可用的配置。打开计算机是,BIOS会找到已配置的主要可引导设备,并从主引导记录(MBR)加载并初始引导程序(引导加载器,即GRUB)。

GRUB的两个主要版本是常用的:

  • GRUB版本1,称为GRUB,只在Linux发行版,其中一些仍然在使用,并支持老版本的流行,例如CentOS的 5 。
  • GRUB 2是从头开始编写和旨在取代其前身,现在被大多数Linux发行版使用。








拿CentOS7举例,这是我个人理解的启动过程:


(1)引导阶段

第一步:打开电源后(开机),计算机相对较笨,只能读取部分存储空间(ROM),由于ROM的特性,这时CPU处于只读模式。在ROM中,存储了一个称为固件的小程序(BIOS),它进行加电自检,检测硬件等(POST),最主要的是,允许访问其他类型的内存(硬盘或RAM)。BIOS存储了磁盘的启动顺序,BIOS按照顺序去找MBR。找到后会将MBR中的引导代码复制到RAM中(是一个446字节的boot.img镜像)。并转移控制权(CPU)。MBR本身还包含一个64字节的分区表,描述了分区的大小和其他属性。剩余的2字节用于磁盘签名。
(此步骤称为阶段1)

第二步:由于MBR引导程序不能理解文件系统的结构,因此它必须定位core.img然后加载到RAM中,并转移控制权。core.img代码必须位于MBR和第一个分区之间的位置,由于历史技术原因,第一个分区的扇区开始位置是63,可以计算(一个扇区为512bytes),62*512=31744,然后减去MBR占用的一个扇区31744-512=31232,而一个core.img的大小也就26676左右。在此注意,还有一个diskboot.img,它是core.img的第一个扇区(即整个磁盘的第二个扇区),其唯一目的是加载core.img中通用的文件系统的驱动,例如FAT,NTFS,EXT等。,这时就可以直接访问文件系统了(/boot文件)。(此步骤为阶段1.5)

第三步:core.img进入32位保护模式,然后加载/boot/grub2/i386-pc/normal.mod,normal.mod用于解析/boot/grub2/grub.cfg文件,可选择加载模块(例如,用于图形UI)并显示菜单。然后通过用户的选择(或者默认)将指定的内核加载到RAM中。并将控制权交给内核(此步骤为阶段2)




(2)内核阶段


加载阶段:内核同通常以镜像文件的形式加载,用zlib压缩成zImage或bzImage格式,内核作为压缩的镜像文件,将解压缩自身。

启动阶段:内核会立即初始化和配置计算机内存,并配置附加到系统上的各种硬件,包括所有处理器,I/O子系统和存储设备。然后它在内存中预定的位置查找压缩后的initrd(临时根文件系统),对其进行解压,然后内核启动虚拟内存交换器(它是一个内核进程)并创建一个根分区,以只读方式挂载,该initrd中包含各种可执行程序和驱动程序。然后内核创建初始用户空间进程执行/sbin/init,这里的init其实就是/usr/lib/systemd的链接文件(即调用systemd,其PID为1)。等待systemd将实际的根文件系统挂载后,再将initrd这个临时的根文件系统卸载。

在Centos7的/boot文件中有一个initrd-plymouth.img镜像文件,你可以解压看看:

[root@www ~]# cp /boot/initrd-plymouth.img ./initrd.img.gz
[root@www ~]# gunzip initrd.img.gz
[root@www ~]# cpio -i -d < initrd.img
2663 blocks
[root@www ~]# ls
bin  etc  initrd.img  lib64  sbin  usr




(3)初始化阶段

systemd是所有进程的父进程。也是一个守护进程,它管理其他守护进程。包括systemd,都是后台进程,systemd是第一个启动的守护进程(在引导期间),也是最后一个终止的守护进程(在关闭期间)。
首先systemd按照/etc/fstab文件中的配置进行挂载。再根据运行级别(通过/etc/systemd/system/default.target链接文件得知运行级别)然后启动该运行级别的一系列的依赖(详细的启动顺序可使用命令systemd-analyze plot > boot.html然后用浏览器查看)。最后执行getty.target启动终端和登陆程序。至此,整个过程结束。


以上是我个人理解,但还是有很多问题,例如最后,getty.target开启了终端,那么它是调用/bin/login还是getty.target本身就和/bin/login一样,这里还是很迷。