当我们面对一台运行着 Ubuntu Server 的生产环境服务器时,很少会去思考它是如何从冰冷的硬件变为一个提供强大网络服务的有机整体。按下电源键后的几十秒内,背后发生了一系列精密而复杂的连锁反应。理解这个过程,不仅是系统管理员和运维工程师的基本功,更是我们进行系统故障排除、性能优化和安全加固的基石。
本文将深入剖析 Ubuntu Server 的完整引导过程。我们将穿越硬件、固件、引导加载程序、内核与用户空间的层层关卡,详细探索每个阶段的任务、机制与核心组件。无论您是初学 Linux 的爱好者,还是经验丰富的系统架构师,相信都能从这篇详尽的指南中获益。
任何操作系统的引导都始于硬件层面。当我们按下服务器的电源按钮时,一场精心编排的启动交响曲正式拉开帷幕。
1.1 上电与电源就绪信号
按下电源按钮并非直接给整个主板供电,而是向电源供应单元(PSU)发送一个信号。PSU 随后进行自检,确保各路输出电压(+12V, +5V, +3.3V, -12V, +5VSB)都稳定在规范范围内。一旦就绪,PSU 会向主板发送一个 Power Good
信号。
1.2 CPU 复位与起点
主板上的芯片组(如 Intel 的 PCH 或 AMD 的 FCH)收到 Power Good
信号后,会释放 CPU 的复位引脚(RESET#)。CPU 结束复位状态,但其内部所有寄存器(除了代码段寄存器 CS 和指令指针寄存器 IP/EIP/RIP)都处于一个不确定的状态。最关键的是,CS:IP 寄存器对被硬连线指向一个特定的内存地址,这对于所有 x86/x86-64 CPU 都是相同的:0xFFFFFFF0。这个地址位于主板 ROM 的映射区域,也就是我们常说的 BIOS 或 UEFI 固件的入口点。
1.3 POST:开机自检
CPU 开始从固件中执行代码,第一项重大任务就是开机自检(Power-On Self-Test, POST)。POST 过程由固件代码实现,主要包括:
如果 POST 过程中遇到关键错误(如无内存、无显卡),固件会通过蜂鸣器代码或主板诊断灯的形式报告错误,引导过程会中止。
现代服务器主要采用 UEFI 固件,但传统 BIOS 模式仍然存在。理解两者的区别对理解引导过程至关重要。
2.1 传统 BIOS(Basic Input/Output System)
BIOS 是存在已久的标准,其工作方式较为简单:
0x55AA
,否则 BIOS 会认为该设备不可引导,转而尝试下一个设备。2.2 UEFI(Unified Extensible Firmware Interface)
UEFI 是现代固件接口,旨在克服 BIOS 的诸多限制:
.efi
的引导程序文件(如 grubx64.efi
)。UEFI 固件直接加载并执行这个 .efi
应用程序。2.3 UEFI 启动流程详解
UEFI 的启动过程比 BIOS 更为结构化:
Power Good
信号,初始化临时内存(Cache as RAM),验证后续阶段代码的完整性。ExitBootServices()
,告知固件它已经准备好接管系统。此后,只有运行时服务(如获取时间、NVRAM 访问)仍然可用,引导服务被完全禁用。UEFI 固件通过启动管理器(Boot Manager)来选择要加载的 EFI 应用程序。启动配置数据存储在 NVRAM 中,可以通过 efibootmgr
命令在 Linux 中查看和修改。
# 示例:查看 UEFI 启动项
$ sudo efibootmgr -v
BootCurrent: 0000
Timeout: 1 seconds
BootOrder: 0000,0001,0002
Boot0000* ubuntu HD(1,GPT,12345678-1234-1234-1234-123456789abc,0x800,0x100000)/File(\EFI\UBUNTU\SHIMX64.EFI)
Boot0001* UEFI: CD/DVD Drive BBS(129,,0x0)
Boot0002* UEFI: Network Card BBS(130,,0x0)
固件找到了引导程序并将其加载到内存后,控制权就移交给了引导加载程序(Bootloader)。在 Ubuntu Server 中,这个角色几乎毫无例外地由 GRUB2(GRand Unified Bootloader, version 2) 扮演。
GRUB2 是一个强大、模块化、跨平台的引导程序,是早期 GRUB Legacy 的彻底重写。它的核心任务是:
vmlinuz
)到内存。initrd
/initramfs
)到内存。root=
)。3.1 GRUB2 的模块化设计
GRUB2 本身非常精简。它的核心镜像文件(core.img
或 .efi
文件)只包含最基本的功能:设备驱动、文件系统驱动、环境变量处理等都是以模块(.mod) 的形式存在,按需动态加载。这种设计使得 GRUB2 可以保持小巧,同时支持海量的硬件和文件系统。
3.2 GRUB2 的安装位置
boot.img
: 安装在 MBR 的前 440 字节。它的唯一作用是从本地磁盘加载紧接着 MBR 的 core.img
。core.img
: 安装在 MBR 之后的扇区(通常称为 "MBR gap")。它包含了足够的代码来访问 /boot/grub
目录,并加载其中的模块。由于 core.img
较大,它可能会侵占第一个分部的空间,这也是为什么通常建议在磁盘开头留出一个未分区的空间。/boot/grub
: 包含所有的模块、配置文件 (grub.cfg
)、主题等。grubx64.efi
(或 shimx64.efi
): 这是一个独立的 EFI 应用程序,存放在 EFI 系统分区 (ESP) 的 \EFI\ubuntu\
目录下。它已经将 core.img
和必要的模块静态链接在一起,因此可以直接由 UEFI 固件加载执行。之后,它会读取 ESP 或其它分区上的 /boot/grub
目录来加载更多模块和配置。GRUB2 的启动是一个多阶段的过程,尤其在 BIOS 模式下更为明显。
4.1 BIOS/MBR 模式下的阶段
boot.img
0x7C00
并执行。boot.img
的唯一任务是加载 core.img
的第一个扇区。它通过 BIOS 中断调用(INT 13h)直接读取硬盘,因为它还不理解文件系统。boot.img
的大小被严格限制在 440 字节。core.img
core.img
通常由 diskboot.img
、kernel.img
以及一些核心模块(如 biosdisk.mod
, part_msdos.mod
, ext2.mod
) 拼接而成。diskboot.img
负责加载 core.img
的剩余部分。core.img
最终的任务是加载并执行 kernel.img
,并将控制权交给它。/boot/grub
目录kernel.img
是 GRUB2 的“内核”,它初始化控制台、动态内存管理(堆)、设备驱动、模块加载器等。/boot/grub
目录加载 normal.mod
模块。normal.mod
执行 normal
命令,该命令读取并解析 /boot/grub/grub.cfg
配置文件。grub.cfg
的配置,GRUB2 会呈现一个漂亮的启动菜单,并等待用户交互或超时。4.2 UEFI 模式下的简化流程
UEFI 模式大大简化了这个过程:
grubx64.efi
(它是一个完整的 PE/COFF 格式的可执行文件)到内存并执行。grubx64.efi
内部已经包含了相当于 core.img
的功能和核心模块。/boot/grub/grub.cfg
文件。grub.cfg
与内核加载/boot/grub/grub.cfg
是 GRUB2 的核心配置文件,但它通常不是由管理员直接编辑的,而是由 update-grub
命令(背后是 grub-mkconfig
)根据 /etc/default/grub
和 /etc/grub.d/
目录下的脚本自动生成的。
5.1 grub.cfg
结构剖析
一个典型的 grub.cfg
包含以下部分:
menuentry
: 定义了一个启动菜单项。insmod
: 加载所需的模块(如文件系统 ext2
、压缩 gzio
)。set root
: 设置根设备,指定 (hd0, gpt2)
表示第一个硬盘的第二个 GPT 分区。search
: 更智能地搜索包含指定 UUID 的分区并设置为根设备。linux
: 最关键的命令。指定要加载的 Linux 内核映像文件路径,并传递内核启动参数。root=UUID=...
: 告诉内核根文件系统所在分区的 UUID。ro
: 以只读模式挂载根文件系统(初始阶段出于安全考虑)。quiet splash
: 控制内核启动时的输出级别和是否显示闪烁标志。initrd
: 指定要加载的初始内存磁盘(initramfs)映像文件路径。5.2 内核加载的底层细节
当用户选择一个菜单项后,GRUB2 执行 linux
和 initrd
命令:
linux
命令):vmlinuz
文件。boot_params
(对于 x86架构),其中包含了从 grub.cfg
中解析来的命令行参数、内存映射图(e820 map)、硬件信息等。initrd
命令):initrd.img
文件加载到内存中的另一块区域。initrd.img
是一个 CPIO 归档文件,通常还用 gzip 压缩过。它的内容将在后续阶段被内核解压并挂载。boot
命令(或超时自动执行)。boot_params
结构体的地址传递给内核。现在,CPU 开始执行 Linux 内核的代码。这是从“引导”到“操作系统”的转折点。内核需要解压自己,初始化关键子系统,并最终切换到真正的根文件系统。
内核映像的入口点是一小段实模式(16位)代码。
0x100000
,即 1MB 处)。这就是所谓的 "解压内核 above, 放置内核 below"。startup_32
和 start_kernel
)解压后的内核代码开始执行,其初始化过程是一个从架构相关代码逐步过渡到架构无关代码的旅程。
7.1 架构相关初始化 (startup_32
)
在 x86 上,这是 arch/x86/boot/compressed/head_64.S
中的 startup_32
(对于 32 位内核)或 startup_64
(对于 64 位内核)。主要任务:
0xffffffff81000000
)。boot_params
结构体中提取信息,如内存布局、命令行参数等。start_kernel()
。7.2 架构无关初始化 (start_kernel
)
init/main.c
中的 start_kernel()
函数是所有 Linux 架构的共通入口点。它是一个巨大的函数,按顺序初始化内核的几乎所有子系统:
trap_init()
, sched_init()
。mm_init()
:初始化伙伴系统分配器(Buddy System),这是物理内存管理的核心。sched_init()
。parse_early_param()
, parse_args()
:解析从 GRUB 传来的 root=
, ro
, quiet
等参数。console_init()
:此时内核才能通过 printk
输出信息到屏幕。fork_init()
。vfs_caches_init()
。rest_init
:在 start_kernel
的最后,它调用 rest_init()
,标志着核心初始化工作的完成。7.3 rest_init()
与第一个进程
rest_init()
的工作至关重要:
kernel_init
:这个线程的任务就是启动用户空间的第一个进程。但在那之前,它必须先处理 initramfs
。kthreadd
:这是所有其它内核线程的父进程(pid=2)。start_kernel
返回:start_kernel
函数不会返回。执行 rest_init
的上下文(可以看作是第 0 号进程,即 idle 进程)最终会调用 cpu_startup_entry(CPUHP_ONLINE)
,进入 idle 循环,当系统中没有其它任务可运行时,CPU 就执行它的 idle 函数。此时,调度器已经开始工作,kernel_init
线程(pid=1)被调度运行。
kernel_init
线程的首要任务是处理 initramfs
。
8.1 为什么需要 initramfs?
在现代 Linux 系统中,根文件系统(/
)可能位于复杂的存储设备上:
这些设备的驱动可能没有直接编译进内核,而是以内核模块的形式存在。而内核模块本身又存储在根文件系统的 /lib/modules/
目录下。这就形成了一个“先有鸡还是先有蛋”的悖论:需要访问根文件系统来加载模块,但又需要模块来访问根文件系统。
initramfs
(Initial RAM File System) 就是为了解决这个悖论而生的。它是一个临时的根文件系统,被加载到内存中。它包含了在挂载真实根文件系统之前所必需的工具、脚本和内核模块。
8.2 initramfs 的组成与解压
initrd.img
是一个 gzip 压缩的 CPIO 归档文件。CPIO 是一种简单的文件归档格式。populate_rootfs
函数)就会将其解压到一个基于 tmpfs
的内存文件系统中,并将其挂载为最初的根文件系统(/
)。8.3 /init
脚本——initramfs 的灵魂
解压 initramfs 后,内核会在其根目录下寻找一个名为 /init
的文件(如果不存在,则寻找 /linuxrc
)。这个文件必须是可执行的,它将成为用户空间的第一个进程(pid=1 的进程,由 kernel_init
线程exec执行)。
在 Ubuntu Server 的 initramfs 中,/init
是一个复杂的 Shell 脚本(通常是 initramfs-tools
包生成的),它的核心使命是:
/dev
, /proc
, /sys
。initramfs
的 /conf/
目录下,或通过 lvm2
, cryptsetup
等工具动态探测。grub.cfg
中传递的 root=UUID=...
参数,找到对应的物理设备。这可能涉及解密(cryptsetup
)、激活 LVM 卷组(vgchange
)、组装 RAID 阵列等。initramfs
下的一个目录(如 /root
)。pivot_root
系统调用,将真正的根文件系统 (/root
) 切换为新的根 (/
)。/init
脚本通过 exec
系统调用,执行真实根文件系统上的 /sbin/init
(通常是 systemd
或 upstart
,在现代 Ubuntu 中是 systemd
)。至此,/sbin/init
替代了 initramfs
的 /init
,成为新的 pid=1 进程。8.4 调试 initramfs
如果系统卡在 initramfs 阶段,通常会掉入一个 BusyBox 提供的救急 shell。这时可以使用以下命令进行调试:
ls /dev/disk/by-uuid/
: 查看所有设备的 UUID。blkid
: 查看块设备信息。cryptsetup luksOpen /dev/sda5 myroot
: 打开加密的根设备。lvm vgchange -ay
: 激活 LVM 卷组。mount /dev/mapper/ubuntu--vg-root /root
: 尝试挂载根文件系统。exit
: 退出 shell,继续启动过程(有时会有效)。控制权从内核移交到用户空间的第一个进程 /sbin/init
,这标志着用户空间初始化的开始。在现代 Ubuntu Server 中,/sbin/init
是指向 /lib/systemd/systemd
的符号链接。systemd 就此接管系统,成为所有进程的父进程(pid=1)。
systemd 不仅仅是一个 init 程序,它是一个庞大的系统和服务管理器套件。其设计目标包括:
systemctl
命令统一管理所有系统服务。journald
收集所有内核和用户空间的日志。systemd 的启动过程是一个按目标(target)推进的链条。目标可以看作是运行级别(runlevel)的现代化替代品,但它更灵活,可以并行化。
10.1 初始目标:default.target
systemd 启动后,它的核心任务是到达默认目标(default.target)。这通常是一个符号链接,指向 graphical.target
(桌面版)或 multi-user.target
(服务器版)。
# 查看默认目标
$ systemctl get-default
multi-user.target
10.2 启动链条与目标依赖
systemd 通过目标的依赖关系来构建一个启动链条。你可以使用 systemctl list-dependencies
来查看一个目标的依赖树。
$ systemctl list-dependencies multi-user.target
multi-user.target
● ├─acpid.path
● ├─atd.service
● ├─cron.service
● ├─dbus.service
● ├─getty.target
● │ ├─getty@tty1.service
● │ ├─...
● ├─remote-fs.target
● │ ├─nfs-client.target
● │ │ └─nfs-blabla.service
● │ └─rpc_pipefs.target
● ├─systemd-ask-password-wall.path
● ├─systemd-logind.service
● ├─systemd-user-sessions.service
● ├─ufw.service
● └─...
这个依赖树展示了 multi-user.target
所依赖的所有其他目标和服务。systemd 会解析这些依赖,并最大限度地并行启动它们。
10.3 关键的启动阶段(Targets)
启动过程会依次经过几个关键的目标:
local-fs-pre.target
: 挂载本地文件系统之前的准备工作。local-fs.target
: 挂载所有本地文件系统(除了 /home
和用户相关的)。/etc/fstab
中列出的文件系统会在这里被挂载。systemd 会生成对应的 .mount
单元来自动处理 fstab
。swap.target
: 激活所有交换分区。cryptsetup.target
: 等待任何加密设备设置完成(如果已经在 initramfs 中解密,则很快通过)。remote-fs.target
: 挂载网络文件系统(NFS)。systemd-udevd.service
: 用户空间设备管理器。它处理所有内核通过 uevent
发出的事件(如设备插拔),加载相应的内核模块,并创建设备节点。它很早就被启动。sysinit.target
: 一个重要的目标,它代表“系统初始化”完成。它依赖于所有基础的系统组件,如 local-fs.target
, swap.target
, udev
等。basic.target
: 在 sysinit.target
之后启动,代表“基本系统”就绪。multi-user.target
: 最终目标之一。所有系统服务(如 SSH, Cron, NTP, Web Server)都在这个目标下启动。系统进入多用户模式,但无图形界面。graphical.target
: 在 multi-user.target
的基础上启动图形界面(如果安装了)。10.4 服务(Service)单元的启动
每个服务(如 ssh.service
, nginx.service
)都是一个单元文件(.service
),存放在 /lib/systemd/system/
或 /etc/systemd/system/
中。systemd 根据依赖关系和并行化原则启动它们。
一个服务单元文件定义了如何启动、停止和管理该服务:
# 示例:简化的 ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
[Install]
WantedBy=multi-user.target
Alias=sshd.service
[Unit]
段:描述服务和依赖关系。After=
表示本服务在 network.target
之后启动。[Service]
段:定义执行参数。ExecStart=
是启动服务的命令。[Install]
段:定义如何“安装”这个服务,即当用户 systemctl enable ssh
时,该服务会被添加到 multi-user.target
的 Wants
列表中,从而在启动时自动运行。当系统服务基本就绪后,getty.target
会被激活。它启动一系列 getty@tty1.service
之类的服务。
login:
提示符。getty
会 exec 到 login
程序,由它来验证密码。login
程序会根据 /etc/passwd
的配置,启动用户的登录 shell(如 /bin/bash
)。.bashrc
和系统级的 /etc/profile
等初始化脚本。最终,用户获得一个可交互的命令行提示符。对于服务器,SSH 服务(ssh.service
) 是更常见的登录方式。它的工作原理与 getty 类似,但通过网络进行认证和会话建立。
在 UEFI 安全启动(Secure Boot) enabled 的系统中,固件只会加载被数字签名的 EFI 应用程序。由于 GRUB2 和 Linux 内核是开源软件,频繁变化,让微软为其签名是不现实的。
解决方案是使用一个中间人:shim。
shimx64.efi
)。它的唯一使命是加载另一个 EFI 应用程序,但会先验证其签名。不过,它信任由 Canonical(Ubuntu 母公司)签名的证书。grubx64.efi
) 由 Canonical 签名。shim 验证其签名后,会加载它。这样,就构成了一条信任链:微软 -> Canonical -> GRUB2 -> Linux Kernel。
理解引导过程是排除故障的关键。以下是常见问题及解决思路:
13.1 GRUB2 问题
core.img
损坏,或 grub.cfg
丢失/配置错误。13.2 内核 Panic
/init
。root=UUID=
参数是否正确。sudo update-initramfs -u -k all
。13.3 systemd 目标失败
systemctl status <service-name>
查看失败服务的状态和日志。journalctl -xe
查看详细的系统日志。systemctl isolate multi-user.target
尝试切换到多用户目标,看是否有更多错误信息。systemctl default
或 systemctl rescue
进入救援模式进行修复。13.4 文件系统错误
init=/bin/bash
进入单用户 shell,然后运行 fsck -y /dev/sda1
(根据实际情况调整设备名)。systemd-analyze
系列命令。systemd-analyze time # 显示总启动时间
systemd-analyze blame # 显示每个服务的启动耗时
systemd-analyze critical-chain # 显示关键路径上的服务耗时
systemd-analyze plot > boot.svg # 生成详细的启动时序图sudo systemctl disable <service-name>
。/etc/modprobe.d/blacklist.conf
。initramfs
只包含必需的模块和工具,避免臃肿。/etc/fstab
中的 pass
值设为 0,避免不必要的 fsck。systemd-boot
(仅限 UEFI),它比 GRUB2 更轻量。Ubuntu Server 的引导过程是一场从硬件到软件、从底层到高层的精妙舞蹈。它经历了:
/init
。每一个环节都承上启下,不可或缺。深入理解这个过程,不仅能让我们在系统出现问题时从容应对、精准定位,更能让我们从整体上把握 Linux 操作系统的运作机理,从而更好地进行系统定制、优化和安全加固。这台无声的交响曲,每天都在全世界的服务器上奏响,支撑着现代数字世界的运转。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。