6.18 Linux 内核管理——/proc、/sys、模块与 udev

预计阅读时间:17 分钟

📖 目录

学习目标

学完本章,你将能够:

  • /proc 读取 CPU、内存、负载、进程和网络统计信息
  • 通过 /sys 查看和修改设备参数(I/O 调度器、网络属性、电源状态)
  • 使用 lsmodmodprobermmod 管理内核模块及 /etc/modprobe.d/ 配置
  • 编写 udev 规则固定设备名称、设置权限、触发自定义动作
  • sysctl 做运行时内核参数调优并持久化到配置文件
  • 完成 Ubuntu/Debian 内核的升级、回退与清理

核心知识

内核管理分为六个子领域:

子领域用户空间接口主要命令/文件
进程/系统信息/proc/proc/meminfo, /proc/loadavg, /proc/cpuinfo
设备模型/sys/sys/class/net, /sys/block
内核模块lsmod / modprobe/etc/modprobe.d/*.conf, /lib/modules/
设备管理udev/etc/udev/rules.d/, udevadm
运行时调优sysctl/etc/sysctl.conf, /proc/sys/
内核本体包管理器apt install linux-image-*, update-grub

知识关联

原理讲解

为什么 Linux 要用 /proc 和 /sys 两个虚拟文件系统

/proc 和 /sys 的存在源于 Unix 哲学"一切皆文件"——内核通过虚拟文件系统向用户空间暴露内部状态和控制接口。/proc(procfs)诞生于早期 Linux,主要暴露进程信息和系统统计(如 /proc/meminfo、/proc/[pid]/status),以及可调内核参数(/proc/sys/)。/sys(sysfs)诞生于 2.6 内核,专门呈现 Linux 设备模型的层次结构——总线、设备、驱动、类都在这里有对应的目录。之所以需要两个文件系统,是因为它们的设计目标不同:/proc 是扁平的键值对集合,适合快速查看系统状态;/sys 是结构化的目录树,完整表达设备拓扑关系。/proc/sys/ 的参数可以通过 sysctl 命令修改,而 /sys/ 下的参数通常通过 echo 直接写入。

内核模块为什么要区分 modprobe 和 insmod

modprobe 和 insmod 的区别源于模块依赖管理的复杂性。Linux 内核模块之间存在依赖关系(如 nft_tables 依赖 nf_tables),手动处理依赖链既繁琐又容易出错。insmod 只能加载单个 .ko 文件,不解析依赖,适合调试场景。modprobe 依据 /lib/modules/$(uname -r)/modules.dep(由 depmod 生成)自动解析依赖并递归加载所有前置模块。同理,modprobe -r 递归卸载整条依赖链,比 rmmod 更安全。这种设计让模块管理从"人工追踪依赖"变成"自动处理",大幅降低了运维复杂度。

sysctl 参数为什么要区分运行时和持久化

sysctl 参数的"运行时修改"和"持久化"分离设计是为了平衡安全性和便利性。运行时修改(sysctl -w 或写 /proc/sys/)立即生效,适合排障和测试——你可以快速验证某个参数对性能的影响,重启后自动恢复默认值。持久化(写入 /etc/sysctl.d/*.conf)在每次启动时加载,适合生产环境的固定调优。这种"先测试再持久"的工作流降低了调优风险——如果某个参数导致系统不稳定,重启即可恢复。注意 sysctl -p 只加载文件里声明的项,不会清空其他运行时修改,这避免了配置文件之间的冲突。

/proc 是一个伪文件系统(procfs),由内核动态生成,不占用磁盘空间。它暴露了进程信息(/proc/[pid]/)、系统统计(meminfoloadavguptime)、网络状态(/proc/net/)以及可调内核参数(/proc/sys/)。写 /proc/sys/ 下的文件等效于 sysctl -w,修改即时生效但重启丢失。

/sys 是 sysfs 文件系统的挂载点,呈现 Linux 设备模型的层次结构——总线、设备、驱动、类都在这里有对应的目录。相比 /proc 的扁平键值对,/sys 用目录树完整表达设备拓扑。

内核模块(.ko 文件)存放在 /lib/modules/$(uname -r)/。模块可以内建(built-in,编译时嵌入内核镜像)或可加载(loadable)。modprobe 自动处理模块依赖,优先于 insmod / rmmod/etc/modprobe.d/ 用于设置模块参数、加载顺序和黑名单。

udev 基于 netlink 监听内核 uevent,根据 /etc/udev/rules.d/ 中的规则匹配设备属性(vendor、model、路径等),执行命名、权限设置或触发脚本。规则匹配成功后,udevadm trigger 可强制重新应用。

sysctl 接口对应 /proc/sys/ 下的参数树,可通过 sysctl -w 运行时修改,写入 /etc/sysctl.conf/etc/sysctl.d/*.conf 实现持久化。

内核版本号规则uname -r 输出如 6.8.0-49-generic,拆解为:主版本 6、次版本 8、补丁版本 0、发行版补丁 49(Ubuntu/Debian 额外维护)、风味后缀 -generic(另有 -lowlatency-aws-azure 等)。2.6 时代曾用次版本奇偶区分稳定/开发线(奇数=开发版),3.0 之后内核不再遵循该规则。

内核版本类型选择

类型维护周期示例适用场景
LTS(长期支持)约 6 年,期间持续发布小版本修复6.6.x、6.12.x生产服务器、嵌入式设备
Mainline(主线)每 2-3 个月一个大版本,维护窗口短6.13、6.14尝鲜新特性、新硬件支持
发行版定制(如 Ubuntu -generic)跟随发行版生命周期(5-10 年)6.8.0-49-generic绝大多数桌面与服务器用户

企业发行版(Ubuntu LTS、RHEL)基于某个 LTS 内核持续回填安全补丁,普通用户跟随发行版仓库升级即可,无需手动追主线。

内核参数生效验证sysctl -a 可导出全部参数,sysctl net.ipv4.ip_forward 查询单项;注意 sysctl -p 文件 只加载文件里声明的项,不会清空其他运行时修改。判断持久化是否成功:修改后重启,再查询同一参数确认值仍在。

示例代码

# /proc 系统监控
cat /proc/meminfo | head -5
# 输出: MemTotal: 16310348 kB
# 输出: MemFree: 8245672 kB
cat /proc/loadavg
# 输出: 0.12 0.08 0.05 1/456 12345

# /proc/pid 进程信息
cat /proc/$$/status

# /sys 查看和设置 I/O 调度器
cat /sys/block/sda/queue/scheduler
# 输出: [mq-deadline] kyber none
echo none > /sys/block/sda/queue/scheduler

# 模块管理
lsmod | head -5
# 输出: Module                  Size  Used by
# 输出: nf_tables             245760  2
modinfo nf_tables
# 输出: filename:       /lib/modules/6.8.0-49-generic/kernel/net/netfilter/nf_tables.ko
# 输出: description:    Netfilter nf_tables subsystem
modprobe -v xfs
# 输出: insmod /lib/modules/6.8.0-49-generic/kernel/fs/xfs/xfs.ko
# udev 规则: 固定 USB 网卡名为 eth-custom
# /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:11:22:33:44:55", NAME="eth-custom"

# 重新加载生效
udevadm control --reload-rules && udevadm trigger
# sysctl 调优常用集合
sysctl -w net.core.somaxconn=65535
# 输出: net.core.somaxconn = 65535
sysctl -w fs.file-max=2097152
# 输出: fs.file-max = 2097152
sysctl -w vm.swappiness=10
# 输出: vm.swappiness = 10
sysctl -p
# 输出: net.core.somaxconn = 65535
# 输出: fs.file-max = 2097152
# 输出: vm.swappiness = 10
# 内核升级完整流程
uname -r
# 输出: 6.8.0-48-generic
apt update && apt install -y linux-image-6.8.0-49-generic
# 输出: ... Unpacking linux-image-6.8.0-49-generic ... Setting up ...
grep menuentry /boot/grub/grub.cfg | head -5
# 输出: menuentry 'Ubuntu' --class ubuntu ... $menuentry_id_option 'gnulinux-simple-xxx'
# 输出: menuentry 'Ubuntu, with Linux 6.8.0-49-generic' ...
# 输出: menuentry 'Ubuntu, with Linux 6.8.0-48-generic' ...
update-grub
# 输出: Sourcing file /etc/default/grub
# 输出: Generating grub configuration file ... Found linux image: /boot/vmlinuz-6.8.0-49-generic
reboot

sysctl 常用参数分类表

分类参数作用
网络net.ipv4.tcp_syncookiesSYN Flood 防护(生产默认 1)
net.ipv4.ip_local_port_range客户端可用端口范围,高并发出站连接需调大
net.ipv4.tcp_fin_timeoutFIN-WAIT-2 等待秒数,缩短可释放连接槽
内存vm.swappinessswap 使用倾向(0-100),服务器常用 10
vm.dirty_ratio / vm.dirty_background_ratio脏页回写阈值,影响磁盘写入突发与 IO 卡顿
文件系统fs.file-max系统级最大文件句柄数(默认约 2^20)
内核kernel.core_patterncore dump 文件命名与存放路径
kernel.pid_max最大 PID 号,容器化/高进程数场景需调大

高性能服务器参数集合

# /etc/sysctl.d/99-performance.conf
# TCP 缓冲区(BDP = 带宽 × 延迟,高吞吐链路需放大)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 连接队列与 TIME_WAIT 复用
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1

# 文件句柄上限
fs.file-max = 2097152

# OOM 行为:0 = 内核按评分杀进程(默认);2 = 触发 OOM 时不杀,直接 panic
vm.panic_on_oom = 0
kernel.panic = 10    # 内核 panic 后 10 秒自动重启,减少停机

# 立即生效
sysctl -p /etc/sysctl.d/99-performance.conf

模块加载管理

# 开机自动加载模块:/etc/modules-load.d/*.conf
echo "wireguard" > /etc/modules-load.d/wireguard.conf
systemctl restart systemd-modules-load.service
lsmod | grep wireguard

# 模块参数:/etc/modprobe.d/*.conf
# 语法:options 模块名 参数名=值
options bonding miimon=100 mode=active-backup

# 黑名单(驱动冲突、硬件误导时禁用):
# /etc/modprobe.d/blacklist-bluetooth.conf
blacklist btusb

modprobe 与 insmod 的区别modprobe 依据 /lib/modules/$(uname -r)/modules.dep(由 depmod 生成)自动解析依赖并递归加载;insmod 只能加载单个 .ko 文件、不解析依赖,仅适合调试场景。modprobe -r 递归卸载整条依赖链,比 rmmod 更安全。

# 查看模块依赖与元信息
modinfo -d bonding
depmod -a                # 安装新模块/内核后重建依赖索引
modprobe -n -v bonding   # 预演加载(-n = dry run),不真正执行

模块加载链:开机时 systemd 读取 /etc/modules-load.d/ 自动加载模块,/etc/modprobe.d/ 控制加载参数与黑名单,同名项按文件名字典序后加载覆盖先加载。内核发现新硬件时(udev uevent)也会按需自动加载对应驱动——这就是插入 USB 设备驱动"立即可用"的原因。

GRUB 默认内核与回滚

# 查看已安装内核与当前使用
dpkg --list | grep linux-image
uname -r

# 指定默认启动项(/etc/default/grub)
GRUB_DEFAULT=1            # 按菜单序号(0 为第一项)
# 更稳妥的用法:始终启动"上次选中的内核"
GRUB_DEFAULT=saved
GRUB_SAVEDEFAULT=true
update-grub

# 仅下一次启动用指定项,用于验证新内核后再回滚
grub-set-default 1
grub-reboot 1
reboot

# 回滚后清理故障内核
apt remove --purge linux-image-6.8.0-49-generic
apt autoremove && update-grub

RHEL / Rocky 系内核升级(dnf)

dnf check-update kernel          # 查看可用更新
dnf update -y kernel             # 升级内核(新内核作为追加引导项,不覆盖旧版)
dnf list installed kernel        # 列出已安装内核

# 查看所有引导项序号与内核对应关系
grubby --info=ALL | grep -E "^index|kernel"
grub2-set-default 0              # 设置默认引导项
grub2-mkconfig -o /etc/grub2.cfg # RHEL 系重建配置(UEFI 系统为 /boot/efi/.../grub.cfg)
reboot

RHEL 系默认保留已安装的全部内核版本,回滚只需 grub2-set-default 对应序号 后重启;清理旧内核用 dnf remove kernel-旧版本号(保留最近两个为宜)。

启动参数(内核命令行)

内核命令行参数写在 /etc/default/grubGRUB_CMDLINE_LINUX 中,update-grub 重建引导项后生效。常用参数:

参数用途
quiet / splash隐藏启动日志 / 显示开机画面(排障时去掉)
nomodeset禁用显卡模式设置,解决黑屏、花屏
noapic / nolapic禁用 APIC,老硬件中断异常时的兜底方案
nokaslr关闭内核地址空间随机化,便于内核调试(生产勿用)
systemd.unit=multi-user.target跳过图形界面直达命令行,图形崩溃时急救
init=/bin/bash用 bash 替代 init,密码重置等紧急修复(注意手动挂载根分区)
rdbreak在 initramfs 阶段暂停,排查根文件系统挂载失败
ip=dhcpinitramfs 阶段配置网络(网络引导、NFS 根)
# /etc/default/grub 示例
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX="net.ipv4.tcp_congestion_control=bbr"
update-grub

# 查看当前实际生效的参数
cat /proc/cmdline
# 输出: BOOT_IMAGE=/boot/vmlinuz-6.8.0-49-generic root=UUID=xxx ro quiet splash

# 临时修改:GRUB 菜单按 e 编辑引导项,在 linux 行追加参数后 Ctrl+X 启动,
# 重启即失效——排障时比改配置文件更安全

查看启动日志dmesgjournalctl -k -b 都能读取内核环形缓冲,用于确认启动参数是否生效、驱动是否加载失败:dmesg | grep -i errordmesg | grep -i bbr

紧急修复实例(忘记 root 密码):GRUB 菜单按 e 编辑引导项,在 linux 行末尾追加 init=/bin/bashCtrl+X 启动。进入单用户 bash 后先执行 mount -o remount,rw / 把根分区改为可写,再 passwd root 重置密码,最后 exec /sbin/init 恢复正常引导。

内核编译简介

绝大多数场景使用发行版内核即可;编译内核适合需要定制驱动、启用安全特性或体验最新功能的高级场景。完整流程(视硬件 30 分钟 - 2 小时):

# 1. 获取源码(以 kernel.org 主线为例)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.1.tar.xz
tar xf linux-6.12.1.tar.xz && cd linux-6.12.1

# 2. 以当前内核配置为起点(最省事)
cp /boot/config-$(uname -r) .config
make olddefconfig

# 3. 图形化调整选项
make menuconfig
# General setup → Local version 给内核打自定义后缀
# Device Drivers → Network device support → WireGuard
# File systems → 勾选需要的文件系统(ext4 / xfs / btrfs)

# 4. 编译安装
make -j$(nproc)            # 按核数并行
make modules_install       # 模块装入 /lib/modules/6.12.1-custom/
make install               # 生成 /boot/vmlinuz + initramfs 并更新 GRUB

# 5. 验证
uname -r
# 输出: 6.12.1-custom

注意事项:编译前先 apt build-dep linux 安装依赖(gcc、make、flex、bison、libncurses-dev);驱动优先编译为模块(<M>)而非内建(*),便于后续单独升级;make install 后务必确认 GRUB 菜单保留旧内核可回退;源码与编译产物约需 10 GB 磁盘空间。

常见编译报错No rule to make target 多为缺依赖(重装 flex / bison / libssl-dev);Killed 是内存不足,调低 -j 并行度或加大 swap;permission denied 检查源码目录权限——不要以 root 解压到 /root 之外再以普通用户编译。

OOM 行为与排查:内存不足时 OOM Killer 按 oom_score 选进程杀死,内核日志形如 Out of memory: Killed process 1234 (java)。对关键进程豁免:echo -17 > /proc/1234/oom_score_adj(-1000 完全豁免,仅 root 可写)。观察历史 OOM 事件:journalctl -k | grep -i "out of memory"

常见错误

现象原因解决
modprobe: FATAL: Module not found模块名拼写错误或内核版本不匹配modprobe -l | grep keyword 确认正确名称;检查 uname -r 版本
rmmod: ERROR: Module is in use有其他模块或进程依赖目标模块lsmod | grep module_name 查看 Used by 列;先停止依赖方或 modprobe -r 递归卸载
unable to load keyringSecureBoot 启用且模块未签名进 BIOS 关闭 SecureBoot,或用 mokutil 导入签名
udev 规则不生效规则名优先级或匹配条件有误udevadm test 模拟测试;文件名数字越大优先级越低(99-*.rules 最后执行)
sysctl: permission denied非 root 或无权限写入对应参数sudo;部分安全参数受 kernel.unprivileged_* 限制
modprobe 报 FATAL 但模块文件存在依赖索引过期或依赖模块缺失depmod -a 重建 modules.dep 后重试
升级内核后第三方驱动(显卡)失效dkms 模块未针对新内核重建dkms status 查看;安装 dkms 后重装驱动源码包
新内核启动进入 initramfs 紧急模式根文件系统驱动缺失或 /etc/fstab 错误GRUB 高级选项回退旧内核重建 /etc/fstab 或用 live CD 修复
sysctl 重启后参数丢失只用了 sysctl -w 运行时修改,未持久化写入 /etc/sysctl.d/*.conf 并重启后复查
自编译内核启动后驱动缺失驱动编为内建(*)或未执行 modules_install驱动优先用 <M>;依次执行 make modules_installmake install

最佳实践

  • 只读优先:除了明确的调优场景,生产环境只在 /proc / /sys 上做只读操作;写操作前充分理解副作用。
  • 模块黑名单:禁用不必要的内核模块(如 pcspkrfloppy),通过 /etc/modprobe.d/blacklist-*.conf 管理,不要直接编辑 /etc/modprobe.conf
  • udev 规则命名:使用 70- / 80- / 99- 前缀控制规则优先级。自定义规则统一放 /etc/udev/rules.d/,不动 /usr/lib/udev/rules.d/
  • sysctl 分文件:按功能域拆分 /etc/sysctl.d/ 下的文件(如 90-network.conf90-memory.conf),不要全部塞进 /etc/sysctl.conf
  • 内核保留:升级后保留近两个旧内核(apt-mark hold linux-image-* 选定版本),清理前确认 GRUB 菜单可回退。
  • 先测试再持久:sysctl 调优参数先用 sysctl -w 运行时验证稳定性,再写入配置文件持久化。
  • 升级窗口:生产内核升级选业务低谷,升级后先在新内核下跑完整冒烟测试,确认无回归再清理旧内核。
  • 记录基线:调优前用 sysctl -a > baseline.txt 保存参数基线,升级内核或排查性能回归时便于比对差异。
  • 留后路:任何涉及引导层面的变更(内核参数、默认内核)都先确认 GRUB 菜单存在可用的旧项,变更后重启验证再固化。

练习题

  1. 运行 cat /proc/loadavg,解释三个数值分别代表什么时间间隔的平均负载。
  2. /proc 文件系统找出当前 bash 进程的 PID、PPID 和状态。
  3. 写一条 udev 规则:当 USB 存储设备 /dev/sdb 插入时,自动将其挂载到 /mnt/usb 并设置权限为 0770
  4. sysctl 查看当前系统的 net.ipv4.ip_forward 值,开启 IP 转发后使其永久生效。
  5. 安装一个新内核(apt install linux-image-generic-hwe-*),重启后用 uname -r 验证,再回退到旧内核启动。
  6. 查看已加载的模块中找到 snd 相关的所有模块,用 modinfo 查看其中任意一个模块的描述。
  7. 通过 /sys/class/net/ 找到主网卡的 MAC 地址和当前链路速率。
点击查看答案
  1. 三个字段分别代表过去 1 分钟、5 分钟、15 分钟的平均活跃进程数(R 状态 + D 状态)。值高于 CPU 核数表示过载。
  2. cat /proc/self/status 查看自己的进程信息,Pid 当前 PID,PPid 父进程 PID,State 当前状态(R/S/D/Z)。
  3. /etc/udev/rules.d/99-usb-automount.rules: ACTION=="add", KERNEL=="sd[b-z]", SUBSYSTEM=="block", RUN+="/usr/bin/mount /dev/%k /mnt/usb"。权限用 MODE="0770"
  4. sysctl net.ipv4.ip_forward 查看,sysctl -w net.ipv4.ip_forward=1 临时开启。/etc/sysctl.conf/etc/sysctl.d/99-ipforward.confnet.ipv4.ip_forward=1 持久化。
  5. 安装 HWE 内核后 uname -r 显示新版本。回退:GRUB 启动菜单选 Advanced → 选旧内核,进入后 apt remove linux-image-新版本
  6. lsmod | grep snd 列出 snd_hda_intel, snd_pcm 等。modinfo snd_hda_intel 显示描述、作者、依赖、参数等。
  7. cat /sys/class/net/eth0/address 显示 MAC 地址。cat /sys/class/net/eth0/speed 显示链路速率(如 1000 Mbps)。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 Linux 内核的模块化架构和内核参数尝试向他人讲解
命令操作能不查文档完成内核模块加载卸载、内核参数配置在终端实际执行
原理掌握能说出内核升级的流程和内核启动参数的作用画出流程图
故障排查能独立排查内核模块加载失败或新内核无法启动的问题模拟故障并修复
最佳实践能说明为什么生产环境需要保留旧内核作为回滚选项对比不同方案

本章总结

速查表

路径/命令用途
/proc/loadavg查看系统平均负载
/proc/[pid]/status查看进程状态和资源
/sys/class/net/查看网络设备信息
lsmod列出已加载的内核模块
modprobe加载/卸载内核模块
udevadm管理和调试 udev 规则
sysctl查询和调优内核参数

内核管理是 Linux 系统管理员的核心能力,覆盖了从信息采集到运行时调整再到系统升级的全链路。三个虚拟文件系统各有分工:/proc 管进程与系统状态,/sys 管设备拓扑,/proc/sys/+sysctl 管参数调优。内核模块提供了按需扩展能力,udev 则让设备管理自动化、可编程。升级内核时务必保留回退选项,始终遵循"先验证、再持久"的安全原则。

延伸阅读

  • 内核文档:https://www.kernel.org/doc/html/latest/
  • udev 手册:man 7 udevman 8 udevadm
  • sysctl 可调参数完整列表:sysctl -a | less
  • Linux 内核模块编程指南(The Linux Kernel Module Programming Guide)
  • Red Hat 企业版 Linux 性能调优指南(RHEL Performance Tuning Guide)

常见问题

/proc 中的文件可以直接修改吗?
部分可以。/proc/sys 下的文件(配合 sysctl)用于配置内核运行时参数:echo 1 > /proc/sys/net/ipv4/ip_forward 开启 IP 转发。其他如 /proc/PID/fd 是文件描述符符号链接,只读。误修改 /proc 可能影响系统稳定性,建议用 sysctl 工具而非直接写文件。
内核模块加载失败怎么排查?
modprobe module 失败时检查:① dmesg | tail 看内核日志(具体失败原因);② lsmod | grep module 看是否已加载;③ depmod -a 重建模块依赖;④ modinfo module 查看模块信息(作者、描述、依赖、参数)。常见失败原因:模块依赖的符号导出被禁用、内核版本不匹配、Secure Boot 拦截。
udev 规则是怎么工作的?
udev 在设备接入/移除时触发,规则文件在 /etc/udev/rules.d/(数字前缀控制优先级)。规则格式:ACTION=="add", ATTR{idVendor}=="1234", SYMLINK+="mydev", MODE="0660"。常用场景:USB 设备固定符号链接(避免 /dev/ttyUSB0 编号漂移)、网卡命名固定(避免重启后 eth0/eth1 互换)、设置设备权限。写完规则运行 udevadm control --reload-rules && udevadm trigger。
↑ 回到顶部