LinuxGod

LinuxGod.net
Linux大神网——精选每一篇高品质的技术干货
  1. 首页
  2. 开源快讯
  3. 正文

开源软件最古老的笑话——阅读源代码-documenting

2023年1月9日 211点热度

(点击上方公众号,可快速关注)

编译:linux中国 /jessie-pang英文:Alison Chaiken

理解运转良好的系统对于处理不可避免的故障是最好的准备。

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

从某些方面看,启动过程非常简单。内核在单核上以单线程和同步状态启动,似乎可以理解。但内核本身是如何启动的呢?initrd(initial ramdisk)和引导程序bootloader具有哪些功能?还有,为什么以太网端口上的 LED 灯是常亮的呢?

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

启动的开始:OFF 状态局域网唤醒Wake-on-LAN

OFF 状态表示系统没有上电,没错吧?表面简单,其实不然。例如,如果系统启用了局域网唤醒机制(WOL),以太网指示灯将亮起。通过以下命令来检查是否是这种情况:

# sudo ethtool

其中 是网络接口的名字,比如eth0。(ethtool可以在同名的 Linux 软件包中找到。)如果输出中的Wake-on显示g,则远程主机可以通过发送魔法数据包MagicPacket来启动系统。如果您无意远程唤醒系统,也不希望其他人这样做,请在系统 BIOS 菜单中将 WOL 关闭,或者用以下方式:

# sudo ethtool -s wol d

响应魔法数据包的处理器可能是网络接口的一部分,也可能是底板管理控制器Baseboard Management Controller(BMC)。

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

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

IME 固件和系统管理模式System Management Mode(SMM)软件是基于 Minix 操作系统的,并运行在单独的平台控制器单元Platform Controller Hub上(LCTT 译注:即南桥芯片),而不是主 CPU 上。然后,SMM 启动位于主处理器上的通用可扩展固件接口Universal Extensible Firmware Interface(UEFI)软件,相关内容已被提及多次。Google 的 Coreboot 小组已经启动了一个雄心勃勃的非扩展性缩减版固件Non-Extensible Reduced Firmware(NERF)项目,其目的不仅是要取代 UEFI,还要取代早期的 Linux 用户空间组件,如 systemd。在我们等待这些新成果的同时,Linux 用户现在就可以从 Purism、System76 或 Dell 等处购买禁用了 IME的笔记本电脑,另外带有 ARM 64 位处理器笔记本电脑还是值得期待的。

引导程序

arm linux 内核启动流程_arm linux内核_arm linux启动流程

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

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

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

# git clone git://git.denx.de/u-boot; cd u-boot

# make ARCH=sandbox defconfig

# 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

# file vmlinux

vmlinux: ELF64-bit LSB executable,x86-64,version1(SYSV),statically

linked,stripped

内核是一个可执行与可链接格式 Executable and Linking Format(ELF)的二进制文件,就像 Linux 的用户空间程序一样。这意味着我们可以使用binutils包中的命令,如readelf来检查它。比较一下输出,例如:

# readelf -S /bin/date

# readelf -S vmlinux

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

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

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

# file /bin/date

/bin/date: ELF64-bit LSB shared object,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 查看。内核显然没有解释器,必须自我配置linux怎么查看系统版本,这是怎么做到的呢?

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

从 start_kernel() 到 PID 1内核的硬件清单:设备树和 ACPI 表

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

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

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

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

是的,你的 Linux 系统已经准备好用于 Windows 2001 了,你要考虑安装吗?与设备树不同,ACPI 具有方法和数据,而设备树更多地是一种硬件描述语言。ACPI 方法在启动后仍处于活动状态。例如,运行acpi_listen命令(在apcid包中),然后打开和关闭笔记本机盖会发现 ACPI 功能一直在运行。暂时地和动态地覆盖 ACPI 表是可能的,而永久地改变它需要在引导时与 BIOS 菜单交互或刷新 ROM。如果你遇到那么多麻烦,也许你应该安装 coreboot,这是开源固件的替代品。

从 start_kernel() 到用户空间

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

早期的内核启动流程。

函数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(),然后等待调度器分配工作。

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

arm linux启动流程_arm linux 内核启动流程_arm linux内核

请注意,init/main.c中的代码在smp_init()运行时几乎已执行完毕:引导处理器已经完成了大部分一次性初始化操作,其它核无需重复。尽管如此,跨 CPU 的线程仍然要在每个核上生成,以管理每个核的中断(IRQ)、工作队列、定时器和电源事件。例如,通过ps -o psr命令可以查看服务每个 CPU 上的线程的 softirqs 和 workqueues。

# ps -o pid,psr,comm $(pgrep ksoftirqd)

PID PSR COMMAND

7 0ksoftirqd/0

16 1ksoftirqd/1

22 2ksoftirqd/2

28 3ksoftirqd/3

# ps -o pid,psr,comm $(pgrep kworker)

PIDPSR COMMAND

4 0kworker/0:0H

18 1kworker/1:0H

24 2kworker/2:0H

30 3kworker/3:0H

[...]

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

那么用户空间是如何启动的呢?在最后,kernel_init()寻找可以代表它执行init进程的initrd。如果没有找到红帽linux,内核直接执行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表代码的地方。

arm linux内核_arm linux 内核启动流程_arm linux启动流程

initrd." />

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

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

最后,当init开始运行时,系统就启动啦!由于第二个处理器现在在运行,机器已经成为我们所熟知和喜爱的异步、可抢占、不可预测和高性能的生物。的确,ps -o pid,psr,comm -p 1很容易显示用户空间的init进程已不在引导处理器上运行了。

总结

Linux 引导过程听起来或许令人生畏,即使是简单嵌入式设备上的软件数量也是如此。但换个角度来看,启动过程相当简单,因为启动中没有抢占、RCU 和竞争条件等扑朔迷离的复杂功能。只关注内核和 PID 1 会忽略了引导程序和辅助处理器为运行内核执行的大量准备工作。虽然内核在 Linux 程序中是独一无二的,但通过一些检查 ELF 文件的工具也可以了解其结构。学习一个正常的启动过程,可以帮助运维人员处理启动的故障。

要了解更多信息,请参阅 Alison Chaiken 的演讲——Linux: The first second,已于 1 月 22 日至 26 日在悉尼举行。参见linux.conf.au。

感谢Akkana Peck的提议和指正。

看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: linux服务器 u-boot 引导程序 热插拔 笔记本电脑
最后更新:2023年1月9日

Linux大神网

每日更新,欢迎收藏♥ 不积跬步无以至千里,加油,共勉。

点赞
< 上一篇
下一篇 >

Linux大神网

每日更新,欢迎收藏♥
不积跬步无以至千里,加油,共勉。

最新 热点 随机
最新 热点 随机
ARM-Linux应用开发和单片机开发的不同ARM的应用 Linux下如何查看内核信息、发行版信息的信息? 《Linux内核编程》入门篇:降维为繁 Linux网卡驱动安装及配置指南 大专计算机有哪些课程,专业的1对1答疑! SamsungNote2Verizoni605onAndroid在Android系统上安装linux发行版 MPV的高级应用——Ubuntu最佳应用列表里的应用 手机上可以安装一套完整的Linux系统吗? Ubuntu上安装SMplayer.6.10-PPA源在CentOS5.5下安装使用 centos启动图形界面的方法.x1,关闭界面 Linux下进程与线程概念重构 Linux中的十大开源播放器C编写 多进程和多线程的区别,你知道几个? Linux发行版中可用的7款最佳开源视频播放器 Linux系统安装网卡驱动的具体操作流程进行说明 如何在Linux上安装视频播放器installvlc 晚上暴露于蓝光,睡眠质量大打折扣 如何安装便携式WiFi驱动程序?360wifi驱动的教程 CentOS云服务器搭建网站和CentOS搭建DNS解析服务 如何在linux上创建一个用户,减少不必要的沟通成本
嵌入式Linux操作系统学习规划+LINUX路线,主攻江苏电信天翼校园客户端故障指引及解决办法(101)英特尔GMAGMA950显卡驱动程序/WIN8/8.1电信校园网宽带用USB数据线共享给电脑无线上网国防科大开源操作系统:它只是一个吉祥的象征10个常用Linux文本查看命令及其详细说明和使用示例Linux嵌入式系统内核裁剪与定制方法的介绍情况淘宝教育热卖C语言编程开发C++程序设计零基础入门课程从CPU、内存、硬盘、显卡等这些方面安装Linux系统的最低配置Linux通过chkconfig设置开机启动服务创建的几种常见方式(技术分析)Linux多线程的使用与操作系统的区别通常rar命令由一个主命令加若干选项(可选)构成RedHatLinux中自动运行程序中的应用linux 读写文件 关于Linux内核的神秘面纱,你知道几个?使用wget实用程序的有用命令行工具的使用怎么设置linux开机项自启动?方式是怎样的?嵌入式Linux应用层与驱动层要想学习关于Linux内核的交叉编译步骤和方法:步骤、方法STM32嵌入式linux开发流程及应用程序分析-STMlinux下有哪些文件在介绍lsof命令实用用法介绍?
TeXLive和MiKTeX两大主要发行版的镜像配置方法 嵌入式Linux内核源码目录树下的各目录文件作用 Linux环境下安装虚拟机的步骤及注意事项!! Linux基础入门(VMWare中CentOS7配置yum)目录之间的加载顺序 学嵌入式Linux系统的看过来了,你知道吗? 不同服务器启动不同端口一样修改教程 C语言教程-Qt.12安装教程05-27 瑞星ESM防病毒终端安全防护系统查杀Sfile勒索软件 如何在Linux系统中实现开机自启动呢? 环境高级编程环境 阿里云>社区>主题地图S>查看存储推荐 (Linux基础知识)Linux中端口号被哪个进程占用 Java面试题:线程的运行状态(2) 8个技巧让你成为一个超强的Linux终端用户 Linux内核页表管理中那些鲜为人知的作用是什么?(图) Linux内核空间的虚拟地址映射空间_TOP~4GB 比特币挖矿程序 linux CalvinStandridgeStandridge分校Ho理学硕士研究生加州大学分校研究生申请 转到程序的开始(地址0)Demo程序 (技术分析)Linux多线程的使用与操作系统的区别 (知识点)Linux文件权限详解:执行权限
标签聚合
操作 linux系统 命令 虚拟机 应用 文件目录 linux服务器 内核 文件 软件
书籍
课程
技术群
技术干货大合集↓
  • 2023年10月 / 8篇
  • 2023年9月 / 90篇
  • 2023年8月 / 93篇
  • 2023年7月 / 94篇
  • 2023年6月 / 90篇
  • 2023年5月 / 93篇
  • 2023年4月 / 90篇
  • 2023年3月 / 129篇
  • 2023年2月 / 84篇
  • 2023年1月 / 161篇
  • 2022年12月 / 187篇
  • 2022年11月 / 76篇
友情链接:

Linux书籍 | Linux命令 | Linux系统 | RHCE红帽认证 | Linux软件 | Linux教程 | CentOS系统 | Linux内核 | Linux服务器 | Linux大神 | IT资源

COPYRIGHT © 2023 linuxgod.net ALL RIGHTS RESERVED.