英语:AlisonChaiken,翻译:Linux中国/jessie-pang

/article-9437-1.html

理解运转良好的系统对于处理不可防止的故障是最好的打算。

关于开源软件最古老的笑话是:“代码是自具文档化的self-documenting”。经验表明,阅读源代码如同听天气预报一样:明智的人仍然出门会瞧瞧室内的天气。本文述说了怎样运用调试工具来观察和剖析Linux系统的启动。剖析一个功能正常的系统启动过程,有助于用户和开发人员应对不可防止的故障。

从个别方面看,启动过程十分简单。内核在单核上以单线程和同步状态启动,虽然可以理解。但内核本身是怎样启动的呢?initrd(initialramdisk)和引导程序bootloader具有什么功能?还有,为何以太网端口上的LED灯是常亮的呢?

请继续阅读找寻答案。在GitHub上也提供了介绍演示和练习的代码。

启动的开始:OFF状态

局域网唤起Wake-on-LAN

OFF状态表示系统没有上电,没错吧?表面简单,虽然不然。诸如,假若系统启用了局域网唤起机制(WOL),以太网指示灯将亮起。通过以下命令来检测是否是此类情况:

#sudoethtool

其中是网路插口的名子,例如eth0。(ethtool可以在同名的Linux软件包中找到。)假如输出中的Wake-on显示g,则远程主机可以通过发送魔法数据包MagicPacket来启动系统。倘若您无意远程唤起系统,也不希望其他人这样做,请在系统BIOS菜单上将WOL关掉,或则用以下形式:

#sudoethtool-swold

响应魔法数据包的处理器可能是网路插口的一部份,也可能是底板管理控制器BaseboardManagementController(BMC)。

英特尔管理引擎、平台控制器单元和Minix

BMC不是惟一的在系统关掉时仍在窃听的微控制器(MCU)。x86_64系统还包含了用于远程管理系统的英特尔管理引擎(IME)软件套件。从服务器到电脑笔记本,各类各样的设备都包含了这项技术,它开启了如KVM远程控制和英特尔功能许可服务等功能。按照Intel自己的测量工具,IME存在仍未修复的漏洞。坏消息是,要禁用IME很难。TrammellHudson发起了一个me_cleaner项目,它可以去除一些相对恶劣的IME组件,例如嵌入式Web服务器,但也可能会影响运行它的系统。

linux 开机启动设置_开机启动设置命令_开机启动设置怎么调

IME固件和系统管理模式SystemManagementMode(SMM)软件是基于Minix操作系统的,并运行在单独的平台控制器单元PlatformControllerHub上(LCTT评注:即北桥芯片),而不是主CPU上。之后,SMM启动坐落主处理器上的通用可扩充固件插口UniversalExtensibleFirmwareInterface(UEFI)软件,相关内容已被提到多次。Google的Coreboot小组早已启动了一个雄心勃勃的非扩充性削减版固件Non-ExtensibleReducedFirmware(NERF)项目,其目的除了是要替代UEFI,还要代替初期的Linux用户空间组件,如systemd。在我们等待那些新成果的同时,Linux用户现今就可以从Purism、System76或Dell等处选购禁用了IME的电脑笔记本linux 开机启动设置,另外带有ARM64位处理器电脑笔记本还是值得期盼的。

引导程序

不仅启动这些问题不断的间谍软件外,初期引导固件还有哪些功能呢?引导程序的作用是为新上电的处理器提供通用操作系统(如Linux)所需的资源。在开机时,不但没有虚拟显存,在控制器启动之前连DRAM也没有。之后,引导程序打开电源,并扫描总线和插口,以定位内核镜像和根文件系统的位置。U-Boot和GRUB等常见的引导程序支持USB、PCI和NFS等插口,以及更多的嵌入式专用设备,如NOR闪存和NAND闪存。引导程序还与可信平台模块TrustedPlatformModule(TPM)等硬件安全设备进行交互,在启动最开始构建信任链。

在建立主机上的沙盒中运行U-boot引导程序。

包括覆盆子派、任天堂设备、汽车显卡和Chromebook在内的系统都支持广泛使用的开源引导程序U-Boot。它没有系统日志,当发生问题时,甚至没有任何控制台输出。为了易于调试,U-Boot团队提供了一个沙盒,可以在建立主机甚至是夜晚的持续集成(CI)系统上测试补丁程序。假如系统上安装了Git和GNUCompilerCollection(GCC)等通用的开发工具,使用U-Boot沙盒会相对简单:

#gitclonegit://git.denx.de/u-boot;cdu-boot

#makeARCH=sandboxdefconfig

#make;./u-boot

=>printenv

=>help

在x86_64上运行U-Boot,可以测试一些棘手的功能,如模拟储存设备的重新分区、基于TPM的秘钥操作以及USB设备热拔插等。U-Boot沙盒甚至可以在GDB调试器下单步执行。使用沙盒进行开发的速率比将引导程序刷新到电路板上的测试快10倍,而且可以使用Ctrl+C恢复一个“变砖”的沙盒。

启动内核

配置引导内核

引导程序完成任务后将跳转到已加载到主显存中的内核代码,并开始执行,传递用户指定的任何命令行选项。内核是哪些样的程序呢?用命令file/boot/vmlinuz可以看见它是一个“bzImage”,意思是一个大的压缩的镜像。Linux源代码树包含了一个可以解压缩这个文件的工具——extract-vmlinux:

#scripts/extract-vmlinux/boot/vmlinuz-$(uname-r)>vmlinux

#filevmlinux

vmlinux:ELF64-bitLSBexecutable,x86-64,version1(SYSV),statically

linked,stripped

内核是一个可执行与可链接格式ExecutableandLinkingFormat(ELF)的二补码文件,如同Linux的用户空间程序一样。这意味着我们可以使用binutils包中的命令,如readelf来检测它。比较一下输出,比如:

#readelf-S/bin/date

#readelf-Svmlinux

这两个二补码文件中的段内容大致相同。

所以内核必须像其他的LinuxELF文件一样启动,但用户空间程序是怎样启动的呢?在main()函数中?并不准确。

在main()函数运行之前,程序须要一个执行上下文,包括堆栈显存以及stdio、stdout和stderr的文件描述符。用户空间程序从标准库(多数Linux系统在用“glibc”)中获取这种资源。参照以下输出:

#file/bin/date

/bin/date:ELF64-bitLSBsharedobject,x86-64,version1(SYSV),dynamically

linked,interpreter/lib64/ld-linux-x86-64.so.2,forGNU/Linux2.6.32,

BuildID[sha1]=14e8563676febeb06d701dbee35d225c5a8e565a,

stripped

ELF二补码文件有一个协程,如同Bash和Python脚本一样,而且类库不须要像脚本那样用#!指定,由于ELF是Linux的原生格式。ELF类库通过调用_start()函数来用所需资源配置一个二补码文件,这个函数可以从glibc源代码包中找到,可以用GDB查看。内核其实没有原语,必须自我配置,这是如何做到的呢?

用GDB检测内核的启动给出了答案。首先安装内核的调试软件包,内核中包含一个未剥离的unstrippedvmlinux,比如apt-getinstalllinux-image-amd64-dbg,或则从源代码编译和安装你自己的内核,可以参照DebianKernelHandbook中的指令。gdbvmlinux后加infofiles可显示ELF段init.text。在init.text中用l*(address)列举程序执行的开头,其中address是init.text的十六补码开头。用GDB可以看见x86_64内核从内核文件arch/x86/kernel/head_64.S开始启动,在这个文件中我们找到了汇编函数start_cpu0(),以及一段明晰的代码显示在调用x86_64start_kernel()函数之前创建了堆栈并解压了zImage。ARM32位内核也有类似的文件arch/arm/kernel/head.S。start_kernel()不针对特定的体系结构,所以这个函数留驻在内核的init/main.c中。start_kernel()可以说是Linux真正的main()函数。

从start_kernel()到PID1

内核的硬件清单:设备树和ACPI表

在引导时,内核须要硬件信息,不仅仅是已编译过的处理器类型。代码中的指令通过单独储存的配置数据进行扩展。有两种主要的数据储存方式:设备树device-tree和中级配置和电源插口(ACPI)表。内核通过读取这种文件了解每次启动时须要运行的硬件。

对于嵌入式设备,设备树是已安装硬件的清单。设备树只是一个与内核源代码同时编译的文件,一般与vmlinux一样坐落/boot目录中。要查看ARM设备上的设备树的内容,只需对名称与/boot/*.dtb匹配的文件执行binutils包中的strings命令即可,这儿dtb是指设备树二补码文件device-treebinary。其实,只需编辑构成它的类JSON的文件并重新运行随内核源代码提供的特殊dtc编译器即可更改设备树。其实设备树是一个静态文件,其文件路径一般由命令行引导程序传递给内核,但近些年来降低了一个设备树覆盖的功能,内核在启动后可以动态加载热拔插的附加设备。

x86系列和许多企业级的ARM64设备使用ACPI机制。与设备树不同的是,ACPI信息储存在内核在启动时通过访问板载ROM而创建的/sys/firmware/acpi/tables虚拟文件系统中。读取ACPI表的简单方式是使用acpica-tools包中的acpidump命令。诸如:

联想电脑笔记本的ACPI表都是为Windows2001设置的。

是的,你的Linux系统早已打算好用于Windows2001了,你要考虑安装吗?与设备树不同,ACPI具有方式和数据,而设备树更多地是一种硬件描述语言。ACPI方式在启动后仍处于活动状态。诸如,运行acpi_listen命令(在apcid包中),之后打开和关掉电脑机盖会发觉ACPI功能仍然在运行。暂时地和动态地覆盖ACPI表是可能的,而永久地改变它须要在引导时与BIOS菜单交互或刷新ROM。假如你遇见这么多麻烦,其实你应当安装coreboot,这是开源固件的取代品。

从start_kernel()到用户空间

init/main.c中的代码居然是可读的,并且有趣的是linux 开机启动设置,它依然在使用1991–1992年的LinusTorvalds的原始版权。在一个刚启动的系统上运行dmesg|head,其输出主要来始于此文件。第一个CPU注册到系统中,全局数据结构被初始化,但是调度程序、中断处理程序(IRQ)、定时器和控制台根据严格的次序逐一启动。在timekeeping_init()函数运行之前,所有的时间戳都是零。内核初始化的这部份是同步的,也就是说执行只发生在一个线程中,在最后一个完成并返回之前,没有任何函数会被执行。为此,虽然在两个系统之间,dmesg的输出也是完全可重复的,只要它们具有相同的设备树或ACPI表。Linux的行为如同在MCU上运行的RTOS(实时操作系统)一样,如QNX或VxWorks。此类情况持续存在于函数rest_init()中,该函数在中止时由start_kernel()调用。

linux 开机启动设置_开机启动设置怎么调_开机启动设置命令

初期的内核启动流程。

函数rest_init()形成了一个新进程以运行kernel_init(),并调用了do_initcalls()。用户可以通过将initcall_debug附加到内核命令行来监控initcalls,这样每运行一次initcall函数都会形成一个dmesg条目。initcalls会历经七个连续的级别:early、core、postcore、arch、subsys、fs、device和late。initcalls最为用户可见的部份是所有处理器外围设备的侦测和设置:总线、网络、存储和显示器等等,同时加载其内核模块。rest_init()也会在引导处理器上形成第二个线程,它首先运行cpu_idle(),之后等待调度器分配工作。

linux 开机启动设置_开机启动设置怎么调_开机启动设置命令

kernel_init()也可以设置对称多处理(SMP)结构。在较新的内核中,假如dmesg的输出中出现“BringingupsecondaryCPUs…”等字样linux下载,系统便使用了SMP。SMP通过“热拔插”CPU来进行,这意味着它用状态机来管理其生命周期,这些状态机在概念上类似于热拔插的U盘一样。内核的电源管理系统常常会使某个核core离线,之后按照须要将其唤起,便于在不忙的机器上反复调用同一段的CPU热拔插代码。观察电源管理系统调用CPU热拔插代码的BCC工具称为offcputime.py。

请注意,init/main.c中的代码在smp_init()运行时几乎已执行完毕:引导处理器早已完成了大部份一次性初始化操作,其它核无需重复。虽然这么,跨CPU的线程一直要在每位核上生成,以管理每位核的中断(IRQ)、工作队列、定时器和电源风波。比如,通过ps-opsr命令可以查看服务每位CPU上的线程的softirqs和workqueues。

#ps-opid,psr,comm$(pgrepksoftirqd)

PIDPSRCOMMAND

70ksoftirqd/0

161ksoftirqd/1

222ksoftirqd/2

283ksoftirqd/3

#ps-opid,psr,comm$(pgrepkworker)

PIDPSRCOMMAND

40kworker/0:0H

181kworker/1:0H

242kworker/2:0H

303kworker/3:0H

[…]

linux 开机启动设置_开机启动设置怎么调_开机启动设置命令

其中,PSR数组代表“处理器processor”。每位核还必须拥有自己的定时器和cpuhp热拔插处理程序。

这么用户空间是怎样启动的呢?在最后,kernel_init()找寻可以代表它执行init进程的initrd。若果没有找到,内核直接执行init本身。这么为何须要initrd呢?

初期的用户空间:谁规定要用initrd?

不仅设备树之外,在启动时可以提供给内核的另一个文件路径是initrd的路径。initrd一般坐落/boot目录中,与x86系统中的bzImage文件vmlinuz一样,或是与ARM系统中的uImage和设备树相同。用initramfs-tools-core软件包中的lsinitramfs工具可以列举initrd的内容。发行版的initrd方案包含了最小化的/bin、/sbin和/etc目录以及内核模块,还有/scripts中的一些文件。所有那些看上去都很熟悉,由于initrd大致上是一个简单的最小化Linux根文件系统。看似相像,虽然不然,由于坐落虚拟显存盘中的/bin和/sbin目录下的所有可执行文件几乎都是指向BusyBox二补码文件的符号链接,由此引起/bin和/sbin目录比glibc的小10倍。

假如要做的只是加载一些模块,之后在普通的根文件系统上启动init,为何还要创建一个initrd呢?想想一个加密的根文件系统,揭秘可能依赖于加载一个坐落根文件系统/lib/modules的内核模块,其实还有initrd中的。加密模块可能被静态地编译到内核中,而不是从文件加载,但有多种诱因不希望这样做。譬如,用模块静态编译内核可能会使其太大而不能适应储存空间,或则静态编译可能会违背软件许可条款。不出所料,储存、网络和人类输入设备(HID)驱动程序也可能存在于initrd中。initrd基本上包含了任何挂载根文件系统所必需的非内核代码。initrd也是用户储存自定义ACPI表代码的地方。

开机启动设置命令_开机启动设置怎么调_linux 开机启动设置

initrd.”/>

搜救模式的shell和自定义的initrd还是很有意思的。

initrd对测试文件系统和数据储存设备也很有用。将这种测试工具储存在initrd中,并从显存中运行测试,而不是从被测对象中运行。

最后,当init开始运行时红旗linux官网,系统就启动啦!因为第二个处理器如今在运行,机器早已成为我们所熟知和喜爱的异步、可占领、不可预测和高性能的生物。的确,ps-opid,psr,comm-p1很容易显示用户空间的init进程已不在引导处理器上运行了。

总结

Linux引导过程听上去似乎令人生畏,虽然是简单嵌入式设备上的软件数目也是这么。但换个角度来看,启动过程相当简单,由于启动中没有占领、RCU和竞争条件等扑朔迷蒙的复杂功能。只关注内核和PID1会忽视了引导程序和辅助处理器为运行内核执行的大量打算工作。其实内核在Linux程序中是独一无二的,但通过一些检测ELF文件的工具也可以了解其结构。学习一个正常的启动过程,可以帮助运维人员处理启动的故障。

要了解更多信息,请参阅AlisonChaiken的讲演——Linux:Thefirstsecond,已于1月22日至26日在旧金山召开。参见linux.conf.au。

谢谢AkkanaPeck的提议和见谅。

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复