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

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com

前面我们已经完成了服务器的硬件画像,也学会了使用 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 内置了大量针对不同工作负载的调优模板,同时也支持自定义配置