1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Linux 性能调优系列(一) 建立科学的性能分析思路
  2. Linux 性能调优系列(二) 掌握基础性能监控命令
  3. Linux 性能调优系列(三) sysstat实现性能数据持久采集
  4. Linux 性能调优系列(四) 别只会用 sysstat,PCP 才是 Linux 性能监控的进阶玩法
  5. Linux 性能调优系列(五) 给你的服务器做一次全面体检
  6. Linux 性能调优系列(六) 想调内核参数?先搞懂 /proc、sysctl 和 sysfs
  7. Linux 性能调优系列(七) 还在手动改 sysctl?TuneD 智能性能调优实战来了
  8. Linux 性能调优系列(八) 进程太能吃资源怎么办?用 ulimit 和 systemd 给它加个"上限"
  9. Linux 性能调优系列(九) cgroup v2实战:防止一个烂进程干崩整台服务器
  10. Linux 性能调优系列(十) CPU 100% 到底是谁在忙?用 perf 把"元凶"揪出来
  11. Linux 性能调优系列(十一) 程序为什么卡住了?strace 和 ltrace 带你找到答案
  12. Linux 性能调优系列(十二) 服务器又卡了?SystemTap 带你杀到内核看看为啥
  13. Linux 性能调优系列(十三) 从 SystemTap 到 eBPF,Linux 性能追踪进入新时代
  14. Linux 性能调优系列(十四) CPU 这么忙,到底是谁决定“让谁先跑”?一文搞懂 Linux 调度器
  15. Linux 性能调优系列(十五) CPU 到底该先给谁?搞懂 Linux 实时调度
  16. Linux 性能调优系列(十六) Linux CPU 隔离实战,为什么实时线程绑核后延迟还会抖?
  17. Linux 性能调优系列(十七) CPU 占用不高却跑的慢?深挖 CPU 缓存性能瓶颈
  18. Linux 性能调优系列(十八) 程序申请了1GB内存,为什么系统却说它没用?
  19. Linux 性能调优系列(十九) 服务器硬盘慢得像蜗牛?先别急着换硬盘,这四招先把瓶颈找出来
  20. Linux 性能调优系列(二十) 磁盘 I/O 调度器怎么选?一文搞懂 mq-deadline、kyber、bfq 和 none
  21. Linux 性能调优系列(二十一) 磁盘为什么慢?从文件系统到 I/O 调优,一次讲透
  22. Linux 性能调优系列(二十二) Linux 网络调优别乱改 sysctl!先找瓶颈再调 TCP 缓冲区
  23. Linux 性能调优系列(二十三) MTU 从 1500 到 9000,巨帧真的能提升性能吗?
  24. Linux 性能调优系列(二十四) 从 CPU 省电到网卡卸载,聊聊 Linux 网络调优
  25. Linux 性能调优系列(二十五) 聊聊VirtIO、TuneD、virtio-mem 调优

前面我们已经完成了服务器的硬件画像,也学会了使用 PCP 全套工具采集系统性能指标。当我们通过这些工具发现了系统瓶颈,下一步就要做一件事情——调整系统行为。而这就涉及今天的主角:内核可调项(Kernel Tunables)。

⚠️ 先提醒大家一句:

内核参数不是普通的应用配置。你改一个参数,实际上是在告诉 Linux 内核:

“以后遇到这种情况,你按照我说的方式处理。”

所以生产环境千万不要看到网上有一篇“Linux 性能优化参数大全”,然后直接复制一堆 sysctl 参数上线。每一个参数,都应该先搞清楚它控制什么、修改以后会产生什么影响。 否则所谓的“性能优化”,最后很可能变成“业务故障”。


什么是内核可调项?

我们先用一个比较容易理解的方式来认识它。可以把 Linux 内核想象成一台非常复杂的自动驾驶汽车。CPU、内存、网络、文件系统,这些都是汽车里面不同的控制系统。正常情况下,系统已经有一套默认的运行规则。

比如:

  • 内存什么时候开始使用 Swap?
  • TCP 接收缓冲区多大?
  • 网络收到 ICMP Ping 要不要回复?
  • 内核应该如何处理某些资源?
  • 某些驱动模块允许创建多少设备?

这些行为并不是完全固定的。Linux 给管理员提供了一套“控制面板”,让我们可以在系统运行过程中调整这些行为。这个“控制面板”,就是我们今天要学习的:内核可调项。


内核可调项到底在哪里?

在Linux中,很多内核可调项通过两个特殊的伪文件系统暴露出来:

  • /proc
  • /sys

这里要特别注意:

它们虽然看起来像普通目录、普通文件,但实际上并不是传统意义上的磁盘文件。这些伪文件系统主要存在于内存中。它们更像是:

用户空间程序和 Linux 内核之间的一座桥梁。

所以你会发现一个非常有意思的现象:

1
cat /proc/xxx

看起来是在读取一个文件。实际上,你读取的并不是某个硬盘上的文本文件,而是内核把当前状态动态提供给你。同样:

1
echo '1' > /proc/sys/xxx

看起来像是在修改文件。

实际上,你是在告诉 Linux 内核:

“把这个运行参数改成 1。”

所以可以简单记住:

  • 读伪文件 = 读取内核当前运行状态
  • 写伪文件 = 修改内核正在运行的参数

还有一个非常重要的特点:

直接修改这些伪文件,修改结果只存在内存中。

服务器一旦重启,这些临时修改就没有了。


proc 伪文件系统:/proc

首先来看 /proc。

procfs 是 Linux 内核提供的一个伪文件系统,系统启动以后会自动挂载到 /proc

大家平时使用 Linux,应该经常见到它。

比如:

1
cat /proc/cpuinfo

可以看到 CPU 信息。

1
cat /proc/meminfo

可以看到内存信息。

1
ls /proc

甚至会看到大量数字目录。

这些数字通常就是正在运行的进程 PID。


/proc 里面的文件都是普通文件吗?

不是。

这是学习 /proc 时非常容易产生的一个误区,有些内容确实表现得很像普通文件,但是实际上,很多 /proc 文件背后对应的是 Linux 内核中的数据结构。

例如:

1
cat /proc/meminfo

你看到的这些数据,并不是某个程序提前写好一个文件,然后让你读取。而是你执行 cat 的时候,内核根据当前系统状态,把相关数据组织起来输出给你。所以:

不要把 /proc 简单理解成“存放配置文件的目录”。

它更像是 Linux 内核向用户空间提供的一扇观察窗口。


/proc 常见信息文件

我们先看几个非常常见的文件:

文件路径作用
/proc/cpuinfoCPU处理器硬件信息
/proc/meminfo物理内存、交换空间使用情况,free、vmstat底层读取这个文件
/proc/swaps交换分区使用状态
/proc/PID/以进程ID命名的子目录,存放单个进程全部信息
/proc/cmdline系统启动时内核命令行参数

比如我们执行:

1
cat /proc/cpuinfo

你可以理解成:

“Linux 内核,你把当前 CPU 的相关信息告诉我。”

再比如:

1
cat /proc/meminfo

实际上就是在询问内核:

“当前系统内存到底是什么状态?”


真正和内核参数关系最大的目录:/proc/sys

虽然 /proc 里面有大量系统信息,但是我们今天真正重点关注的是:/proc/sys,这个目录就是 Linux 内核可调项非常重要的入口。 /proc/sys 下面的文件,很多都对应一个可以动态调整的内核参数。可以把它理解成:

/proc 是 Linux 内核提供给我们的“信息中心”,而 /proc/sys 更像是其中的“控制面板”。


/proc/sys 下面有哪些重要目录?

常见的几个目录:

目录对应子系统主要用途常见内容
/proc/sys/dev设备设备相关的内核可调项RAID、SCSI 等设备参数
/proc/sys/fs文件系统文件系统相关参数文件句柄、目录项等文件系统行为
/proc/sys/kernelLinux 内核内核本身的基础行为共享内存、内核基础行为等
/proc/sys/net网络协议栈网络相关的内核参数TCP、UDP、ICMP 等网络行为
/proc/sys/vm虚拟内存虚拟内存管理Swap、脏页、大页等

案例:临时调整内核参数

下面我们通过一个非常直观的例子来理解。假设现在有一个需求:

服务器不希望响应 Ping。

注意,我们这里主要是为了演示内核参数的修改方式。对应的参数是:

1
net.ipv4.icmp_echo_ignore_all

它对应的 /proc/sys 文件:

1
/proc/sys/net/ipv4/icmp_echo_ignore_all

第一步:查看当前值

执行:

1
2
[root@localhost ~]# cat /proc/sys/net/ipv4/icmp_echo_ignore_all
0

这里的 0 表示:

正常处理 ICMP Echo Request,也就是允许服务器响应 Ping。

我们先测试一下:

1
2
3
4
5
6
7
[root@localhost ~]# ping -c1 192.168.8.128
PING 192.168.8.128 (192.168.8.128) 56(84) bytes of data.
64 bytes from 192.168.8.128: icmp_seq=1 ttl=64 time=0.034 ms

--- 192.168.8.128 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.034/0.034/0.034/0.000 ms

正常情况下可以收到回复。


第二步:直接修改参数

现在我们把参数修改成:1

执行:

1
[root@localhost ~]# echo '1' > /proc/sys/net/ipv4/icmp_echo_ignore_all

这个命令是什么意思?

简单说就是:

把 /proc/sys/net/ipv4/icmp_echo_ignore_all 这个内核参数设置成 1。

再执行:

1
2
3
4
5
[root@localhost ~]# ping -c1 192.168.8.128
PING 192.168.8.128 (192.168.8.128) 56(84) bytes of data.

--- 192.168.8.128 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

这时候你会发现:

Ping 不再收到回复。

因为我们刚刚已经告诉 Linux 内核:

“不要响应 ICMP Echo Request。”


这里有一个非常重要的知识点,刚才我们使用:

1
echo '1' > /proc/sys/...

修改的参数,并没有写到磁盘上的配置文件里。

它只是修改了:

当前正在运行的 Linux 内核。

所以服务器重启以后,这个修改就没有了。这就是我们经常说的:

临时生效。

有些内核参数不是一个数字,大家不要认为 /proc/sys 下面所有参数都是:

1
0

或者:

1
1

有些参数可能包含多个数值。

例如:

1
2
[root@localhost ~]# cat /proc/sys/net/ipv4/tcp_rmem
4096 131072 6291456

我来介绍一下这个参数吧,tcp_rmem 可以理解成 Linux 内核给 TCP 接收数据准备的“内存缓冲区大小控制参数”,tcp_rmem 不是一个单独的数值,而是由 三个数字组成:

数值含义说明
第一个最小值 minTCP 接收缓冲区允许使用的最小内存
第二个默认值 defaultTCP 连接通常使用的默认接收缓冲区大小
第三个最大值 maxTCP 接收缓冲区能够增长到的最大值

单位是:字节。,也就是说,TCP 接收缓冲区这里不是简单的一个开关,而是一组参数。可以把 TCP 接收缓冲区想象成一个仓库。比如服务器正在接收一个大文件:

1
2
3
4
5
6
7
8
9
10
客户端
│
│ TCP 数据
▼
┌──────────────┐
│ TCP接收缓冲区 │ ← 内核临时存放收到的数据
└──────────────┘
│
▼
应用程序读取

网络数据到达服务器之后,并不是直接塞进你的应用程序。通常会先进入 Linux 内核中的 TCP 接收缓冲区,然后应用程序再从这里读取数据。

所以:

tcp_rmem 控制的,就是 TCP 接收方向能够使用多少内核内存作为缓冲区。

有时候多个参数需要同时存在,如果我们希望把默认值调整一下,可以:

1
echo '4096    131072  6291456' > /proc/sys/net/ipv4/tcp_rmem

注意:

这里必须完整写入三个值。,不能想当然地只写一个:

1
echo '6291456' > /proc/sys/net/ipv4/tcp_rmem

这种参数本身就是由三组值组成的,所以修改的时候需要按照它要求的格式提供完整参数。


为什么不能随便把 TCP 缓冲区调得特别大?

假设你有一台服务器:

1
1000 个 TCP 连接

每个连接都可能消耗一定的内核网络缓冲区。如果你把 TCP 缓冲区调得非常大,那么连接数一多,消耗的内核内存也会跟着增加。

就像一个餐厅,原来每桌准备一个普通大小的储物柜,现在你为了“提高效率”,给每一桌都换成一个巨大的储物柜,单独看一桌,好像没有问题,但是如果餐厅有 1000 桌:

每桌都变大以后,整个餐厅的空间就可能被这些储物柜占满。

服务器也是一样。TCP 缓冲区变大,不代表一定性能更好。,在大流量网络场景下,它可能有助于提高吞吐能力。但是如果服务器同时存在大量并发连接,就可能消耗大量内核内存。最终甚至可能:

网络性能参数调好了,但是业务应用的内存被挤没了。

所以:调内核参数一定要结合实际业务负载。


sysctl:更方便地操作内核参数

刚才我们修改参数使用的是:

1
echo '1' > /proc/sys/xxx

这种方式虽然直接,但是有一个问题:太麻烦。,比如:

1
/proc/sys/net/ipv4/icmp_echo_ignore_all

路径这么长。如果以后每天都要操作几十个参数,光敲路径都够累的。所以 Linux 提供了一个专门的工具:sysctl,它可以让我们不用直接操作 /proc/sys 文件,而是直接使用参数名称进行管理。


参数名称和文件路径是什么关系?

sysctl 使用的是点号分隔的参数名称,而 /proc/sys 使用的是目录层级结构。它们实际上是一一对应的。例如:

1
2
sysctl 参数名称:
net.ipv4.icmp_echo_ignore_all

对应:

1
/proc/sys/net/ipv4/icmp_echo_ignore_all

大家仔细看一下就会发现一个规律:

参数名称中的 .,对应 /proc/sys 路径中的 /。

也就是说:

1
2
3
net.ipv4.icmp_echo_ignore_all
↓ ↓ ↓
net/ipv4/icmp_echo_ignore_all

最终就变成:

1
/proc/sys/net/ipv4/icmp_echo_ignore_all

所以大家以后看到一个 sysctl 参数的时候,不需要死记它对应哪个文件,按照这个规则转换一下就可以了。

比如:

1
vm.swappiness

对应:

1
/proc/sys/vm/swappiness

再比如:

1
net.ipv4.tcp_rmem

对应:

1
/proc/sys/net/ipv4/tcp_rmem

这样一来,sysctl 和 /proc/sys 其实就是同一套内核参数的两种操作方式:

1
2
3
4
5
6
7
              Linux 内核参数
│
┌────────┴────────┐
│ │
/proc/sys 文件 sysctl 命令
│ │
/proc/sys/net/ipv4/... net.ipv4....

这样使用 sysctl 就方便多了。


常用 sysctl 操作

1. 查看全部内核可调项

执行:

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# sysctl -a | more
abi.vsyscall32 = 1
crypto.fips_enabled = 0
crypto.fips_name = Rocky Linux 10 - Kernel Cryptographic API
crypto.fips_version = 6.12.0-211.16.1.el10_2.0.1.x86_64
debug.exception-trace = 1
debug.kprobes-optimization = 1
dev.cdrom.autoclose = 1
dev.cdrom.autoeject = 0
dev.cdrom.check_media = 0
dev.cdrom.debug = 0

这个命令会把系统当前的内核可调项以及对应值全部列出来。参数比较多,所以实际工作中一般不会只盯着完整输出看,而是结合具体参数进行查询。


2. -n:只输出参数值

例如我们想看看:vm.swappiness,当前是多少。可以:

1
2
3
[root@localhost ~]# sysctl -n vm.swappiness
60

这里的 -n 可以理解成:

“我只要结果,不要把参数名称也打印出来。”


vm.swappiness 是干什么的?这个参数和 Linux 内存管理有关。简单理解:

它控制 Linux 使用 Swap 的积极程度。

数值越低,Linux 越倾向于:

尽量少使用 Swap。

我们可以临时调整成10:

1
2
3
4
5
[root@localhost ~]# sysctl -w vm.swappiness=10
vm.swappiness = 10
[root@localhost ~]# sysctl -n vm.swappiness
10

这时候参数已经修改成功。


3. -w:临时修改内核参数

这里最关键的就是:

1
sysctl -w 参数=值

例如:

1
sysctl -w vm.swappiness=10

它和前面的:

1
echo '10' > /proc/sys/vm/swappiness

本质上是在修改同一个内核参数。区别主要在于:

sysctl 更方便。

但是一定要记住:

sysctl -w 修改的也是当前运行中的内核参数。

所以:

重启以后仍然会丢失。


4. 如何让 sysctl 参数永久保存?

这时候问题来了。如果我测试:

1
sysctl -w vm.swappiness=10

发现业务运行正常。那么总不能每次服务器重启以后,都让运维人员手工再执行一次吧?当然不行。所以我们需要:

持久化配置。

也就是把参数写入配置文件。

假设我们经过测试以后,确定:

1
vm.swappiness=10

符合当前服务器的业务需求。

那么我们可以创建:

1
/etc/sysctl.d/swappiness.conf

执行:

1
2
3
cat > /etc/sysctl.d/swappiness.conf <<EOF
vm.swappiness=10
EOF

这样我们就把配置永久保存下来了,但是现在并没有生效,得重启服务器。


不重启服务器,如何让配置立即生效?

配置文件写完以后,难道必须重启服务器?当然不是。可以直接:

1
2
[root@localhost ~]# sysctl -p /etc/sysctl.d/swappiness.conf
vm.swappiness = 10

这条命令的意思就是:

读取刚才这个配置文件,然后把里面的参数加载到当前运行中的内核。

所以现在就可以同时做到:配置永久保存 + 当前立即生效。

如果直接执行 sysctl -p 呢?如果执行:

1
sysctl -p

不指定具体文件,那么会按照系统的配置机制加载相关配置文件。所以实际生产环境中,我们既可以指定某个配置文件进行加载,也可以按照系统配置机制统一加载。

sysctl 配置文件加载优先级

Linux中常见的配置目录有:

1
2
3
/etc/sysctl.d/*.conf
/run/sysctl.d/*.conf
/usr/lib/sysctl.d/*.conf

按照优先级从高到低:

  1. /etc/sysctl.d/*.conf
  2. /run/sysctl.d/*.conf
  3. /usr/lib/sysctl.d/*.conf

这里大家一定要记住:

高优先级配置可以覆盖低优先级配置。


/etc/sysctl.d/

这是管理员进行自定义配置最重要的位置。也就是说:

如果是我们自己要调整 Linux 内核参数,优先考虑在这里创建配置文件。


/run/sysctl.d/

这个目录用于运行时配置。它的特点就是:

重启以后会丢失。

所以它更适合运行期间产生的临时配置。


/usr/lib/sysctl.d/

这个目录主要是软件包或者供应商提供的默认配置。这里尤其需要注意:

不要直接修改 /usr/lib/sysctl.d/ 里面的软件包配置文件。

为什么?

因为这些文件属于系统或者软件包提供的默认配置。以后软件升级,很可能覆盖你的修改。正确做法是什么?

如果你希望修改某一个默认参数:

在 /etc/sysctl.d/ 创建自己的配置文件。 利用配置优先级覆盖默认值。


sysfs:/sys 和 /proc 有什么区别?

到这里,我们已经讲了:

1
2
3
/proc
/proc/sys
sysctl

但是 Linux 还有另外一个非常重要的伪文件系统:

1
/sys

它就是:

sysfs。

它和 /proc 有什么区别?

这个地方非常容易混。可以简单这么理解:

/proc 更偏向于系统运行状态、进程信息和全局内核参数。

而:

/sys 更偏向于硬件设备、设备树、内核模块以及设备相关参数。

所以:

1
/proc

你可以更多地理解为:

“系统现在运行得怎么样?”

而:

1
/sys

更像:

“系统里面有哪些硬件、设备和内核模块,它们之间是什么关系?”


sysfs 里面有哪些重要目录?

sysfs 下面有几个比较常见的目录,大家可以先记住它们分别对应什么:

目录主要内容怎么理解
/sys/module当前已经加载的内核模块信息看内核模块
/sys/devices系统硬件设备的完整拓扑看硬件设备
/sys/bus各类硬件总线的信息看硬件总线
/sys/dev块设备、字符设备等设备信息看设备节点

这里大家不用一上来就把 /sys 下面所有目录都背下来,先建立一个最简单的印象就够了。

看到 /sys/module,想到 内核模块。
看到 /sys/devices,想到 硬件设备。
看到 /sys/bus,想到 硬件总线。
看到 /sys/dev,想到 设备信息。


修改内核模块参数

Linux 很多功能都是通过内核模块实现的。这些模块有时候会提供一些可以配置的参数。例如:

1
loop

loop 模块用于把普通文件映射成块设备(如 /dev/loop0),从而可以像磁盘一样对文件进行挂载和读写。我们先使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@localhost ~]# modinfo loop
filename: /lib/modules/6.12.0-211.16.1.el10_2.0.1.x86_64/kernel/drivers/block/loop.ko.xz
alias: devname:loop-control
alias: char-major-10-237
alias: block-major-7-*
license: GPL
description: Loopback device support
rhelversion: 10.2
srcversion: 0FFA7B05595962D1693C751
depends:
intree: Y
name: loop
retpoline: Y
vermagic: 6.12.0-211.16.1.el10_2.0.1.x86_64 SMP preempt mod_unload modversions
sig_id: PKCS#7
signer: Rocky Linux kernel signing key
sig_key: 1E:24:82:87:11:EB:1E:95:4C:A8:A5:B4:32:2F:EA:F3:72:6C:B8:90
sig_hashalgo: sha256
signature: 20:7D:58:88:E9:2F:BB:25:F9:77:4C:01:8E:B6:BA:FE:53:E7:2C:EE:
13:40:A4:F4:00:5D:EA:FF:48:3E:69:C9:9C:F9:CD:6B:2B:FA:91:AC:
0C:EA:C2:F1:30:AD:23:82:47:2D:30:66:1A:7A:4A:24:9B:38:45:E5:
30:7F:24:4C:46:5F:70:D5:24:0C:BB:FB:2C:39:3E:44:43:F2:83:17:
37:C9:B9:CC:D7:55:C4:2D:71:90:8F:1E:D7:2B:D3:2C:0C:3A:F7:0A:
58:71:0D:03:4D:D8:7D:E4:9D:8A:47:19:86:6B:A3:03:62:4D:11:ED:
D3:3B:9F:29:23:22:EA:09:C1:37:00:E0:BB:A3:5F:ED:3A:92:0D:95:
7C:45:B0:25:00:46:4A:DE:33:57:50:9C:06:4D:53:73:AC:D7:01:C6:
C7:4E:A6:32:C8:C8:80:4D:50:C8:3E:F7:6A:C2:F3:85:07:36:58:68:
AF:4B:01:1B:E1:11:A9:2B:2C:25:85:1C:82:73:8D:52:A7:B8:E8:62:
1F:57:10:EC:0A:16:51:9F:B7:5D:64:20:32:32:8B:03:12:39:0D:20:
D1:7F:F5:FF:3D:74:25:90:2B:FD:84:31:F3:A0:CE:6B:E7:41:4D:D8:
A1:62:E0:0A:56:A6:A2:E3:0D:CA:A8:41:A6:03:A3:AB:86:92:E7:22:
A7:28:17:2C:3C:2C:A6:F5:48:60:AB:C8:4A:85:60:26:43:D9:63:E5:
26:CC:D4:39:F1:70:16:11:4C:F7:BD:0E:9F:F5:B1:72:DA:38:36:72:
1F:2B:87:38:CC:69:7E:F7:E1:0A:F1:D0:72:0D:2F:2B:55:1A:45:8A:
8A:C8:D5:C4:90:95:E9:A6:DA:60:47:A9:16:70:26:9D:CD:0A:17:2F:
E7:2E:8D:7A:78:55:69:54:8B:30:DF:3C:BD:F0:60:36:33:FA:B9:64:
97:70:28:9C:2A:7C:E3:6D:D2:89:69:AF:57:C8:F2:09:C0:6F:1F:7A:
A8:E3:BC:00
parm: max_loop:Maximum number of loop devices
parm: max_part:Maximum number of partitions per loop device (int)
parm: hw_queue_depth:Queue depth for each hardware queue. Default: 128

查看模块信息。如果我们只关心:

“这个模块到底支持哪些参数?”

可以使用:

1
2
3
4
5
[root@localhost ~]# modinfo -p loop
max_loop:Maximum number of loop devices
max_part:Maximum number of partitions per loop device (int)
hw_queue_depth:Queue depth for each hardware queue. Default: 128

这样我们就知道:

loop 模块提供了哪些可以调整的参数。


模块已经加载,还能改参数吗?

如果模块已经加载,那么这些模块参数通常会暴露到:

1
/sys/module/<模块名>/parameters/

例如:

1
/sys/module/loop/parameters/

查看:

1
2
3
[root@localhost ~]# cat /sys/module/loop/parameters/max_loop
0

这就是当前 loop 模块对应参数的值。


如果对应文件具备写权限,那么可以直接:

1
echo '5' > /sys/module/loop/parameters/max_loop

这样就是直接修改当前已经加载模块的参数。但是注意:

这种修改也是临时的。

服务器重启以后会失效。而且重新加载模块以后,这个临时修改同样可能失效。所以这种方式更适合:

先临时测试参数修改是否符合预期。


模块参数不能使用 sysctl 来管理。这是一个需要特别区分的知识点。如果是:

1
sysctl 类内核参数

我们使用:

1
/etc/sysctl.d/*.conf

进行持久化。而如果是:

1
内核模块参数

那么应该使用:

1
/etc/modprobe.d/*.conf

进行持久化。


实操:永久设置 loop 模块参数

例如:

希望 loop 模块加载的时候,把 max_loop 设置成 5。

我们创建:

1
/etc/modprobe.d/loop.conf

内容:

1
options loop max_loop=5

也就是说:

1
2
3
4
5
6
7
8
9
10
11
options
↓
告诉 modprobe 这是模块参数配置

loop
↓
模块名称

max_loop=5
↓
加载模块时传入的参数

为什么写完配置以后没有马上变化?

这是因为:

1
/etc/modprobe.d/loop.conf

配置的是:

模块加载阶段的参数。

它不是像 sysctl 那样,写完以后自动修改已经运行中的内核参数。如果 loop 模块已经加载,那么你刚才创建配置文件以后:

1
2
3
已经加载的 loop
↓
不会自动重新读取配置

所以如果需要让新的模块参数生效,就需要重新加载模块。


临时加载模块时直接传参数

还有一种方法。如果我们只是临时测试,不想写配置文件,可以:

1
modprobe loop max_loop=5

意思就是:

加载 loop 模块的时候,同时把 max_loop 设置成 5。

这种方式同样是临时的。重启以后不会自动保留。


内核参数看不懂怎么办?

实际工作中,大家肯定会遇到这种情况:

看到一个参数:

1
2
3
vm.xxx
net.ipv4.xxx
kernel.xxx

完全不知道是什么意思。这个时候不要第一反应:

“网上搜一下别人怎么配。”

更好的方式是:看官方文档。 可以安装:

1
2
3
4
5
6
7
[root@localhost ~]# dnf install kernel-doc -y
...
Installed:
kernel-doc-6.12.0-211.60.1.el10_2.noarch

Complete!

安装完成以后,相关文档会放在:

1
/usr/share/doc/kernel-doc-*/Documentation/

看我的:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# ls /usr/share/doc/kernel-doc-6.12.0-211.60.1/Documentation/
ABI Changes dontdiff hid Kconfig misc-devices peci sphinx-static trace
accel CodingStyle driver-api hwmon kernel-hacking mm power spi translations
accounting conf.py fault-injection i2c leds netlabel process staging usb
admin-guide core-api fb iio litmus-tests netlink RCU SubmittingPatches userspace-api
arch cpu-freq features images livepatch networking rust subsystem-apis.rst virt
atomic_bitops.txt crypto filesystems index.rst locking nvdimm scheduler sunrpc w1
atomic_t.txt devicetree firmware_class infiniband maintainer nvme scsi target watchdog
block dev-tools firmware-guide input Makefile output security tee wmi
bpf doc-guide fpga isdn memory-barriers.txt PCI sound timers
cdrom docutils.conf gpu kbuild mhi pcmcia sphinx tools

这里面可以查阅内核相关文档。

包括:

  • 参数的作用
  • 参数取值
  • 使用场景
  • 相关说明

所以实际工作中遇到陌生参数:

先看文档,再决定是否修改。


临时生效 VS 持久生效

到这里我们已经讲了好几种方式。

把它们放到一起,就非常清楚了。

修改方式操作方法是否重启丢失适用场景
修改 /proc/sys 文件echo 值 > /proc/sys/xxx✅ 重启丢失临时测试参数效果
sysctl -wsysctl -w 参数=值✅ 重启丢失命令行快速临时调参
sysctl conf 配置/etc/sysctl.d/*.conf❌ 重启保留sysctl 类内核参数永久配置
sysfs 模块参数 echo 写入echo 值 > /sys/module/xxx/parameters/xxx✅ 重启丢失临时修改已经加载模块
modprobe.d 配置/etc/modprobe.d/*.conf❌ 重启保留模块加载时设置驱动参数

大家可以把它总结成一句话:

先临时改,确认有效以后,再持久化。


生产环境到底应该怎么调内核参数?

这一点非常重要。实际生产环境中,我非常不建议大家:

1
2
3
4
5
6
7
8
9
看到一篇文章
↓
复制几十个 sysctl 参数
↓
粘贴
↓
重启服务器
↓
祈祷性能变好

这种方式风险非常大。正确的思路应该是:

第一步:先发现问题

通过前面学习的 PCP、vmstat、iostat、网络指标等工具,确定:

系统到底哪里存在瓶颈。


第二步:找到对应的内核参数

例如确定问题与内存有关,那么再去研究:

1
/proc/sys/vm/

如果是网络问题,再去关注:

1
/proc/sys/net/

第三步:临时修改

先通过:

1
sysctl -w

或者直接:echo修改。为什么?因为这样可以先验证:

参数调整以后,业务到底有没有改善。


第四步:观察业务表现

不要参数改完,看到一个数字变化,就宣布:

“优化成功。”

真正需要观察的是:

  • 系统指标有没有改善
  • 应用性能有没有改善
  • 业务有没有受到影响
  • 内存消耗有没有异常
  • 网络连接有没有异常

第五步:确认有效以后再持久化

如果测试结果符合预期,再写:

1
/etc/sysctl.d/*.conf

或者:

1
/etc/modprobe.d/*.conf

让它在服务器重启以后继续生效。


最后总结一下

今天这一篇,我们实际上解决了一个非常核心的问题:

Linux 运行起来以后,我们到底怎么修改内核行为?

主要涉及三套东西:

1
2
3
4
5
6
7
8
9
10
11
/proc
↓
Linux 内核提供的运行状态和接口

/proc/sys + sysctl
↓
系统级内核可调参数

/sys
↓
硬件设备、设备树、内核模块以及相关参数

然后又进一步区分了:

1
2
3
4
5
6
7
8
9
10
临时修改
↓
echo
sysctl -w
/sys/module/.../parameters/...

持久化修改
↓
/etc/sysctl.d/*.conf
/etc/modprobe.d/*.conf

最重要的生产实践原则还是那句话:

不要为了“性能优化”而优化。

先通过监控发现问题,再找到对应的内核参数。

先临时调整、验证业务,确认有效以后,再进行持久化。

这样做,才是真正意义上的 Linux 性能调优。


参考 man 手册

1
2
3
4
5
6
7
8
man proc
man sysfs
man sysctl
man sysctl.conf
man sysctl.d
man modinfo
man modprobe
man modprobe.conf

这些手册建议大家实际敲一下。因为性能调优真正做到后面,很多时候不是:

“我记得这个参数应该设置成多少。”

而是:

“我知道应该去哪里查这个参数,它到底控制什么,以及修改以后可能产生什么影响。”


下一篇预告:

前面我们学习了如何手动维护 sysctl 配置。

但是如果一台服务器还好,几十台、几百台服务器呢?

难道每台机器都手工修改?

显然不现实。

下一篇我们就继续往前走,看看 TuneD 调优守护进程。

TuneD 内置了大量针对不同工作负载的调优模板,同时也支持自定义配置