centos7无盘启动_从无盘启动看Linux启动原理

作者:bobyzhang,腾讯 IEG 运营开发工程师

0. 故事的开始

0.1 为什么和做什么

最近家里买了对音响,我需要一个数字播放器。一凡研究后我看上了volumio(

我打算让volumio运行在我2009年购买的老爷机笔记本上,也让它发挥一点余温热。正常操作是将volumio的系统镜像刷到U盘上,连接电脑后使用U盘启动系统即可。但是家里没有找到合适的U盘(穷~~),加上前段时间听了同事关于linux内核的分享,感慨自己对系统的理解不够。因此我决定使用无盘启动volumio顺便研究一下linux启动原理。

目标:无盘启动volumio系统

0.2 方案

正常Linux启动流程大体如下:BIOS启动,完成自检,选择启动硬件

如果是磁盘系统读取MBR

从MBR指示,找到GRUB所在分区,加载GRUB显示菜单

加载Linux内核到内存中

执行INIT程序

进入用户界面

由于我需要从网络启动,过程会变得复杂一些,主要变化如下在MBR引导前,需要执行一系列的PXE流程,目的是挂载iscsi磁盘。

在加载linux内核后,由于之前iPXE固件已经退出,还需要再次挂载iscsi磁盘。

0.3 准备工作

无盘启动并不是说完全没有磁盘,只是客户端本身没有磁盘,我们需要在远端给机器提供一种文件存储和磁盘共享的方案。 我这里选择的是iscsi共享,相比于NFS和samba共享,它更底层,对系统的兼容性更好。

iSCSI利用了TCP/IP作为沟通的渠道。透过两部计算机之间利用iSCSI的协议来交换SCSI命令,让计算机可以透过高速的局域网集线来把SAN模拟成为本地的储存设备。

关于iscsi的配置不是本文重点,这里就不详细描述了,要完成iscsi磁盘的挂载需要接信息。

iscsi服务器地址:我这里是nas服务的地址192.168.3.5

target名称:这个是服务端用来区分目标的,通常一个target服务一个客户端,并关联一块共享存储,例如:iqn.2005-10.org.freenas.ctl:yong-pc.volumio

initiator名称:这个是客户端名称,用来告诉服务端谁来请求了。

1 BIOS和UEFI

准备工作做完,我们先来了解一下计算机的启动原理,这里就要说到BIOS和UEFI,他们是计算机按下电源后最先被执行的程序。

1.1 BIOS (Basic Input/Output System)

上个世纪70年代初,"只读内存"(read-only memory,缩写为ROM)发明,开机程序被刷入ROM芯片,计算机通电后,第一件事就是读取它。这块芯片里的程序叫做"基本输入输出系统"(Basic Input/Output System),简称为BIOS。

BIOS程序首先检查,计算机硬件能否满足运行的基本条件,这叫做"硬件自检"(Power-On Self-Test),缩写为POST。 硬件自检完成后,BIOS把控制权转交给下一阶段的启动程序。

这时,BIOS需要知道,"下一阶段的启动程序"具体存放在哪一个设备。也就是说,BIOS需要有一个外部储存设备的排序,排在前面的设备就是优先转交控制权的设备。这种排序叫做"启动顺序"(Boot Sequence)。

1.2 UEFI (Unified Extensible Firmware Interface)

不知道大家是否发现,这些年已经很难看到BIOS的身影了。

ROM的存储能力有限,BIOS能驱动的硬件类型和数量大大受限。导致大量新硬件无法在PC启动时被加载。最明显就是你无法在BIOS时使用鼠标。此外BIOS的代码历史悠久难以维护。

在2005年年中时候,包括BIOS供应商、OS供应商、系统制造商以及芯片生产公司在内的行业参与者统一建立了统一的EFI联盟(UEFI,Unified Extensible Firmware Interface)并在2006年一月发行了UEFI规范2.0。

从此你可以愉快的在PC启动初期使用鼠标,甚至像苹果一样加载网络,实现联网下载并安装操作系统。

UEFI的启动流程和BIOS的启动流程不同,由于我2009年购买的老爷机还是BIOS结构,这里不详细展开,简单提一下。系统开机 - 上电自检(Power On Self Test 或 POST)。

UEFI 固件被加载,并由它初始化启动要用的硬件。

固件读取其引导管理器以确定从何处(比如,从哪个硬盘及分区)加载哪个 UEFI 应用。

固件按照引导管理器中的启动项目,加载UEFI应用。

已启动的 UEFI 应用还可以启动其他应用(对应于 UEFI shell 或 rEFInd之类的引导管理器的情况)或者启动内核及initramfs(对应于GRUB之类引导器的情况),这取决于 UEFI 应用的配置

2. PXE

回到我的BIOS老爷机,上电自检完成后BIOS按照设置的启动顺序应该交棒磁盘,但是 但是 但是 这个机器没有硬盘,也没有插入U盘,找不到任何启动设备的BIOS将控制权交给了网卡,BIOS光荣退场进入了PXE阶段。

**预启动执行环境(Preboot eXecution Environment,PXE,也被称为预执行环境)**提供了一种使用网络接口启动计算机的机制。这种机制让计算机的启动可以不依赖本地数据存储设备(如硬盘)或本地已安装的操作系统。

2.1 PXE原理Client向DHCP发送IP地址请求消息,DHCP返回Client的IP地址,同时将启动文件(如:pxelinux.0)的位置信息(通常是TFTP路径)一并传送给Client

Client向TFTP发送获取启动文件请求消息,TFTP接收到消息之后再向Client发送启动文件大小信息,试探Client是否满意,当TFTP收到Client发回的同意大小信息之后,正式向Client发送启动文件Client执行接收文件

Client向TFTP发送针对本机的配置信息文件请求,TFTP将配置文件发回Client,继而Client根据配置文件执行后续操作。

Client会加载启动文件,之后根据配置执行动作。这里有多重方案进行下一步操作。可以直接通过Http协议获取Linux kernel和ramdisk然后启动

或者加载一块iscsi磁盘,将linux kernel和ramdisk等信息放在iscsi磁盘中,走正常磁盘引导。我用的是这种方案

2.2 iPXE

上面说到了启动文件,普通的pxe启动文件功能有限,通常只能从tftp服务器上获取文件,不支持HTTP协议和其他共享协议,更别说我们要支持的iscsi磁盘挂载了。这里推荐一个高端开源pxe启动文件:iPXE(

iPXE需要根据自己硬件对应的平台进行编译,编译前需要搞清楚几个要点:启动方式:BIOS或者EFI前面已经说了。

平台:X86或ARM,如果用树莓派等产品就是ARM,PC是x86

CPU位:32或64,32位机器只支持32位固件,64位机器可以兼容32位和64位固件。注意:如果使用64位固件需要保证后续所有环节使用兼容64位的软件,我就遇到了SysLinux不支持64位,导致卡死的问题。

git clone git://git.ipxe.org/ipxe.git

make [platform]/[driver].[extension]

Platform支持如下:按照上面说的启动方式、平台、CPU情况选择。bin (alias for bin-i386-pcbios)

bin-i386-pcbios

bin-i386-efi

bin-i386-linux

bin-x86_64-efi

bin-x86_64-linux

bin-x86_64-pcbios

bin-arm32-efi

bin-arm64-efi

Driver:主要选择支持的网卡驱动类型,一般选ipxe(表示所有支持的网卡,但可能导致生成的启动文件过大,如果过大可以酌情选其它)

Boot type:和启动方式、启动介质有关,参考下表:

编译时添加 EMBED={脚本名称} 可以关联一个启动脚本。推荐一个大佬做好的脚本 http://boot.netboot.xyz/menu.ipxe 可以直接使用。

我最终命令如下:

git clone git://git.ipxe.org/ipxe.git

cd ./ipxe/src

wget http://boot.netboot.xyz/menu.ipxe

make bin-i386-pcbios/ipxe.pxe EMBED=menu.ipxe

完成之后在/data/ipxe/src/bin-i386-pcbios/ipxe.pxe可以拿到最终的启动文件。

2.3 DHCP、TFTP配置

如何配置DHCP和TFTP服务器不是本文重点,如果需要命令行方式配置可以参考这篇文章的前半部分https://blog.51cto.com/dyc2005/2068188

如今大部分高端路由器或开源路由器固件都内置了DHCP和TFTP配置功能。我家的LEDE路由器配置界面如下。TFTP服务器根目录:这个是启动文件、配置文件存放的目录路径(是在路由器上的路径,可以放在u盘挂上去,也可以直接放在路由器存储的目录)

网络启动镜像:这是对客户端下发的启动文件名称。(不同CPU架构,不同平台的文件名不同)

拷贝之前编译好的ipxe.pxe和menu.ipxe文件到/www/pxe/目录下,并设置网络启动镜像为:ipxe.pxe

配置正确,启动后就可以看到如下选择界面了:

3. 分区:MBR和GPT

ipxe完成使命后,正式交棒给磁盘,如果你是硬盘启动,可以直接跳过第2部分,直接到这一步。这一阶段系统需要从磁盘上找到启动文件并加载。在说如何找到启动文件前,先要说说硬盘是如何划分区块的,主要有两大方式MBR和GPT。我们先来聊一下机械硬盘的工作原理。

机械硬盘由坚硬金属材料制成的涂以磁性介质的盘片,盘片两面称为盘面或扇面。

假设磁头不动,硬盘旋转,那么磁头就会在磁盘表面画出一个圆形轨迹并将之磁化,数据就保存在这些磁化区中,称之为磁道,将每个磁道分段,一个弧段就是一个扇区。一个硬盘可以包含多个扇面,扇面同轴重叠放置,每个盘面磁道数相同,具有相同周长的磁道所形成的圆柱称之为柱面,柱面数与磁道数相等。如下图:

最初的寻址方式称为CHS,所谓CHS即柱面(cylinder),磁头(header),扇区(sector),通过这三个变量描述磁盘地址。

3.1 MBR

说了这么多还是没说明白到底计算机怎么从磁盘上找到引导程序。答案是:它被固定写死在了 0柱面,0磁头,1扇区的位置通常是512byte,这个位置被称为主扇区(Master Boot Record, MBR)。

MBR主要包含如下数据:主引导记录(bootloader),负责从活动分区加载并运行系统引导程序。446字节

硬盘分区表项(DPT——disk partition table),由四个分区表项组成,负责记录磁盘的分区情况。64字节。

硬盘有效标志(magic number),代表引导扇区结束,占用2字节。

Bootloader: 这部分记录了一段较小引导代码,用于去启动硬盘其他分区位置上更大的引导文件,例如linux操作系统的grub目录。

我们知道一个硬盘的每个分区的第一个扇区叫做boot sector,这个扇区存放的就是操作系统的loader。如上图,第一个分区的boot sector存放着windows的loader,第二个分区放着Linux的loader,第三个第四个由于没有安装操作系统所以空着。至于MBR的bootloader是干嘛呢, bootloader有三个功能:提供选单:让用户选择进入哪个系统。

读取内核文件:默认启动的loader会被拷贝一份到MBR中,这样就可以直接读取内核了,图中1部分

转交给其他loader:图中2,3部分

Disk Partition table: 这一部分64字节大小被均分为4份,每份大小16字节,每当我们在硬盘上创建出一个新的主分区或者扩展分区时,便会占用1个16字节的大小用于记录这个分区的相关信息(例如起始和截止柱面位置、分区文件系统类型等等)。这就是为什么mbr分区模式最多只能有4个主分区的原因。

MBR的局限:最多只支持4个主分区,超过4个就需要使用扩展分区。

磁盘的最大容量只能到2.2TB

如今我家的硬盘都4T了,MBR早就不能满足需求了。你也不能怪MBR,毕竟人家1983年就提出了,比我的年纪还大。

3.2 GPT

为了解决MBR的问题,GPT分区诞生,GPT全称Globally Unique Identifier Partition Table,也叫GUID分区表,它是UEFI规范的一部分(但这并不是说它只支持UEFI,它也支持BIOS方式的引导)。

GPT分区结构如下:Protective MBR:GPT分区表的最前面部分也保存了和MBR相同的格式和内容称为Protective MBR,这极大的提高了GPT分区表的兼容性。

主GPT Header:这里记录了分区表项目数和每项目大小。

主GPT分区表:包含分区的类型GUID,名称,起始终止位置,该分区的GUID以及分区属性

实际分区

备份GPT分区表: 用于提高安全性,防止主GPT分区表损坏

备份GPT Header: 用于提高安全性,防止主GPT Header损坏

3.3 Bootloader写入

使用dd命令结合hexdump可以输出MBR信息

dd if=~/Desktop/volumio-2.799-2020-07-16-x86.img ibs=512 count=1 | hexdump -C

同样的使用dd命令可以拷贝MBR信息从img文件到物理磁盘。(之前我是分分区写入到磁盘的,导致MBR信息丢失无法引导)

dd if=~/Desktop/volumio-2.799-2020-07-16-x86.img ibs=512 count=1 of=/dev/sda

也可以使用下载的syslinux中的mbr.bin写入

dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/bios/mbr.bin of=/dev/sda //MBR分区表

dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/bios/gptmbr.bin of=/dev/sda //GPT分区表

4. 引导加载程序:Syslinux和GRUB

前文说到MBR的bootloader主要功能是交棒内核,但是bootloader不会直接拉起linux内核,400K太小,它没有能力将linux内核直接加载到内存。这时需要引导加载程序登场,它的主要目的就是将系统内核镜像和initrd镜像加载到内存并将控制权交给它们。目前常用的有两种Syslinux和GRUB:Syslinux是一个启动加载器集合,可以从硬盘、光盘或通过 PXE 的网络引导启动系统。支持的文件系统包括 FAT,ext2,ext3,ext4 和非压缩单设备 Btrfs 文件系统。

GRUB ,即GRand Unified Bootloader(大一统启动加载器),是一个多重启动加载器,承自PUPA项目。今的GRUB也被称作GRUB 2,而GRUB Legacy 表示0.9x版本。

对于普通用户来说他们有什么用呢?它可以提供选单选择Linux内核版本,此外加载程序使得我们可以向Linux内核传递参数。这点很重要,在我的案例中volumio就是通过Syslinux向内核传递启动参数的。

Syslinux已经不支持bios64位系统了,目前使用GRUB2 的比较多。由于volumio使用的是Syslinux我没有对GRUB展开研究。

下图是volumio的默认syslinux配置。LINUX命令:指定了当前内核文件为vmlinuz-3.18.5版本;

INITRD命令:指定了initrd文件为volumio.initrd(之后修改initrd也就是修改这个文件);

APPEND命令:是向内核传递的参数,在下文initrd的init shell中可以通过cat /proc/cmdline读取到。

这里指定了imgpart,bootpart的uuid用于挂载分区,imgfile名字用于确定当前真实root分区的文件名,还有loglvevel、USE_KMSG等参数。

5. 内核:vmlinuz和initrd

引导加载程序交棒之后系统进入内核引导阶段。这一步会在内存中运行系统内核和根文件系统。之后根目录下的init shell会被调用执行,完成进一步的初始化操作。

5.1 vmlinuz和initrd

vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核。

initrd是“initial ramdisk”的简写。initrd一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。initrd 字面上的意思就是"boot loader initialized RAM disk",换言之,这是一块特殊的RAM disk,在载入Linux kernel前,由boot loader予以初始化,启动过程会优先执行initrd的init程序,initrd完成阶段性目标后,kernel 会挂载真正的root file system ,并执行/sbin/init程序。

采用这种分离的方式,使得我们有机会在内核引导阶段做一些我们自己的事情。简单读了volumio.initrd中的init shell发现它至少做了几件事情:读取syslinux传递来的环境变量

根据变量决定是否在屏幕打印日志。 USE_KMSG参数决定

加载各种内核驱动模块

挂载boot分区

使用fdisk处理磁盘,img文件写入磁盘后大小不一致,首次启动需要使用fdisk命令调整分区大小

挂载一个imgpart分区,这个不是真正的root分区,这里面的volumio_current.sqsh文件才是,这样做的目的是方便系统升级,在系统内替换imgpart分区的volumio_current.sqsh文件即可完成系统升级。volumio_current.sqsh文件名也是通过 imgfile 参数决定的。

处理volumio_current.sqsh升级问题,发现有新的 volumio.sqsh文件会重命名旧的,然后将新的重命名volumio_current.sqsh

使用overlay方式结合volumio_current.sqsh文件挂载真正的root分区。

执行switch_root命令,重定向新的根分区并执行/sbin/init命令。

5.2 initrd编辑

由于linux内核启动后,之前ipxe对应的环境已经退出,因此之前挂载的iscsi磁盘也无法访问,需要在initrd的init shell中重新挂载iscsi磁盘。因此我需要在上文的4步骤之前挂载iscsi磁盘,修改如下:加载网卡内核驱动

启动网络

启动iscsi客户端挂载网络磁盘。

可以使用如下方式编辑已经生成好的initrd文件。

mount -o loop,offset=1048576 ./wrt/Build/Volumio2.799-2020-09-29-x86.img ./vboot/ //挂载img镜像的boot分区到目录

cp ../vboot/volumio.initrd volumio.initrd.gz //拷贝initrd文件,重命名一下

gunzip ./volumio.initrd.gz //解压gz文件

cpio -ivmd < volumio.initrd //展开initrd文件,在当前目录就可以看到整个rom disk的内容了

vim init //编辑init shell

find . | cpio -c -o > ../volumio.initrd.img //重新打包成新的initrd

gzip volumio.initrd.img

mv volumio.initrd.img.gz volumio.initrd

还有另外一种方案,由于volumio是开源项目,编译volumio的脚本在github开源。我可以编辑编译脚本,直接修改init之后编译成新的initrd文件。

git clone https://github.com/volumio/Build.git

ls -la scripts/initramfs/init-x86

ls -la scripts/x86config.shx86config.sh 这是编译生成x86版本volumio镜像的脚本,在这个文件中,我们需要添加命令,使得生成的initrd文件中包含iscsi客户端

init-x86 这个文件是initrd文件在系统启动后,需要执行的init shell脚本。这里我们需要添加 网卡驱动、初始化iscsi客户端。

首先处理x86config.sh脚本,我们需要在initrd中添加iscsi客户端下图中: 193-195行安装iscsi客户端 231-232行向initrd中添加iscsi模块

之后处理init-x86,在118行左右的位置,脚本读取了配置在/proc/cmdline中的根目录uuid并在之后挂载磁盘。这里的cmdline就是之前说到的在syslinux阶段向内核传递的参数。所以我们要在挂载磁盘前加载网卡驱动、启动网络、启动iscsi客户端、挂载iscsi磁盘。

修改如下图:103行 加载网卡驱动

104行 加载iscsi内核模块

105行 加载iscsi ibft模块

107-108行 通过ibft配置网络

114-116行 使用ibft配置连接iscsi服务器并挂载磁盘

这里要说一下ibft这是一种将iscsi配置信息传递到系统的方式,我们在iPxe阶段已经配置网络信息、iscsi服务器地址、iscsi target等信息了,这里可以使用ibft直接读取并使用。当然你也可以在这里再次手动启用DHCP,手动初始化iscsi客户端。

修改完成后,iscsi磁盘就可以像正常本地磁盘一样被挂载,之后的操作就和正常硬盘安装一样了,正常启动进入volumio系统。

6. init进程

内核引导阶段完成以后,系统会挂载真实的root分区,执行/sbin/init程序初始化系统环境。这一阶段已经和是否网络启动没有关系了,不过启动原理都研究到现在了就顺便一起看一下吧。

/sbin/init会首先确定运行级别,这个配置在/etc/inittab中,一般Linux有7种运行级别(0-6)。一般来说,0是关机,1是单用户模式(也就是维护模式),6是重启。运行级别2-5,各个发行版不太一样,对于Debian来说,都是同样的多用户模式(也就是正常模式)。 确定运行级别后会访问/etc/rcN.d(这里的N就是运行级别)。

这里的文件都采用“字母S或K+两位数字+程序名”的命名方式。其中S开头的表示在这个级别需要执行start命令,K开头需要执行Stop命令,数字越小越优先执行。系统会依次执行相应的软件和服务,负责用户界面的程序也被启动你就有了X11界面,然后是SSH服务你就可以使用ssh登录。这样系统就完成了启动。

当然啦现在这种方式已经过时了,目前基本使用systemd方式用systemctl命令管理。篇幅已经很长了,这块有兴趣的同学自己搜索一下。

7. 尾巴

7.1 其他遇到的问题

syslinux卡死 这个问题前面说到了,挂载iscsi磁盘后ipxe交棒磁盘引导,但是就卡死了。

经过很多的google和尝试之后最终发现,我使用了64位的iPxe引导固件,但是syslinux只有32位版本导致卡死,更换了32位的iPxe固件后解决。

可以启动无法关闭 这个问题困扰了我很久,系统可以正常启动,但是在关机或者重启时会死机,按键没有任何反应但是系统应该还是活的(大小写灯正常切换)只能强制关机退出。经过排查原因可能是:关机时网络服务会关闭导致网卡关闭,进而导致iscsi网盘断开。但是此时系统根分区还没有umount导致系统无响应。

我禁用了网络服务的关机关闭,把K06networking从rc0.d目录中去掉就好了。

Airplay服务无法找到 Volumio自带shairport-sync服务,手机可以通过airplay链接volumio系统播放音乐,但是在我折腾完以后发现怎么也搜不到。经过排查shairport-sync使用mDns发布组播告诉局域网内的所有设备自己的地址,使用的是avahi-daemon程序。排查日志发现它启动时没有识别到网卡。我猜原因应该是我们的网卡是在内核引导阶段自己拉起的,并不是进入系统后由networking服务拉起的,所以avahi-daemon无法查找到它对应的ip。

我没有找到很好的解决方案,还好老爷机还有一块无线网卡,最后使用了无线网卡绑定shairport-sync服务。

7.2 最终效果

7.3 总结

总结:为了省掉一块U盘,我开始折腾iscsi无盘启动没想到这一折腾就是好久,前后研究了好多资料好好的学习了一下linux的启动原理。

实际过程并没有文中展现的那么顺利,很多研究的弯路没有在文中一一展现出来。在不同的节点也有很多方案可以选择,比如:iPxe本可以直接http下载vmlinuz和initrd引导,这样就可以省去MBR和syslinux引导。但是后来想想都研究了还是整理给大家。再比如initrd中iscsi客户端的启动和初始化有很多种方式,一开始我都手动初始化网卡,设置dhcp和ip路由。最后还是觉得太麻烦发现ibft的方案最简单,果断选择了它。

水平有限如果发现那里总结的不对欢迎指正。

你都看到这了点个赞再走吧~ 对了前几天99公益日同事10块钱买了块U盘好像挺香的~

参考文献

更多干货尽在腾讯技术,官方微信交流群已建立,交流讨论可加:Journeylife1900(备注腾讯技术) 。

你可能感兴趣的:(centos7无盘启动)