资讯动态

Linux开机引导全解析:从BIOS/UEFI到systemd服务管理

发布时间:2026/10/8 8:49:51 来源:尧图企业网站定制
1. 开机到登录之间Linux究竟干了多少事很多人用Linux用了好几年ping通网络、能跑服务、会调配置但真被问到机器一通电到出现登录界面中间到底发生了什么往往只能说出好像是启动内核这样半吊子的答案。这个问题一旦搞不清楚后面遇到开机卡死、服务起不来、系统引导损坏这类故障时排查就全靠瞎试。我最早带运维团队的时候新人入职第一课就是让他们把引导过程完整讲一遍不是背流程而是真的能在故障现场判断出现在机器卡在哪一步了。因为引导过程的每一步都有对应的现象特征是灯亮但显示器无信号、还是GRUB菜单没出来、还是内核解压后卡住、还是进入了系统但服务全挂。你只有知道正常链路是什么样才能快速定位异常发生在哪一环。这篇文章我打算把Linux从按下电源键到进入命令行的完整路径拆开讲再重点说明服务控制的部分。服务控制之所以要单独拎出来说是因为现在主流发行版都已经全面转向systemd它既是引导的最后一步也是日常运维里打交道最多的东西。不管是开机自启配置、服务异常排查还是性能优化最后都要落到systemd的管理逻辑上。内容面向两类读者一类是刚接触Linux、想系统化理解的初学者另一类是已经会敲命令但没系统梳理过引导链路的运维同行。我会尽量不堆术语把每个环节的行为、用途、常见故障点都讲透。2. 引导的第一棒固件层如何把控制权交给磁盘2.1 BIOS与UEFI两条不同的接力路线按下电源键之后最先醒过来的不是Linux也不是Windows而是写在主板芯片里的固件程序。传统的主板用的是BIOS新一点的基本都是UEFI。这俩干的活本质上一样初始化硬件、做自检、然后找到一块可引导的设备把控制权交出去。区别在于工作方式。BIOS走的是16位实模式寻址空间有限流程老派但兼容性极好UEFI是现代32/64位固件支持更大的磁盘分区GPT启动速度更快还带安全启动Secure Boot这样的附加机制。你去看Windows笔记本预装Linux会发现引导格外麻烦多半就是Secure Boot在作怪。固件阶段会按顺序去检查引导设备列表U盘、光驱、硬盘、网络启动PXE。服务器上常见的做法是把网络启动放在第一位方便批量装机普通桌面机一般默认硬盘优先这就是为什么插入带有引导记录错误的U盘时机器会卡在自检界面很久——它在逐个尝试设备直到某个设备返回我能启动。这里有个新手容易忽略的点固件层根本不懂什么是Linux还是Windows。它只负责执行一个被叫做引导程序的引导程序术语叫bootloader的代码。Linux下最常见的就是GRUB2它在硬盘的特定位置放着固件把它读进内存整个引导才进入下一阶段。2.2 MBR、GPT与引导分区的关系引导程序放在哪里取决于磁盘的分区表类型。老式的MBR主引导记录分区表引导代码占据磁盘最开始的512字节空间极其有限所以GRUB2本身并不全塞在MBR里——MBR里只放了一段一级引导代码它再去读取后续的GRUB核心模块。GPT分区表则是保留了一个专属的EFI系统分区ESP一般格式化为FAT32里面存放.efi格式的引导文件。装机的时候经常会遇到一种情况重装系统或者调整分区大小之后开机直接进入GRUB rescue界面屏幕上就一个grub提示符。这多半是因为GRUB的核心模块路径变了或者ESP分区被误格式化。处理方式一般是用Live CD启动挂载原系统的根分区和ESP分区再用挂载后的实际路径重新安装GRUB。比如ESP挂在/boot/efi时重建指令大致是grub-install --targetx86_64-efi --efi-directory/boot/efi之后还要重新生成引导菜单。我见过不少服务器运维在扩容磁盘时因为对分区表类型没有概念把GPT磁盘当成MBR来操作结果整块盘的引导记录丢失机器直接起不来。所以要我说这一层哪怕你日常不碰也必须知道自己的机器到底是BIOSMBR还是UEFIGPT因为所有引导类故障的处理路径完全不一样。2.3 固件阶段的故障特征固件阶段如果出了问题现象通常是显示器没有输出、键盘灯闪一下就没反应、蜂鸣器报警、或者卡在厂商Logo页面。这种时候别急着怀疑Linux先用排除法确认基本硬件没问题。服务器上常见的是远程管理卡比如IPMI里能看到报错日志家用机就只能用最小化硬件排查法——拔掉所有非必需设备只留CPU、单条内存、板载显示输出逐步加回硬件找到问题点。3. GRUB引导菜单内核启动前的最后一道闸门3.1 GRUB2的配置文件与加载逻辑进入GRUB之后屏幕会出现一个操作系统选择菜单。可能很多人以为菜单里的选项都是自动扫描出来的其实不是。GRUB2的菜单是安装时生成的由/boot/grub2/grub.cfgRHEL系或/boot/grub/grub.cfgDebian系定义这个文件里记录了内核镜像的位置、initramfs镜像的位置以及要传给内核的参数。grub.cfg本质上是自动生成的日常我们不应该手动编辑它。想临时改启动参数比如进入单用户模式应该在GRUB菜单界面按e键进入编辑模式修改linux那一行末尾的启动参数。这个操作在忘记root密码的时候几乎是救命技能在参数末尾加上rd.break或者single然后按Ctrlx引导就能进入紧急模式重置密码。如果要永久修改某个启动项的参数正确做法是修改/etc/default/grub文件RHEL系执行grub2-mkconfig -o /boot/grub2/grub.cfg重新生成配置Debian系执行update-grub。这两个命令背后做的事是一样的读取模板和脚本扫描系统中实际存在的内核生成对应每个内核版本的启动条目。3.2 initramfs为什么内核不能直接挂载根文件系统有个问题是初学者最喜欢问的内核明明能识别磁盘、文件系统为什么还需要一个叫initramfs初始RAM文件系统也叫initrd的东西先把一个微型系统加载进内存再去挂载真正的根分区原因其实很现实根分区可能位于LVM逻辑卷上、或者被LUKS加密了、或者是某些文件系统驱动还没被内核加载。内核本身不含这些外挂逻辑如果直接尝试挂载根分区很可能认不出来。所以GRUB会把initramfs镜像也加载进内存这个镜像里包含必要的驱动模块和挂载脚本它启动一个临时环境把根分区真正找到并挂载好然后通过switch_root把控制权交给真正的根文件系统。RHEL系重装内核时会自动更新initramfs但如果你手工调整了LVM卷或者修改了fstab里的根分区UUID就需要手动重建。RHEL系命令是mkinitrd -f /boot/initramfs-$(uname -r).img $(uname -r)或dracut -fDebian系可以执行update-initramfs -u。我在实际工作中遇到过虚拟机克隆后无法开机的案例原因就是克隆导致网卡MAC变化网卡设备名从eth0变成了eth1而initramfs里的配置还写死着旧的eth0。这种问题用dracut -f重建一下就好。3.3 内核参数在引导中的实际作用内核启动时会解析GRUB传过来的参数常见的有quiet静默模式不打印太多启动日志、splash显示启动动画、selinux0关闭SELinux、nomodeset关闭显卡模式设置常用于NVIDIA驱动导致的黑屏问题。这些参数的知识在排障时非常有用尤其是当系统启动到一半花屏或者卡住时去掉quiet和splash加上systemd.log_leveldebug往往能看到卡住时的确切内核日志。4. 内核初始化与systemd接管从裸机到用户空间的转换4.1 内核启动的第一批动作GRUB把内核和initramfs加载到内存后控制权正式交给内核。内核做的第一件事是解压自身到内存中做CPU、内存、设备树的探测初始化内存管理、进程调度等核心子系统。然后它会把initramfs挂载为临时的根文件系统执行里面作为PID 1存在的/init脚本完成真正根分区的挂载。这一段的日志通常刷得很快正常启动时几乎看不清楚。如果想事后复盘执行journalctl -b可以看到本次启动的完整日志前面部分就是内核启动到systemd接管的过程。排查内核恐慌Kernel Panic时journalctl -k -b只过滤内核消息会更高效。4.2 PID 1的历史变迁SysVinit到systemd内核启动完找到了真正的根分区然后要启动用户空间的第一个进程也就是PID 1。在老派的SysVinit时代PID 1是/sbin/init它按照运行级别runlevel的概念依次执行/etc/rc.d/rc?.d/下面的启动脚本每个脚本负责启动一个服务按数字顺序一个个来。这种方式简单直白但缺点也很明显串行启动速度慢服务脚本各自维护一套启动停止逻辑检查依赖全靠脚本约定而且SysVinit对服务具体启动到哪里了、出了什么问题几乎是黑盒想查看状态就得读日志、翻脚本。所以现在几乎所有主流发行版都把PID 1换成了systemd。systemd是一个并行启动的初始化系统它把服务抽象成单元Unit单元之间声明依赖关系systemd可以根据依赖图并行启动互不依赖的服务启动速度比SysVinit快得多同时状态查询、日志收集、故障隔离的能力也强得多。目前Debian 8以上、Ubuntu 15.04以上、CentOS 7以上、RHEL 7以上默认都用systemd管理。4.3 systemd单元的启动顺序与依赖解析systemd启动时不是按数字编号跑的而是读入所有单元文件构建依赖图然后自底向上激活。启动目标target是systemd的一个概念相当于SysVinit里的运行级别。比如multi-user.target对应多用户文本模式graphical.target对应图形界面。你可以执行systemctl get-default查看当前默认目标用systemctl set-default multi-user.target把默认启动到文本模式。依赖关系在单元文件里通过Wants、Requires、After、Before这些指令声明含义上有微妙差别Requires表示强依赖被依赖的单元启动失败当前单元也会失败Wants是弱依赖失败了不影响当前单元After只决定顺序不决定依赖。我建议所有自己写service单元文件的人都把这几个指令彻底搞清楚因为排障时候看依赖关系全靠它们。5. 服务控制实战systemctl命令族的完整逻辑5.1 单元状态的四个维度日常使用systemctl管理服务最关键的是搞懂状态这个词的几种含义。systemctl status sshd的输出里会显示该服务当前是否为运行中以及它是否被设置为开机自启这两者是不同的维度一个是当前运行态一个是持久化配置。初学者经常问我为什么我用systemctl stop停掉的服务重启后又自动起来了——因为它的enable状态还是开着的stop只管当前运行态要真正让它以后都不启动得执行systemctl disable。管理服务主要就是围绕这四个状态维度操作当前是否运行systemctl start、systemctl stop、systemctl restart控制是否开机自启systemctl enable、systemctl disable控制systemctl is-enabled查询是否加载到内存systemctl daemon-reload重新加载单元定义是否处于异常状态systemctl status查看详细情况包括主进程PID、内存占用、最近日志5.2 自定义service单元的编写规范有些软件不自带systemd服务配置或者我们希望自己定义一个守护进程就需要手动写单元文件。单元文件放在三个位置/usr/lib/systemd/system/软件包自带的、/etc/systemd/system/管理员手工创建的、/run/systemd/system/运行时临时生成的。优先级是后者覆盖前者所以如果我们要覆盖软件包自带的配置应该把自定义文件放到/etc/systemd/system/下同一个服务名文件会覆盖库目录中的同名文件。一个最简单的service单元文件长这样[Unit] DescriptionMy Custom Daemon Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/mydaemon --config/etc/mydaemon.conf Restarton-failure [Install] WantedBymulti-user.target其中Typesimple表示ExecStart启动的这个进程本身就是服务主进程如果服务有fork行为启动一个父进程后自动脱壳变成后台进程需要改成Typeforking再配合PIDFile指定pid文件位置否则systemd会认为主进程退出导致服务状态异常。Restarton-failure是防止守护进程意外崩溃时全程宕机的好习惯生产环境服务基本都会配上。写完单元文件之后执行systemctl daemon-reload重新加载然后就能用systemctl start、systemctl enable管理它了。5.3 日志查看journalctl的正确打开方式systemd把标准输出和标准错误输出都收集进了自己的日志系统journald这意味着很多服务不用配置单独的日志文件也能通过统一接口查看。最常用的几个用法journalctl -u sshd查看ssh服务的全部日志journalctl -u sshd -f实时跟踪最新日志journalctl --since 1 hour ago只看最近一小时journalctl -u sshd -o json-pretty用JSON格式输出方便脚本处理。有一个坑我踩过好几年如果服务器时间使用UTC且时区没设置正确journalctl里的时间戳看起来总是差八小时。建议在任何生产机器上第一时间把时区校准执行timedatectl set-timezone Asia/Shanghai并配合chronyc或者systemd-timesyncd做好时间同步。不然排查问题时看到的时间戳错位会严重误导判断。6. 引导失败与服务异常的排障链路6.1 启动到一半卡住先看是内核阶段还是用户空间阶段处理引导故障第一件事不是去网上搜错误码而是确认当前卡在哪一段。判断方法很直接启动时去掉quiet silent参数观察屏幕输出。如果输出停在类似Loading initial ramdisk之前说明是GRUB阶段如果已经出现内核版本信息但后面没动静多半是内核初始化或initramfs阶段如果已经能看到一些类似[ OK ] Started xxx的消息后卡住那是systemd启动用户服务阶段出了问题。内核阶段的问题多见于硬件不兼容、驱动缺失解决方向是换引擎版本或者调整内核参数initramfs阶段的常见问题是根分区无法识别解决方向是用dracut -f或update-initramfs -u重建initramfssystemd阶段的问题最常见原因是某个关键服务阻塞了依赖链大量服务排队等它超时看起来就像整个系统卡死了。6.2 服务状态Failed的典型排查过程systemctl status xxx显示failed状态时先别急着重启服务那样会丢掉第一现场。正确顺序是查看状态是否显示主进程退出的退出码再看journalctl -u xxx -n 100 --no-pager查最近100条日志确认退出前最后几条日志有没有明显的错误信息比如找不到配置文件、端口被占用、权限不对。我曾经处理过一个Nginx反复启动失败的案例journalctl里只看到报emerg无法启动看不出原因。后来用nginx -t手工测试配置才发现SELinux上下文没配好根本没到监听端口那一步就退出了。这类问题从服务日志里看可能只有个笼统的失败消息需要多维度交叉验证。另一个常见情况是端口冲突报bind() to 0.0.0.0:80 failed用ss -lntp一看确实有别的进程先占了这个端口。6.3 rescue模式与恢复手段如果系统引导到无法进入正常多用户环境的程度可以用GRUB菜单里的rescue.target或者emergency.target进入救援模式。在GRUB菜单按e编辑启动项往linux行的末尾追加systemd.unitrescue.target按Ctrlx引导即可。救援模式会挂载根文件系统但不会启动其他服务适合修复损坏的单元文件、修改错误的fstab、重置忘记的root密码等操作。如果连GRUB本身都坏了那就只能借助Live CD引导后chroot进入原系统修复这往往是引导修复的终极手段。7. 从开机自启到按需启动systemd的高级控制思路7.1 用target管理一组服务的开关状态现实场景里我们经常需要一次性启停一组服务比如维护窗口要停掉所有Web应用相关服务或者批量启动整个Kubernetes节点需要的组件。sysadmin习惯用systemctl isolate multi-user.target切到纯文本模式或者用systemctl list-dependencies查看某个target下面的所有依赖单元。target真正的价值在于它是一组服务的逻辑分组通过修改默认target可以快速改变开机后的服务环境。一个容易踩坑的细节是systemctl enable并不会立即启动服务它只是创建了符号链接让服务在对应的target启动时被拉起来。很多新手改了enable之后发现服务没有立刻运行又来问我是不是命令没生效。systemctl enable --now是连enable带start一起做完在日常操作里用起来更省心。同理systemctl disable --now可以同时禁掉自启并停止当前运行。7.2 服务单元的模板化用符号复用配置systemd支持模板单元文件名带一个符号比如[email protected]。模板文件里可以用%i引用实例名。这样一台机器上跑多个相同程序的实例就不需要为每个实例写一份单元文件只要写一个模板然后通过systemctl start [email protected]、systemctl start [email protected]分别启动不同实例。我在跑多实例的Celery worker时就是这么做的配合不同的环境变量和配置文件路径一处改动全实例生效比复制多份文件好维护得多。7.3 开机时间优化的小技巧掌握了引导过程之后优化开机时间就成了一件有目标的事。命令systemd-analyze查看总启动耗时systemd-analyze blame按时间降序列出每个单元的初始化耗时systemd-analyze critical-chain显示当前默认target中最耗时的那条关键链路。我会优先处理critical-chain里暴露出来的单元看是否有可以延迟启动的服务加上After依赖、是否有不必要的自启服务执行disable、是否有等待网络超时的单元考虑调整NetworkManager-wait-online.service的配置。优化过一个现场一台物理机开机时间从90秒降到35秒改善主要靠三点——把等等网络的单元去掉把不需要在开机阶段就拉的LVM卷检查延后以及给固定IP的网卡配置里关掉DHCP等待。这些调整全部基于systemd-analyze的输出而不是拍脑袋。所以只要你掌握了引导的完整链路做优化就是顺理成章的事。8. 我在实际运维中积累的几条经验最后分享几个从实战里摔出来的心得不一定系统但大概率能帮你省点事。第一不要手动编辑/etc/fstab里根分区的UUID只改挂载点路径。一旦写错系统启动会尝试等待一个不存在的设备卡死很久才报错而救援模式又不一定挂载得上。第二给生产机器的GRUB菜单设置个超时时间默认的5秒往往不够人手忙脚乱但设成0又会让维护时进不了菜单。建议设成GRUB_TIMEOUT10需要安静维护时再临时改。第三所有自己写的systemd服务单元ConditionPathExists和ExecStartPre里能加校验就加上。比如启动前检查配置目录是否存在配置无效就直接失败配合systemctl status可以看到明确原因而不是服务启动到一半死在内部逻辑里。第四journalctl的日志可能长期不清理会撑满根分区。建议配置SystemMaxUse限制journal日志占用的磁盘总量比如设成200M避免日志增涨把根目录塞满。第五不要一上来就kill -9去杀服务进程。systemd下有systemctl kill这个受管信号发送方式它知道怎么正确处理子进程和资源清理比手工kill粗暴得多。系统里有大量服务依赖systemd管理的cgroup资源统计绕过它杀进程容易留下僵尸资源。引导过程和服务控制这两件事表面看是一堆命令和配置实际内核里是一条完整的责任链固件找引导、引导找内核、内核找根分区、systemd跑服务。每一条都有它的设计目的也都有对应的故障模式。把这条链背下来遇到问题的时候顺着链路逐个环节检查很多疑难杂症会变得清晰很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑