Linux 性能调优系列 (六) 想调内核参数?先搞懂 /proc、sysctl 和 sysfs
1 | 作者:李晓辉 |
前面我们已经完成了服务器的硬件画像,也学会了使用 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/cpuinfo | CPU处理器硬件信息 |
/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/kernel | Linux 内核 | 内核本身的基础行为 | 共享内存、内核基础行为等 |
/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 | [root@localhost ~]# cat /proc/sys/net/ipv4/icmp_echo_ignore_all |
这里的 0 表示:
正常处理 ICMP Echo Request,也就是允许服务器响应 Ping。
我们先测试一下:
1 | [root@localhost ~]# ping -c1 192.168.8.128 |
正常情况下可以收到回复。
第二步:直接修改参数
现在我们把参数修改成:1
执行:
1 | [root@localhost ~]# echo '1' > /proc/sys/net/ipv4/icmp_echo_ignore_all |
这个命令是什么意思?
简单说就是:
把
/proc/sys/net/ipv4/icmp_echo_ignore_all这个内核参数设置成1。
再执行:
1 | [root@localhost ~]# ping -c1 192.168.8.128 |
这时候你会发现:
Ping 不再收到回复。
因为我们刚刚已经告诉 Linux 内核:
“不要响应 ICMP Echo Request。”
这里有一个非常重要的知识点,刚才我们使用:
1 | echo '1' > /proc/sys/... |
修改的参数,并没有写到磁盘上的配置文件里。
它只是修改了:
当前正在运行的 Linux 内核。
所以服务器重启以后,这个修改就没有了。这就是我们经常说的:
临时生效。
有些内核参数不是一个数字,大家不要认为 /proc/sys 下面所有参数都是:
1 | 0 |
或者:
1 | 1 |
有些参数可能包含多个数值。
例如:
1 | [root@localhost ~]# cat /proc/sys/net/ipv4/tcp_rmem |
我来介绍一下这个参数吧,tcp_rmem 可以理解成 Linux 内核给 TCP 接收数据准备的“内存缓冲区大小控制参数”,tcp_rmem 不是一个单独的数值,而是由 三个数字组成:
| 数值 | 含义 | 说明 |
|---|---|---|
| 第一个 | 最小值 min | TCP 接收缓冲区允许使用的最小内存 |
| 第二个 | 默认值 default | TCP 连接通常使用的默认接收缓冲区大小 |
| 第三个 | 最大值 max | TCP 接收缓冲区能够增长到的最大值 |
单位是:字节。,也就是说,TCP 接收缓冲区这里不是简单的一个开关,而是一组参数。可以把 TCP 接收缓冲区想象成一个仓库。比如服务器正在接收一个大文件:
1 | 客户端 |
网络数据到达服务器之后,并不是直接塞进你的应用程序。通常会先进入 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 | sysctl 参数名称: |
对应:
1 | /proc/sys/net/ipv4/icmp_echo_ignore_all |
大家仔细看一下就会发现一个规律:
参数名称中的
.,对应/proc/sys路径中的/。
也就是说:
1 | 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 | Linux 内核参数 |
这样使用 sysctl 就方便多了。
常用 sysctl 操作
1. 查看全部内核可调项
执行:
1 | [root@localhost ~]# sysctl -a | more |
这个命令会把系统当前的内核可调项以及对应值全部列出来。参数比较多,所以实际工作中一般不会只盯着完整输出看,而是结合具体参数进行查询。
2. -n:只输出参数值
例如我们想看看:vm.swappiness,当前是多少。可以:
1 | [root@localhost ~]# sysctl -n vm.swappiness |
这里的 -n 可以理解成:
“我只要结果,不要把参数名称也打印出来。”
vm.swappiness 是干什么的?这个参数和 Linux 内存管理有关。简单理解:
它控制 Linux 使用 Swap 的积极程度。
数值越低,Linux 越倾向于:
尽量少使用 Swap。
我们可以临时调整成10:
1 | [root@localhost ~]# sysctl -w 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 | cat > /etc/sysctl.d/swappiness.conf <<EOF |
这样我们就把配置永久保存下来了,但是现在并没有生效,得重启服务器。
不重启服务器,如何让配置立即生效?
配置文件写完以后,难道必须重启服务器?当然不是。可以直接:
1 | [root@localhost ~]# sysctl -p /etc/sysctl.d/swappiness.conf |
这条命令的意思就是:
读取刚才这个配置文件,然后把里面的参数加载到当前运行中的内核。
所以现在就可以同时做到:配置永久保存 + 当前立即生效。
如果直接执行 sysctl -p 呢?如果执行:
1 | sysctl -p |
不指定具体文件,那么会按照系统的配置机制加载相关配置文件。所以实际生产环境中,我们既可以指定某个配置文件进行加载,也可以按照系统配置机制统一加载。
sysctl 配置文件加载优先级
Linux中常见的配置目录有:
1 | /etc/sysctl.d/*.conf |
按照优先级从高到低:
/etc/sysctl.d/*.conf/run/sysctl.d/*.conf/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 | /proc |
但是 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 | [root@localhost ~]# modinfo loop |
查看模块信息。如果我们只关心:
“这个模块到底支持哪些参数?”
可以使用:
1 | [root@localhost ~]# modinfo -p loop |
这样我们就知道:
loop模块提供了哪些可以调整的参数。
模块已经加载,还能改参数吗?
如果模块已经加载,那么这些模块参数通常会暴露到:
1 | /sys/module/<模块名>/parameters/ |
例如:
1 | /sys/module/loop/parameters/ |
查看:
1 | [root@localhost ~]# cat /sys/module/loop/parameters/max_loop |
这就是当前 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 | options |
为什么写完配置以后没有马上变化?
这是因为:
1 | /etc/modprobe.d/loop.conf |
配置的是:
模块加载阶段的参数。
它不是像 sysctl 那样,写完以后自动修改已经运行中的内核参数。如果 loop 模块已经加载,那么你刚才创建配置文件以后:
1 | 已经加载的 loop |
所以如果需要让新的模块参数生效,就需要重新加载模块。
临时加载模块时直接传参数
还有一种方法。如果我们只是临时测试,不想写配置文件,可以:
1 | modprobe loop max_loop=5 |
意思就是:
加载
loop模块的时候,同时把max_loop设置成5。
这种方式同样是临时的。重启以后不会自动保留。
内核参数看不懂怎么办?
实际工作中,大家肯定会遇到这种情况:
看到一个参数:
1 | vm.xxx |
完全不知道是什么意思。这个时候不要第一反应:
“网上搜一下别人怎么配。”
更好的方式是:看官方文档。 可以安装:
1 | [root@localhost ~]# dnf install kernel-doc -y |
安装完成以后,相关文档会放在:
1 | /usr/share/doc/kernel-doc-*/Documentation/ |
看我的:
1 | [root@localhost ~]# ls /usr/share/doc/kernel-doc-6.12.0-211.60.1/Documentation/ |
这里面可以查阅内核相关文档。
包括:
- 参数的作用
- 参数取值
- 使用场景
- 相关说明
所以实际工作中遇到陌生参数:
先看文档,再决定是否修改。
临时生效 VS 持久生效
到这里我们已经讲了好几种方式。
把它们放到一起,就非常清楚了。
| 修改方式 | 操作方法 | 是否重启丢失 | 适用场景 |
|---|---|---|---|
修改 /proc/sys 文件 | echo 值 > /proc/sys/xxx | ✅ 重启丢失 | 临时测试参数效果 |
sysctl -w | sysctl -w 参数=值 | ✅ 重启丢失 | 命令行快速临时调参 |
| sysctl conf 配置 | /etc/sysctl.d/*.conf | ❌ 重启保留 | sysctl 类内核参数永久配置 |
| sysfs 模块参数 echo 写入 | echo 值 > /sys/module/xxx/parameters/xxx | ✅ 重启丢失 | 临时修改已经加载模块 |
| modprobe.d 配置 | /etc/modprobe.d/*.conf | ❌ 重启保留 | 模块加载时设置驱动参数 |
大家可以把它总结成一句话:
先临时改,确认有效以后,再持久化。
生产环境到底应该怎么调内核参数?
这一点非常重要。实际生产环境中,我非常不建议大家:
1 | 看到一篇文章 |
这种方式风险非常大。正确的思路应该是:
第一步:先发现问题
通过前面学习的 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 | /proc |
然后又进一步区分了:
1 | 临时修改 |
最重要的生产实践原则还是那句话:
不要为了“性能优化”而优化。
先通过监控发现问题,再找到对应的内核参数。
先临时调整、验证业务,确认有效以后,再进行持久化。
这样做,才是真正意义上的 Linux 性能调优。
参考 man 手册
1 | man proc |
这些手册建议大家实际敲一下。因为性能调优真正做到后面,很多时候不是:
“我记得这个参数应该设置成多少。”
而是:
“我知道应该去哪里查这个参数,它到底控制什么,以及修改以后可能产生什么影响。”
下一篇预告:
前面我们学习了如何手动维护
sysctl配置。但是如果一台服务器还好,几十台、几百台服务器呢?
难道每台机器都手工修改?
显然不现实。
下一篇我们就继续往前走,看看 TuneD 调优守护进程。
TuneD 内置了大量针对不同工作负载的调优模板,同时也支持自定义配置
