文章目录
- 详细分析 系统启动过程
- 主要流程
- 阶段说明
- BIOS
- MBR(Stage 1 bootloader)
- GROUB(Stage 2 bootloader)
- kernel
- vmlinuz
- initrd.img
- Init
详细分析 系统启动过程
主要流程
PC 启动主要流程,分为四个阶段:
BIOS -> MBR -> GRUB -> KERNEL -> INIT
-
主机加电后,系统首先加载BIOS,这个BIOS是烧录在主板上的ROM芯片上的。
-
BIOS启动后,执行了一些例如开机自检,硬件初始化等工作,然后读取硬盘MBR分区的第 一个扇区(前512字节),其中前446字节储存了一个小程序叫做boot loader,中间的64 字节是磁盘分区表,最后两个字节是固定的0x55AA的文件类型识别标记。
(1) UEFI对比BIOS本身就是一个微型的操作系统,直接读取FAT格式的文件分区(EFI 程序),相比BIOS读取MBR文件分区快了不少。
(2) 常见的boot loader有GRUB、U-boot、UEFI、Etherboot、ARMboot等。
-
bootloader程序主要用的两个文件,/boot/vmlinuz内核文件,/boot/initramfs虚拟文件系 统。
-
这两个文件加载到内存运行后,系统会加载真正的文件系统,然后启动1号进程systemd(init)。 这样真正使用的系统就启动了。
换句话说,也可以换成下图,方便理解
需要说明的是,这里有 bootloader 学过嵌入式的同学都知道,ARM 板子使用的 bootloader 是 UBOOT 去启动的,uboot 一般使用 busybox 或者buildroot,今天主要讲下 pc 的启动,就不详细说嵌入式的 UBOOT;
其实常见的 boot loader 有GRUB、U-boot、UEFI、Etherboot、ARMboot等。
PC 相对于 嵌入式 arm 会复杂一些,所以在 x86 PC 是 GRUB、UEFI,arm PC 是 UEFI
阶段说明
BIOS
主机加电后,系统首先加载BIOS,这个BIOS是烧录在主板上的ROM芯片上的。
BIOS(基本输入输出系统)是烧录计算机在主板芯片上的程序,它保存着计算机基本的输入输出的程序,开机后的硬件自检程序和系统自启动程序,它从COMS中读写系统的设置的具体信息,为计算机提供最底层的硬件控制。
BIOS设置硬件参数,设置显示类型核显显示、独立显示和自动,设置串口协议和波特率,常见协议有RS-232,RS-422,RS-485,设置启动硬盘(安装双系统建议使用两块硬盘),控制风扇转速和模式,设置TPM模式,是否上电自启,设置CPU功耗,设置网络的POE供电功能,设置BOOT启动模式传统模式(Legacy Boot Type)、UEFI模式和双模式,设置PXE网络协议启动,设置是否开启门口看门狗-WDT(当检测到系统程序非正常运行后,会强制CPU发送复位信号使整个系统复位)
(BIOS硬件图:双排直插式封装 DIP,上面有”BIOS“ 丝印)
MBR(Stage 1 bootloader)
1、BIOS启动后,执行了一些例如开机自检,硬件初始化等工作,然后读取硬盘MBR分区的第 一个扇区(前512字节),其中前446字节储存了一个小程序叫做boot loader,中间的64 字节是磁盘分区表,最后两个字节是固定的0xAA55的文件类型识别标记。
2、具体作用
前446字节是primary bootloader,包含了可执行代码和错误信息字符串。接下去64字节是磁盘的分区表,该分区表中包含了四条分区记录,每条分区记录为16字节,分区记录可以为空,若为空则表示分区不存在。最后是2个字节的magic number,这两个字节是固定的0xAA55,这两个字节的magic number可以用于判断该MBR记录是否存在;
primary bootloader的作用就是用于寻找并定位secondary bootloader,也就是Stage 2 bootloader。它通过遍历分区表寻找可用的分区,当它发现可用的分区的时候,还是会继续扫描其他分区,确保其他分区是不可用的。然后从可用的分区中读取secondary bootloader到内存中,并执行
3、MBR的内容,在Linux中可以通过以下命令获取:
dd if=/dev/sda of=mbr.bin bs=512 count=1
od -xa mbr.bin
注:UEFI对比BIOS本身就是一个微型的操作系统,直接读取FAT格式的文件分区(EFI 程序),相比BIOS读取MBR文件分区快了不少
GROUB(Stage 2 bootloader)
bootloader程序主要用的两个文件,/boot/vmlinuz内核文件,/boot/initramfs虚拟文件系 统。
Stage 2 bootloader 也称为 secondary bootloader,更恰当是 kernel loader,它的任务是把 kernel 加载到 内存中,并根据设置,有选择性的 将 initial RAM disk 也加载到内存中。
在x86 PC环境中,Stage 1 bootloader和Stage 2 bootloader合并起来就是 LILO (Linux Loader)或者GRUB(GRand Unified Bootloader)。因为LILO中存在一些缺点,并且这些缺点在GRUB中得到了比较好的解决,所以这里将会以GRUB为准进行讲解。
GRUB的一大优点是,它能够正确识别到Linux文件系统。相对于像LILO那样只能读取原始扇区数据,GRUB则可以从ext2和ext3的文件系统中读取到Linux内核。为了实现这个功能,GRUB将原本2个步骤的bootloader变成了3个步骤,多了Stage 1.5 bootloader,即在Stage 1 bootloader和Stage 2 bootload中间加载一个可以识别Linux文件系统的bootloader(Stage 1.5 bootloader),例如reiserfs_stage1_5(用于识别Reiser日志文件系统)或者e2fs_stage1_5(用于识别ext2和ext3文件系统)。当Stage 1.5 bootloader被加载和执行后,就可以继续Stage 2 bootloader的加载和执行了。
当Stage 2 bootloader被加载到内存后,GRUB就能够显示一系列可启动的内核(这些可启动的内核定义于/etc/grub.conf文件中,该文件是指向/etc/grub/menu.lst和/etc/grub.conf的软链接)。你可以在这些文件中配置,让系统自己默认选择某一个内核启动,并且可以配置内核启动的相应参数(如下图)。
当Stage 2 bootloader已经被加载到内存中,文件系统被识别到,并且默认的内核镜像和initrd镜像被加载到内存中,这就意味着镜像都已经准备好了,可以直接调用内核镜像开始内核的启动了。
在Ubuntu中bootloader的相关信息可以在/boot/grub/目录下找到,主要是/boot/grub/grub.cfg,但是该文件是自读的,需要在其他地方(如/etc/default/grub)更改,然后执行update-grub。
kernel
既然内核镜像已经准备好,并且控制权已经从Stage 2 bootloader传递过来,启动过程的Kernel阶段就可以开始了。内核镜像并非直接可以运行,而是一个被压缩过的。通常情况下,它是一个通过zlib压缩的zImage(compressed image小于51KB)或者bzImage(big compressed image,大于512KB)文件。在内核镜像的开头是一个小程序,该程序对硬件进行简单的配置并将压缩过的内核解压到高内存地址空间中
在./init/main.c:start_kernl()函数中,一长串的初始化函数将会被调用到用于设置中断、执行更详细的内存配置、加载initial RAM disk等。接着,将会调用./arch/i386/kernel/process.c:kernel_thread()函数来启动第一个用户空间进程,该进程的执行函数是init。最后,idle进程将(cpu_idle)会被启动,并且调度器其将接管整个系统。当中断使能时,可抢占的调度器周期性地接管系统,用于提供多任务同时运行的能力。
在内核启动的时候,原本由Stage 2 bootloader加载到内核的initial RAM disk(initrd)将会被挂载上。这个位于RAM里面的initrd将会临时充当根文件系统(initramfs.img,下文会把它的原理和制作进行展开),并且允许内核直接启动,而不需要挂载任何的物理磁盘。因为那些用于跟外设交互的内核模块可以被放置到initrd中,所以内核可以做得非常小,并且还能支持很多的外设配置。当内核启动起来后,这个临时的根文件系统将会被丢弃(通过pivot_root()函数),即initrd文件系统将会被卸载,而真正的根文件系统将会被挂载。
initrd功能让驱动不需要直接整合到内存中,而是以可加载的模块存在,从而让Linux内核能够做到很小。这些可加载模块为内核提供访问磁盘和文件系统的方法,同时也提供了访问其他硬件设备的方法。因为根文件系统其实是位于磁盘的一个文件系统,initrd提供了访问磁盘和挂载真正根文件系统的方法。在没有磁盘的嵌入式文件系统中,initrd可以作为最终的根文件系统,或者最终的根文件系统可以通过NFS(Network File System)挂载。
vmlinuz
vmlinuz是可引导的、压缩的内核。“vm”代表 “Virtual Memory”
- Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接。
- vmlinuz自然就是内核,initrd.img是一个小的映象,包含一个最小的linux系统。通常的步骤是先启动内核,然后内核挂载initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init… …。
initrd.img
-
initrd.img 当然是可选的了,如果没有 initrd.img ,内核就试图直接挂载root分区。
之所以要有initrd,那是为了启动的时候有更大的灵活性。比如,你把ext3支持编译成模块了。偏偏你真正的root分区又是ext3的。这下就麻烦了。因为内核需要挂载root分区之后才能加载ext3支持。但是没有ext3支持就没法挂载root分区。initrd就是用来解决这个问题的。
类似的用这个东西还可以做其他的事情,比如从usb盘启动linux也会面临上面类似的问题。用initrd就能搞定了。
-
实际根文件系统可用之前挂载到系统中的一个初始根文件系统,initrd中包含了实现这个目标所需要的目录和可执行程序的最小集合
-
在桌面或服务器 Linux 系统中,initrd是一个临时的文件系统。其生存周期很短,只会用作到真实文件系统的一个桥梁。在没有存储设备的嵌入式系统中,initrd是永久的根文件系统
-
作用?
- 因为它是在内核启动之后加载,通过加载 initrd 最小系统中加载设备驱动,这些设备驱动有些是在系统运行中必须的(GPU、网卡等),为真正的的 rootfs 启动做准备,当initrd 系统中的 设备驱动都启动正常后,再加载真正的文件系统,
-
排错?
- 如果没指定 initrd.img 或者指定的 initrd.img 中并没有包含正确的驱动模块,则系统启动时会挂起,并报告"kernel panic: VFS:
-
常见修改方式?
-
设备驱动模块更新:下载内核源码包,(具体方法,网上有很多相关资料,或许有时间我会写一篇),用 make menuconfig 时最好直接选上随着原来 kernel &
initrd.img 在一起的config文件,重新编译内核。如果不出错,执行make modules_install 后就生成了需要的模块(通常在目录/lib/modules/kernel-version)。之后我用新的/lib/modules/kernel-version/lib/下的modules目录及其文件替换掉旧的initrd.img中的modules目录(当然先得拆解之,方法见第2部分)。
备注:通过cpio -i < initrd-kernel_version.img
-
-
如果不能直接修改 ISO 镜像怎么办?
- 直接在系统中重新制作 initramfs.fs(
mkinitramfs -o /boot/initrd.img-xxx
然后重新启动系统)
- 直接在系统中重新制作 initramfs.fs(
-
流程?
- boot loader 把内核以及 initrd 文件加载到内存的特定位置
- 内核判断 initrd 的文件格式,如果是 cpio 格式
- 将 initrd 的内容释放到 rootfs 中(作为临时根文件系统,加载少量必要的驱动模块)
- 执行 initrd 中的 /init 文件,执行到这一点,内核的工作全部结束,完全交给 /init 文件处理
-
制作
-
一个initramfs至少包含一个文件,即systemd,内核将这个文件执行起来的进程设
为main init进程,pid=1。内核挂载initramfs时,文件系统的根分区并没有挂载,所以无法访问 文件系统中的文件。多数的嵌入式设备需要一个shell,那么也会在initramfs打包进一个shell。如 果还需要其他工具或脚本,也可以打包到initramfs。
注意:打包时,必须包含依赖,因为initramfs是一个能够独立运行的ram文件系统。
1、手动解压制作
流程如下:
解压:
mkdir initrd
cp isolinux/initrd.img initrd
mv initrd.img initrd.img.xz
xz -l initrd.img.xz (看一下当前压缩详细)
xz -d initrd.img.xz (得到initrd.img)
cd initrd
cpio -i -F …/initrd.img
替换其中的 lib/modules/ 目录
压缩:
find . |cpio -oc -O …/initrd.img
xz -z initrd.img --check=crc322、mkinitrd
mkinitrd命令 建立要载入ramdisk的映像文件,以供Linux开机时载入ramdisk。
这个是重新封包核心的命令,例如你自己修改了一个设备的驱动,如果这个驱动要加入核心级别的话,就需要对核心进行重新封包,把新加的配置编译到核心内部去!
-f:若指定的映像问家名称与现有文件重复,则覆盖现有的文件;
-v:执行时显示详细的信息;
–omit-scsi-modules:不要载入SCSI模块;
–preload=<模块名称>:指定要载入的模块;
–with=<模块名称>:指定要载入的模块;
–version:显示版本信息。mkinitrd -v -f myinitrd.img $(uname -r)
3、dracut是用来制作更轻量化initramfs的工具,它的使用方式跟mkinitrd非常接近,迁移成本较低。
-
Init
当内核启动并初始化完毕后,内核就会开始启动第一个用户空间程序,这个被调用的程序是第一个使用标准C库编译的程序,在这之前,所有的程序都不是使用标准C库编译得到的。
在Linux桌面系统中,虽然不是强制规定的,但是第一个启动的应用程序通常是/sbin/init。嵌入式系统中通常很少要求init程序通过/etc/inittab提供大量的初始化工作。很多情况下,用户可以通过调用一个简单的shell脚本来启动所需的应用程序
什么是 init?
Linux在完成核内引导(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式来启动其他用户级的进程或服务.所以,init始终是第一个进程,其PID始终为1(ps -aux | less),它是系统所有进程的父进程.
内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init.如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败.
init程序需要读取配置文件/etc/inittab.inittab是一个不可执行的文本文件,它有若干行指令所组成.
很多系统 init 为啥被 systemd 取代?
init启动时只能串行执行脚本,一个服务启动后才能启动下一个服务,而systemd则可以并行启动服务。 使用systemd在启动速度上比init快的多。 这也是使用systemd替代init的原因。 但systemd也有自身的缺点,systemd虽然功能强大,但是其体系庞大,非常复杂。
systemd 系统:CentOS、Ubuntu、等
参考:
https://www.cnblogs.com/yi-mu-xi/p/13084582.html#:~:text=%E9%80%9A%E5%B8%B8%E7%9A%84%E6%AD%A5%E9%AA%A4%E6%98%AF%E5%85%88%E5%90%AF,t…%E3%80%82
https://blog.csdn.net/lindahui2008/article/details/82525975
https://zhuanlan.zhihu.com/p/567076094