Linux内核安装与启动
2013-12-21 14:09:06 阿炯

当按下电源键的那一刻,到系统桌面或者命令行界面出现,这中间究竟发生了什么?是什么在背后默默运作,让 Linux 系统能够顺利启动并为众所用?这个问题的答案,就藏在 Linux 内核的启动过程之中。为什么 Linux 系统能够在不同的硬件设备上稳定运行?为什么它能够高效地管理系统资源?这些问题的答案,都与 Linux 内核的启动过程紧密相连。当深入了解 Linux 内核的启动过程,不仅能够明白系统是如何从无到有地构建起来,还能掌握系统底层的运行机制,从而在系统出现问题时,能够更加准确地进行调试和优化。今天就一起踏上探索 Linux 内核启动过程的奇妙之旅,揭开它神秘的面纱,看看那些在幕后默默工作的代码和机制,究竟是如何协同合作,让 Linux 系统得以顺利启动并提供一个强大而稳定的运行环境。


一、硬件初始化:系统启动的前奏

当按下计算机的电源按钮,一场精密而复杂的启动之旅便悄然开启。在这个过程中,硬件初始化是至关重要的第一步,它为后续 Linux 内核的顺利启动奠定了坚实的基础。而 BIOS/UEFI 和引导加载程序在其中扮演着不可或缺的角色。启动顺序:
1.BIOS或启动固件加载并运行引导装载程序
2.引导装载程序在磁盘上找到内核映像并装载到内存中启动
3.内核初始化设备及其初始程序
4.内核挂载root文件系统
5.内核用PID 1 来运行init,用户空间此时开始启动
6.init启动其他系统进程
7.init通常在最后启动一个用于用户登录的进程

1.1BIOS/UEFI:硬件的 “把关者”
BIOS(Basic Input/Output System,基本输入输出系统),是计算机启动时最先运行的一段固化在主板 ROM 芯片中的程序。它就像是一位严谨的 “硬件管家”,在计算机启动初期,承担着硬件自检和初始化的重要职责。BIOS 会逐一检查 CPU、内存、硬盘、显卡等硬件设备,确保其都能正常工作,并为这些设备提供必要的初始化参数,就像为一场演出的各个演员安排好出场顺序和基本动作一样。完成硬件检查后,BIOS 会按照预设的启动顺序,寻找可引导的设备,比如硬盘、光盘或者 U 盘 ,并将系统的控制权传递给该设备上的引导程序,从而拉开操作系统启动的序幕。

随着计算机技术的不断发展,UEFI(Unified Extensible Firmware Interface,统一可扩展固件接口)逐渐崭露头角,成为 BIOS 的继任者。UEFI 采用了更加现代化的设计理念,支持 32 位和 64 位模式,拥有更大的内存地址空间,还提供了图形用户界面和网络启动功能,为用户带来了更加便捷和高效的使用体验。在启动方式上,UEFI 支持 GPT(GUID Partition Table)分区表,突破了 BIOS 使用 MBR(Master Boot Record)分区方式对磁盘大小和分区数量的限制,使得人们能够使用更大容量的硬盘和创建更多的分区。UEFI 还引入了安全引导功能,通过数字签名验证启动程序的真实性,有效抵御了病毒和恶意软件的攻击,为系统的安全性提供了更可靠的保障。

简单来说,BIOS 像是一位坚守传统的老管家,虽然经验丰富,但在面对一些新的需求时,可能会稍显力不从心;而 UEFI 则像是一位年轻有为的新管家,不仅继承了老管家的基本职责,还带来了许多创新的理念和方法,能够更好地适应现代计算机技术的发展需求。在如今的计算机市场中,UEFI 正逐渐取代 BIOS,成为主流的固件接口,但 BIOS 在一些旧设备中仍然发挥着作用,见证着计算机技术的发展历程。

1.2引导加载程序:连接硬件与内核的桥梁
在 BIOS/UEFI 完成硬件初始化并找到可引导设备后,引导加载程序就接过了“接力棒”,成为连接硬件与内核的关键桥梁。引导加载程序的主要任务是从存储设备中读取内核镜像,并将其加载到内存中,同时为内核传递必要的启动参数,确保内核能够顺利启动并运行。

以 GRUB2(GRand Unified Bootloader version 2)为例,它是目前大多数 Linux 发行版默认使用的引导加载程序,功能强大且灵活。GRUB2 采用了模块化设计,使得其核心更加精炼,能够支持多种文件系统和设备,包括常见的 ext4、NTFS 等文件系统以及 USB、SATA 等设备,这使得它在不同的硬件环境中都能游刃有余地工作。当计算机启动时,GRUB2 会首先读取其配置文件,通常位于/boot/grub2/grub.cfg 。这个配置文件中包含了丰富的信息,如系统中安装的各个操作系统的位置、内核镜像的路径以及传递给内核的启动参数等。通过解析这些配置信息,GRUB2 能够准确地找到内核镜像,并将其加载到内存中指定的位置。

在加载内核镜像的过程中,GRUB2 还会根据配置文件中的设置,为内核传递各种启动参数。这些参数就像是给内核下达的“指令”,告诉内核如何初始化硬件设备、挂载文件系统以及设置系统运行的各种环境参数等。root=/dev/sda1这个参数会告诉内核根文件系统所在的设备是/dev/sda1;ro参数表示以只读方式挂载根文件系统,确保在系统启动初期文件系统的完整性和安全性。通过这些启动参数的传递,内核能够在启动时正确地识别和配置硬件环境,为后续的系统运行做好充分准备。

可以说,引导加载程序就像是一位经验丰富的“引航员”,在硬件与内核之间搭建起了一座沟通的桥梁,确保内核能够在正确的时间、以正确的方式加载到内存中,并顺利启动运行,为整个 Linux 系统的稳定运行奠定了坚实的基础。

二、内核加载与初始化:系统核心的启动

在完成硬件初始化并由引导加载程序将内核镜像加载到内存后,Linux 内核正式开始启动,进入内核加载与初始化阶段。这一阶段是整个系统启动过程的核心,它涉及到内核的解压、核心初始化、加载 initramfs 以及挂载根文件系统等一系列关键步骤,每一步都紧密相连,共同构建起系统运行的基础。

2.1内核解压与核心初始化
当引导加载程序将内核镜像成功加载到内存后,内核的启动之旅才刚刚开始。此时,内核镜像可能是以压缩形式存在的,这就需要进行解压操作,就像打开一个精心包装的礼物,才能看到里面真正的宝贝。以常见的 zImage 镜像文件为例,它是经过 gzip 压缩的内核镜像。在启动时,会首先调用解压程序,将压缩的内核镜像解压到内存中的指定位置。这个过程就像是把一本被压缩打包的书籍重新展开,以便能够正常阅读其中的内容。

内核解压完成后,紧接着就进入核心初始化阶段。这一阶段,内核就像是一个刚刚苏醒的巨人,开始有条不紊地对自身进行初始化设置。它会初始化一系列关键的子系统,如进程管理子系统、内存管理子系统、设备驱动子系统等,这些子系统就像是巨人身体的各个重要器官,各自承担着独特而关键的职责。

进程管理子系统负责创建、调度和终止进程,就像一个繁忙的交通警察,指挥着系统中众多进程的运行,确保它们能够有序地共享 CPU 资源,避免出现混乱和冲突。内存管理子系统则如同一个精打细算的管家,负责管理系统的物理内存和虚拟内存,为进程分配合理的内存空间,确保内存的高效利用,避免内存泄漏和浪费,保障系统运行的稳定性和高效性。设备驱动子系统就像是各种设备的翻译官,负责与硬件设备进行交互,为设备提供统一的接口,使得应用程序可以方便地访问硬件设备,无论是硬盘、显卡还是网卡等设备,都能通过设备驱动子系统与内核进行顺畅的沟通。

内核还会初始化系统调用接口,这是用户空间与内核空间进行交互的重要通道,应用程序通过系统调用接口向内核发出各种请求,如文件读写、进程创建等,内核则根据这些请求执行相应的操作,并将结果返回给应用程序。这个过程就像是用户通过一扇特殊的门,向内核这个神秘的城堡内传递指令,然后得到相应的回应和服务。

2.2加载 initramfs
在核心初始化的过程中,加载 initramfs(Initial RAM Filesystem,初始随机存取存储器文件系统)是一个不可或缺的关键环节。initramfs 就像是一个临时搭建的 “工具箱”,里面装满了启动 Linux 系统所需的最低限度工具和驱动程序,这些工具和驱动程序是系统启动过程中解决各种问题的必备 “武器”。

为什么需要这样一个临时的 “工具箱” 呢?这就涉及到一个类似于 “先有鸡还是先有蛋” 的问题。在 Linux 内核被加载到内存并运行后,内核进程最终需要切换到用户空间的进程来使用计算机,而用户进程又存在于外存储设备上,比如 systemd 进程,通常 systemd 进程所在的存储设备也是 Linux 真正的根文件系统所在的位置。然而,内核源码中并没有包含驱动程序,驱动程序存放在外存储设备上。要切换到 systemd 进程,就需要外存储的驱动,但没有驱动又没办法访问外存储,这就形成了一个看似无解的矛盾。

initramfs 的出现巧妙地解决了这个矛盾。它作为一个临时的文件系统,包含了必要的设备驱动,如硬盘、网卡、文件系统等驱动,以及加载驱动的工具及其运行环境,比如基本的 C 库和动态库的链接加载器等。这样一来,内核就不必再从硬盘等外存储设备中获取驱动,而是可以直接从已经加载到内存的 initramfs 中获取所需的驱动,进而驱动硬盘,访问硬盘上的根文件系统,成功打破了这个 “先鸡还是先蛋” 的僵局。

当计算机启动时,initramfs 会被加载到内存中,然后 Linux 内核会使用这个临时文件系统来完成系统启动的一些必要步骤。内核执行 initramfs 中的 init 脚本,这个脚本就像是一个精心策划的行动计划,它会按照预定的步骤探测硬件设备、加载驱动,为挂载真正的根文件系统做好充分准备。在这个过程中,initramfs 就像是一座坚固的桥梁,帮助 Linux 内核在启动过程中顺利跨越各种障碍,完成各项关键任务,确保系统能够平稳地过渡到正常运行状态。

2.3挂载根文件系统
挂载根文件系统是 Linux 内核启动过程中的最后一个关键步骤,也是系统能够正常运行的重要保障。根文件系统就像是系统的“家园”,它包含了操作系统运行所需的所有基本文件和目录结构,是整个系统文件和目录的起点,所有其他文件系统和目录都建立在它的基础之上。

在挂载根文件系统之前,内核已经完成了一系列的初始化工作,包括内核解压、核心初始化以及加载 initramfs 等。此时内核已经具备了足够的能力来识别和访问存储设备上的根文件系统。挂载根文件系统的过程涉及到多个复杂的步骤,首先需要初始化文件系统相关的数据结构,这些数据结构就像是一份详细的地图,记录着文件系统的各种信息,如文件的存储位置、目录结构等,为内核访问文件系统提供了重要的指引。

接着,内核会注册并挂载根文件系统。在这个过程中,内核会根据启动参数中指定的根文件系统类型,如常见的 ext、XFS 等,调用相应的文件系统驱动程序。这些驱动程序就像是一把把特殊的钥匙,能够打开不同类型文件系统的大门,使得内核能够与根文件系统进行有效的交互。

假设根文件系统位于硬盘的某个分区上,内核会通过设备驱动程序与硬盘进行通信,读取分区表信息,找到根文件系统所在的分区。然后,内核会根据文件系统的格式,如 ext4 文件系统的超级块信息,来初始化文件系统的元数据,建立起文件系统的目录结构和文件索引,从而实现对根文件系统的挂载。一旦根文件系统成功挂载,内核就可以访问其中的文件和目录,执行系统启动所需的初始化脚本和程序,如启动 systemd 进程,进入用户空间,完成整个系统的启动过程。

可以说,根文件系统是 Linux 系统架构的基石,没有它,系统将无法启动或正常运行。它不仅为系统提供了基本的文件和目录结构,还包含了启动和运行系统所需的关键文件和工具,如系统初始化脚本、设备文件、配置文件等,这些文件和工具是系统正常运行的重要保障。

三、初始化系统:系统服务的 “调度者”

当 Linux 内核完成初始化并成功挂载根文件系统后,系统便进入了初始化系统阶段。在这个阶段,初始化系统将负责启动各种系统服务,配置系统环境,为用户提供一个完整且可用的系统环境。其中,systemd 和 init 作为初始化系统的关键组件,在系统服务的启动和管理中发挥着核心作用。

3.1systemd 与 init:初始化系统的 “指挥官”
在 Linux 系统的发展历程中,init 一直是传统的系统初始化工具,基于 System V 的实现,它采用串行启动方式,就像一列按顺序停靠站点的火车,每次只能启动一个服务。init 依赖于运行级别(runlevel)来控制系统的状态,通过/etc/inittab文件定义系统启动时的行为,并借助/etc/rc.d/下的脚本控制服务的启动与停止。在运行级别切换时,init 会按照预设的顺序执行相应的脚本,启动或停止对应的服务。然而,这种串行启动方式在服务数量较多时,会导致系统启动时间较长,而且由于缺乏对服务之间依赖关系的明确表达,很容易出现服务启动顺序错误或失败的情况 。

随着 Linux 系统的广泛应用和功能的不断扩展,传统 init 的局限性逐渐凸显。为了满足现代系统对高效启动和灵活服务管理的需求,systemd 应运而生,成为新一代的系统和服务管理器。systemd 摒弃了传统的运行级别概念,转而使用 target 单元来表示不同的系统状态,为系统管理带来了全新的思路和方法。

systemd 最大的优势之一在于它支持并行启动多个服务,这就好比多辆火车同时出发,大大加快了系统的启动速度。它通过/etc/systemd/system/和/usr/lib/systemd/system/中的单元文件定义服务和目标状态,每个单元文件都包含了丰富的配置信息,如服务的启动命令、依赖关系、重启策略等。以sshd.service为例,通过编写相应的单元文件,我们可以清晰地定义它在网络服务启动之后启动,并且在服务出现故障时自动重启,确保系统的安全性和稳定性。

在服务管理方式上,systemd 也展现出了强大的功能和灵活性。它使用.service文件描述服务单元,支持丰富的配置选项,包括依赖关系、自动重启、资源限制、安全上下文等。通过systemctl命令,我们可以方便地管理服务的生命周期,如启动服务(systemctl start sshd.service)、设置服务开机自启(systemctl enable sshd.service)等。systemd 还能自动检测服务状态并进行恢复,提供了更强的可控性和可维护性,让系统管理员能够更加高效地管理系统服务。

systemd 在日志与会话管理方面也有出色的表现。它提供了journald服务,用于收集和存储日志信息,支持结构化日志记录,并可通过journalctl命令方便地查询日志,为系统故障排查提供了有力的支持。logind模块负责管理用户登录会话,支持会话切换和电源管理,进一步提升了系统的管理效率和用户体验。

3.2运行级别与服务启动:系统状态的 “掌控”
在传统的 init 系统中,运行级别是控制系统状态的重要概念,它定义了系统启动时所加载的服务和进程,以及在系统运行过程中可以切换到的不同操作模式。运行级别通常用数字来表示,在常见的 Linux 系统中,有 7 个标准的运行级别,每个级别都有其特定的用途和功能。

运行级别 0 表示系统关机状态,当系统切换到这个级别时,所有的系统服务和进程都会被关闭,计算机硬件也会停止运行,这是一种安全的关机方式,确保数据的完整性和硬件的安全性。运行级别 1 是单用户模式,主要用于系统维护和修复。在这种模式下,系统只允许一个用户(通常是 root 用户)登录,并且大多数网络服务不会启动,只启动最少数量的服务和进程,方便管理员对系统进行故障排除和修复,比如修复文件系统错误、重置密码或者调整系统配置等。

运行级别 2 到 5 代表着不同类型的多用户模式。运行级别 2 通常是多用户模式,但没有网络支持,适合某些特定的环境,比如在进行网络故障排除时,管理员可以将系统切换到这个级别,以排除网络相关的问题是否是由于系统启动时加载的网络服务引起的。运行级别 3 是最常见的多用户模式,具有完整的网络支持,大多数服务器系统都会默认运行在这个级别。在运行级别 3 下,用户可以登录系统,运行各种应用程序,并且可以通过网络进行通信和数据传输。

运行级别 4 在一些系统中可能被预留用于特定的用途,或者可能与运行级别 3 类似,但具有一些额外的配置或服务,不过在实际应用中很少被使用。运行级别 5 是带有图形界面支持的多用户模式,系统不仅会启动所有必要的网络服务和进程,还会启动图形界面相关的服务和进程,以便用户可以通过图形界面进行操作,这种模式通常用于桌面系统或者需要图形界面支持的应用环境。运行级别 6 表示系统重启,当系统切换到这个级别时,会按照预定的顺序关闭所有的服务和进程,然后重新启动计算机硬件。

在 systemd 中,虽然摒弃了传统的运行级别概念,但仍然提供了与运行级别类似的功能,通过 target 单元来实现。例如,multi-user.target相当于传统的运行级别 3,是多用户文本模式;graphical.target相当于运行级别 5,是带有图形界面的多用户模式。而且,systemd 通过单元文件中的依赖关系定义,能够更加智能地管理服务的启动顺序,确保系统的稳定运行。

在服务启动方面,无论是 init 还是 systemd,都有相应的管理方式。在 init 系统中,服务启动脚本通常位于/etc/init.d/或/etc/rc.d/init.d/,通过执行这些脚本来启动、停止或重启服务。而在 systemd 中,使用systemctl命令来管理服务的启动、停止、重启、状态查看等操作,并且可以通过设置服务的开机自启属性,确保服务在系统启动时自动运行。比如,要启动 Nginx 服务,可以使用命令sudo systemctl start nginx.service;要设置 Nginx 服务开机自启,可以使用命令sudo systemctl enable nginx.service 。通过这些管理方式,系统能够按照用户的需求和系统的配置,准确地启动和管理各种服务,为用户提供稳定、高效的系统环境。

四、终端与用户登录:人机交互的开始

当系统完成初始化并启动各项服务后,就进入了终端与用户登录阶段,这是用户与系统进行交互的起点。在这个阶段,系统会完成终端初始化,为用户提供登录环境,并支持多种登录方式,让用户能够安全、便捷地进入系统。

4.1终端初始化:准备登录环境
终端初始化是系统为用户提供登录环境的重要环节。在 Linux 系统中,终端是用户与系统进行交互的主要界面,它可以是物理终端,如显示器和键盘,也可以是虚拟终端,如通过 SSH 远程连接的终端。终端初始化的过程涉及到多个方面,包括设置终端的属性、加载终端驱动程序以及启动登录管理器等。

在传统的 Linux 系统中,getty程序在终端初始化中扮演着关键角色。getty是一个负责管理终端连接的程序,它会监听终端设备的输入,等待用户输入用户名和密码。当用户在终端上按下键盘时,getty会捕获输入信号,并将其传递给系统进行处理。getty还会设置终端的属性,如波特率、字符编码等,确保终端能够正确地显示和接收信息。

在现代的 Linux 系统中,systemd 引入了systemd - getty - service和systemd - login来管理终端。systemd - getty - service负责启动和管理getty进程,它会根据系统的配置,在指定的终端设备上启动getty,为用户提供登录界面。systemd - login则负责处理用户的登录会话,它会验证用户的身份,创建用户会话,并为用户提供相应的权限和环境变量。通过这种方式,systemd 实现了对终端的更高效管理,提高了系统的安全性和稳定性。

以 Ubuntu 系统为例,在启动过程中,systemd - getty - service会在虚拟终端/dev/tty1到/dev/tty6上启动getty进程,用户可以通过按下Ctrl + Alt + F1到Ctrl + Alt + F6组合键来切换到不同的虚拟终端进行登录。当用户在终端上输入用户名和密码后,systemd - login会对用户的身份进行验证,如果验证通过,就会创建用户会话,用户就可以开始使用系统了。

4.2登录方式:多样的进入途径
Linux 系统支持多种登录方式,以满足不同用户的需求和场景。常见的登录方式包括文本界面登录、图形界面登录和远程登录,每种登录方式都有其特点和适用范围。

文本界面登录是最基本的登录方式,它在系统启动后,用户会看到一个文本界面提示符,需要输入用户名和密码进行登录。这种登录方式简单直接,不占用过多系统资源,适合于服务器环境或者对图形界面需求不高的用户。在文本界面登录时,用户通过键盘输入命令来操作计算机,对于熟悉命令行操作的用户来说,这种方式能够高效地完成各种任务。比如,在服务器上进行系统维护、配置服务等操作时,文本界面登录是非常方便的。

图形界面登录则提供了更加直观和友好的用户体验。在 Linux 系统中,常见的图形界面登录管理器有 GDM(GNOME Display Manager)、KDM(KDE Display Manager)、LightDM 等。用户只需要点击屏幕上的用户名图标,然后输入密码进行登录。登录后,用户可以通过鼠标和键盘操作图形界面,运行各种图形化应用程序。这种登录方式适合于普通桌面用户,他们可以通过图形界面轻松地进行文件管理、网页浏览、办公软件使用等操作。比如,在日常办公和娱乐中,图形界面登录能够让用户更加便捷地使用计算机。

远程登录允许用户从其他主机或者终端连接到 Linux 系统进行登录,这在系统管理和维护中非常有用。常用的远程登录协议有 SSH(Secure Shell)和 Telnet。SSH 提供了更安全的连接方式,它使用加密技术对传输的数据进行加密,防止数据被窃取和篡改。通过 SSH,管理员可以在远程主机上执行各种命令,管理服务器的配置、安装软件、查看日志等。而 Telnet 是一种不加密的连接方式,数据在传输过程中可能会被截获,因此安全性较低,现在已经较少使用。

为了确保登录的安全性,Linux 系统采取了一系列措施。设置强密码是非常重要的,密码应该包含字母、数字和特殊字符,长度足够,避免使用简单易猜的密码。启用 SELinux(Security - Enhanced Linux)也是增强系统安全性的有效手段。SELinux 是一种强制访问控制(MAC)安全模块,它通过定义和实施安全策略,限制进程对资源的访问权限,即使系统中的某个服务被攻陷,攻击者也只能在有限的权限范围内活动,无法获取系统的最高权限,从而有效地保护了系统的安全。

可以说,终端与用户登录是 Linux 系统启动过程中的最后一个环节,也是用户与系统交互的开始。通过终端初始化和多种登录方式,Linux 系统为用户提供了一个安全、便捷的登录环境,让用户能够顺利地进入系统,享受 Linux 系统带来的强大功能和高效体验。

五、关机流程:系统的 “落幕”

当我们完成对 Linux 系统的使用后,正确的关机流程同样至关重要。关机过程并非简单地切断电源,而是涉及到数据同步、服务停止以及系统资源的释放等一系列有序操作,以确保系统的安全性和稳定性。在这个过程中,sync 命令和 shutdown/reboot 命令发挥着关键作用。

5.1 sync:数据的 “守护者”
在 Linux 系统中,为了提高 I/O 性能,当我们进行文件写入操作时,数据通常不会立即被写入物理磁盘,而是先被存储在内存缓冲区(即 “脏页”)中。这样做的好处是减少了对磁盘的频繁读写操作,提高了系统的整体运行效率。但这种机制也带来了一定的风险,如果在数据还未写入磁盘时,系统突然断电、崩溃或进行其他异常操作,那么内存中尚未写入磁盘的数据就会丢失,这可能会导致文件损坏、数据不一致等严重问题。

sync 命令的出现,就是为了解决这个问题。它的核心作用是强制将内存缓冲区中所有尚未写入磁盘的缓存数据,包括文件数据、目录结构、元数据等,立即刷新到物理磁盘中,确保数据的持久性和完整性。当我们执行 sync 命令时,系统会调用内核的 sync () 系统函数,将内存中的 “脏页” 数据写入磁盘,使得磁盘上的数据与内存中的数据保持一致。在进行重要文件操作,如修改系统配置文件、完成数据库事务等之后,执行 sync 命令可以保证这些操作的数据已经被安全地保存到磁盘上,避免因意外情况导致数据丢失。

sync 命令的使用场景非常广泛。在安全卸载可移动设备,如 U 盘、移动硬盘时,在执行 umount 命令之前,先运行 sync 命令是一个非常重要的步骤。这可以确保所有针对该设备的数据都已经真正写入了物理设备,防止在卸载过程中数据丢失或损坏。在自动化脚本中,特别是在执行关键操作,如数据库备份、生成重要日志、更新核心文件后,加入 sync 命令可以提高操作的可靠性。在数据库备份完成后,运行 sync 命令可以确保备份文件已经完整地写入磁盘,避免因系统意外导致备份文件损坏或不完整。

虽然 Linux 内核默认每 5 - 30 秒会自动同步缓存(通过 bdflush 或 kupdated 守护进程),但在一些特殊情况下,手动执行 sync 命令仍然是必要的。在进行系统关机或重启操作前,虽然标准的系统关机和重启流程中会自动调用 sync 多次,但在某些复杂的操作环境中,手动执行 sync 命令可以进一步确保数据的安全性。

5.2 shutdown/reboot:系统的有序关闭
shutdown 和 reboot 命令是 Linux 系统中用于实现系统关机和重启的重要工具,它们为系统的关闭和重启提供了一种有序、安全的方式。

shutdown 命令功能强大且灵活,它允许用户有计划地关闭或重启系统,并可以发送警告信息给所有登录用户,让用户有时间保存工作并正常退出系统。shutdown 命令的基本语法为 “sudo shutdown [选项] [时间] [警告信息]”。其中,“选项” 部分有多个参数可供选择,“-h” 表示停止系统并关机,“-r” 表示重启系统。“时间” 参数可以指定关机或重启的时间,“now” 表示立即执行,“+5” 表示 5 分钟后执行,也可以指定具体的时间,如 “22:00” 表示在晚上 10 点执行。“警告信息” 则是在关机或重启前发送给所有登录用户的提示信息,告知他们系统即将进行的操作。

假设我们需要立即关机,可以使用命令 “shutdown -h now”,系统会立即停止所有服务,并将内存中的数据同步到磁盘,然后关闭系统。如果我们希望在 30 分钟后重启系统,并向用户发送提示信息,可以使用命令 “shutdown -r +30 "系统将在 30 分钟后重启,请保存您的工作!"” 。在执行 shutdown 命令后,系统会按照指定的时间进行操作,并在操作前向用户发送警告信息,让用户有足够的时间保存数据和关闭应用程序。

reboot 命令则更为简洁,它的作用是直接重启系统,相当于执行 “shutdown -r now”。当我们需要快速重启系统,如在安装系统更新、修改系统配置后,希望立即应用更改时,可以使用 reboot 命令。reboot 命令会立即停止所有服务,同步数据到磁盘,然后重新启动系统。

正确使用 shutdown 和 reboot 命令对于系统的安全和稳定至关重要。如果直接切断电源或使用不当的关机方式,可能会导致文件系统损坏、数据丢失、系统无法启动等严重问题。在 Linux 系统中,文件系统采用日志式结构,在写入数据时会先记录操作日志,然后再实际写入数据块。如果突然断电,尚未写入磁盘的数据和未完成的操作日志会使文件系统处于不一致状态,下次开机时,系统需要花费大量时间运行 fsck 工具来修复这些不一致,甚至可能无法完全修复,从而导致数据丢失。

在关机或重启系统时,一定要使用正确的命令和流程,确保系统能够安全、有序地关闭或重启。这不仅可以保护系统中的数据和文件系统,还能延长硬件设备的使用寿命,为我们提供一个稳定、可靠的系统运行环境。

六、Linux内核启动实战

6.1环境准备
操作系统:Ubuntu 22.04(x86_64)
内核版本:5.15.0(目标编译 5.15.100 版本)
工具依赖:build-essential、libncurses5-dev、bison、flex、libssl-dev、grub2-common
内核文件通常存放在/boot目录下,通常该目录也是grub程序的根目录,内核是以bzimage压缩存放在该目录中,并且名称一般都是以vmlinuz-后跟版本号。这样的方式来命名:
vmlinuz-2.6.32-585.el.i686

通过配置文件,该配置文件通常存放在/boot/grub目录中,名字通常为:grub.conf

配置文件定义了各种菜单,每个一般都对应一个内核,以及传递一些运行参数给内核

开头的几个选项是一些通用配置,只要定义默认菜单、超时时间和背景图的一些选项。

每个菜单,基本上都定义了三个类内容:root、kernel、initrd。

6.2实战步骤
①下载并编译内核:首先从内核官网下载源码,编译出可启动的内核镜像。

# 下载内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.100.tar.xz
tar -xf linux-5.15.100.tar.xz
cd linux-5.15.100

# 配置编译选项(基于当前系统配置修改)
cp /boot/config-$(uname -r) .config
make menuconfig  # 图形化配置,可按需精简模块(比如关闭不支持的硬件驱动)

# 编译内核(-j6表示6线程,根据CPU实际核心数调整)
make -j6
make modules_install  # 安装内核模块
make install  # 安装内核镜像到/boot目录
编译完成后,/boot目录会新增 3 个关键文件:

vmlinuz-5.15.100:压缩的内核镜像
initrd.img-5.15.100:临时根文件系统(initramfs)
System.map-5.15.100:内核符号表

②修改 GRUB 配置,指定启动参数:GRUB 是引导内核的关键,需要手动配置启动项,明确告诉 GRUB 内核位置和启动参数。

# 编辑GRUB配置(不同系统路径可能为/etc/grub.d/40_custom或/boot/grub/grub.cfg)
vim /etc/grub.d/40_custom

③添加如下配置(核心是root、kernel、initrd三个选项)

menuentry 'Custom Linux 5.15.100' --class ubuntu --class gnu-linux {
    # 1. 指定GRUB的根目录(即/boot所在分区,这里假设是第一个硬盘的第一个分区)
    root=(hd0,1)  # 注意:Ubuntu的/boot通常在sda1,索引从0开始,故为(hd0,1)

    # 2. 指定内核路径+启动参数
    #   /vmlinuz-5.15.100 对应实际路径/boot/vmlinuz-5.15.100
    #   root=/dev/sda2 告诉内核:系统真正的根目录在sda2
    #   console=tty0 输出启动日志到显示器
    #   debug 开启内核调试模式(可选,用于跟踪细节)
    kernel /vmlinuz-5.15.100 root=/dev/sda2 console=tty0 debug

    # 3. 指定临时根文件系统(协助内核挂载真正的根目录)
    initrd /initrd.img-5.15.100
}

保存后更新 GRUB:
update-grub  # 生成新的grub.cfg

④重启系统,跟踪启动流程:重启后在 GRUB 菜单选择「Custom Linux 5.15.100」,观察启动过程。若想记录日志,可在启动后查看。

# 查看内核启动日志(包含从解压到初始化的关键步骤)
dmesg | less

# 重点关注以下关键字段:
# [    0.000000] Linux version 5.15.100  # 内核版本
# [    0.000000] Command line: root=/dev/sda2  # 启动参数
# [    0.5xxxxx] VFS: Mounted root (ext4 filesystem)  # 挂载根文件系统
# [    1.2xxxxx] systemd[1]: Starting System Initialization...  # 进入初始化阶段

⑤常见问题排查:如果启动失败,可通过以下方式定位问题

内核找不到根目录:检查kernel行的root=/dev/sdaX是否正确(可用lsblk查看分区)
缺少模块:编译时未勾选必要驱动(比如文件系统驱动ext4),需重新make menuconfig开启
GRUB 路径错误:root=(hd0,X)填错,可在 GRUB 命令行用ls查看分区(启动时按c进入命令行)

6.3核心配置解析
上述实战中,kernel选项是关键,它包含两个核心信息:

内核路径:/vmlinuz-5.15.100基于 GRUB 的根目录(root=(hd0,1)),对应实际/boot/vmlinuz-5.15.100
启动参数:root=/dev/sda2是给内核的关键指令,告诉内核「真正的根文件系统在哪个分区」,否则内核会因找不到系统文件而启动失败
通过这个实战,你可以直观看到:从 GRUB 加载内核,到内核解压、初始化硬件、挂载根目录,再到启动systemd的完整链条。如果想深入,还可以尝试添加init=/bin/sh参数(跳过初始化系统,直接进入 shell),观察内核启动的更早期阶段。

6.4centos下安装内核源码包
一些系统级的应用软件在安装时需要用到内核源码,但并不是所有的系统中安装了对应的内核包。像centos6上一般就只需要有kernel-devel包安装了即可:yum install kernel-devel

如果安装好后仍不能解决问题,那就需要看下面的内容了。

On CentOS 6 there is only one kernel-devel package for both architectures x86_64 and i386 however on CentOS 5 there are three versions, if you are unsure on what version you should install then run the command uname -r which will display your kernel version you then need to match the kernel version you are running with the correct kernel-devel package below:

CentOS 5 install kernel-devel for i386 & x86_64:
yum install kernel-devel

Centos 5 install Xen kernel-devel for x86_64 & i386:
yum install kernel-xen-devel

And finally to install kerenl-devel for PAE:
yum install kernel-pae-devel

下面的方法适用于centos 5及6
CentOS Kernel source install (full) for CentOS 5 & 6

如果只是想用包来安装,解决软件对其的依赖而已,可用如下指令:

yum -y install dkms gcc kernel-headers-`uname -r` kernel-devel-`uname -r` kernel-`uname -r`

If install kernel-devel above does not work or you require the full CentOS kernel source for another reason you can install it on CentOS 5 & 6 with:
yum install rpm-build redhat-rpm-config unifdef

如果是非root用户:
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros

安装centos 6的内核源码
To install the CentOS 6 kernel source

The following command will install the kernel source for CentOS 6.2, you can install it for other version of CentOS 6 simply by using the URL for the alternative version.

rpm -i http://vault.centos.org/6.2/updates/Source/SPackages/kernel-2.6.32-220.7.1.el6.src.rpm 2>&1 | grep -v mockb

安装centos 5.x的内核源码
How To install the CentOS 5.x kernel source

The following will install the full kernel source on CentOS 5.8, again the same applies – you can install the kernel source for another version of CentOS 5 by swapping the URL.

rpm -i http://vault.centos.org/5.8/updates/SRPMS/kernel-2.6.18-308.1.1.el5.src.rpm 2>&1 | grep -v mockb

当安装好了对应的内核包,进行如下的操作:
cd ~/rpmbuild/SPECS

And then unpack the kernel source with:
rpmbuild -bp --target=`uname -m` kernel-2.6.spec 2> prep-err.log | tee prep-out.log

Make sure you copy and paste the commands above as the uname -m command will pull your kernel arch (e.g x86_64) and prep the source for your kernel, you can find the kernel source in your home dir in a directory called rpmbuild/BUILD/ to access the CentOS kernel source install dir type the following as the logged in user you built the kernel source with ~/rpmbuild/BUILD/