系统化、可直接上手的 /proc/sys
+ sysctl 接口使用文档。内容涵盖:机制原理、适用场景、ctl_table
字段详解、常用解析器(proc_handler)完整清单与选型、最小样例到进阶(范围校验、毫秒→jiffies、字符串、数组、每网络命名空间)、并发/安全/发布与持久化要点、调试清单。
0. 怎么最快落地
- 一步到位:在你的模块里注册一张
ctl_table
,选择合适的proc_handler
(解析器),把变量地址塞进去即可。 - 用户态:
sysctl -w my.feature.param=42
或echo 42 > /proc/sys/my/feature/param
。 - 持久化:
/etc/sysctl.d/99-my.conf
写入my.feature.param=42
(确保模块加载在 systemd-sysctl 运行前或用 udev/模块黑名单机制保证加载顺序)。
1. 适用场景与优缺点
适用:
- 你要把系统级可调参数暴露给用户态(如调度/缓存/协议超时/开关等),并希望集中在
/proc/sys
树下,统一被sysctl
(和运维体系)管理。 - 文本接口足够,频率较低(人为调优/配置),而非高频大吞吐。
不适用:
- 需要稳定的设备语义(更建议用 sysfs 设备属性)。
- 高频/低延迟/二进制批量(建议字符设备 +
ioctl
/mmap
或 netlink)。 - 私有临时调试(建议 debugfs)。
2. 工作原理与路径规则
-
通过
register_sysctl("my/feature", table)
把一张ctl_table
注册到/proc/sys/my/feature
下。 -
文件系统路径 → sysctl 点分名:
/proc/sys/my/feature/param
↔sysctl -w my.feature.param=...
3. ctl_table
/ proc_handler
速查
3.1 结构体字段(关键)
struct ctl_table {const char *procname; // 显示为文件名void *data; // 变量地址/数组首址/字符串缓冲int maxlen; // data 的字节长度(标量=sizeof(type),数组=sizeof(array),字符串=缓冲区长度)umode_t mode; // 0644等(文件权限)struct ctl_table *child; // 子表(做树形结构时用,一般为空)proc_handler *proc_handler; // 解析器(核心)void *extra1; // 解析器自定义用(常做“最小值”)void *extra2; // 解析器自定义用(常做“最大值”)
};
注意:
data
指向的内存必须在unregister_sysctl_table()
前一直有效(通常用模块/全局静态变量)。
3.2 常用解析器(proc_handler)清单与用途(版本差异用法备注)
解析器(handler) | 适用类型 | 功能/特点 | extra1/extra2 | 备注 |
---|---|---|---|---|
proc_dobool | bool (文本 0/1) | 读写布尔 | 无 | 简洁明了 |
proc_dointvec | int /int[] | 文本十进制,数组支持空格分隔 | 无 | 常用 int |
proc_douintvec | unsigned int /数组 | 同上 | 无 | 无符号 |
proc_dointvec_minmax | int /数组 | 含范围校验 | extra1 =min(int*), extra2 =max(int*) | 写时检查 |
proc_douintvec_minmax | unsigned int /数组 | 含范围校验 | 同上(无符号) | |
proc_doulongvec_minmax | unsigned long /数组 | 含范围校验 | extra1 =min(ulong*), extra2 =max(ulong*) | 64 位架构上 unsigned long 即 64b |
proc_dostring | char[] | NUL 终止字符串,长度受 maxlen 限制 | 可选 extra1 标志指针(如严写入),具体依内核 | 安全起见自己做校验 |
proc_dointvec_jiffies | int | 单位是 jiffies(读写按十进制 jiffies) | 无 | 与内核延时/超时变量匹配 |
proc_dointvec_ms_jiffies | int | 用户写毫秒,内核存 jiffies | 无 | 常用于毫秒超时参数 |
proc_doulongvec_ms_jiffies_minmax | unsigned long /数组 | 毫秒↔jiffies + 范围校验 | extra1/extra2 =min/max | 常用于超时上/下限控制 |
关于跨架构 64 位:如果你必须用固定 64 位(而不是
unsigned long
),保守做法是自定义 handler 或选用你内核版本已提供的proc_do_u64vec_minmax
(不同版本支持度不同,使用前确认头文件原型)。
4. 最小可运行示例(5.10 通过)
4.1 单个 int
参数(/proc/sys/my/feature/level
)
内核模块
// my_sysctl_demo.c
#include <linux/module.h>
#include <linux/sysctl.h>static int level = 7;
static struct ctl_table my_sysctl_table[] = {{.procname = "level",.data = &level,.maxlen = sizeof(level),.mode = 0644,.proc_handler = proc_dointvec,},{ } // terminator
};
static struct ctl_table_header *sysctl_hdr;static int __init my_init(void)
{// 在 /proc/sys/my/feature 下注册sysctl_hdr = register_sysctl("my/feature", my_sysctl_table);if (!sysctl_hdr)return -ENOMEM;pr_info("sysctl ready: /proc/sys/my/feature/level\n");return 0;
}static void __exit my_exit(void)
{unregister_sysctl_table(sysctl_hdr);
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
Kconfig / Makefile(模块构建)
# Makefile
obj-m += my_sysctl_demo.o
# 编译:make -C /lib/modules/$(uname -r)/build M=$(PWD) modules
用户态验证
cat /proc/sys/my/feature/level # 7
sysctl -w my.feature.level=10 # 写
echo 12 > /proc/sys/my/feature/level
5. 进阶:范围限制 / 毫秒入参 / 字符串 / 数组
5.1 数值带范围(最常用)
static unsigned int gain = 42;
static unsigned int gain_min = 0;
static unsigned int gain_max = 1000;static struct ctl_table tbl[] = {{.procname = "gain",.data = &gain,.maxlen = sizeof(gain),.mode = 0644,.proc_handler = proc_douintvec_minmax,.extra1 = &gain_min,.extra2 = &gain_max,},{ }
};
超过范围写入会返回
-ERANGE
(sysctl
会显示失败)。
5.2 毫秒写入、内核保存为 jiffies(超时常见)
static int timeout_jiffies = 5 * HZ; // 默认5秒static struct ctl_table tbl[] = {{.procname = "timeout_ms",.data = &timeout_jiffies,.maxlen = sizeof(timeout_jiffies),.mode = 0644,.proc_handler = proc_dointvec_ms_jiffies,},{ }
};
/* 用户写: sysctl -w my.feature.timeout_ms=200 # 200ms内核持有: timeout_jiffies = msecs_to_jiffies(200) */
5.3 字符串(带长度)
static char name_buf[64] = "default";static struct ctl_table tbl[] = {{.procname = "name",.data = name_buf,.maxlen = sizeof(name_buf),.mode = 0644,.proc_handler = proc_dostring,},{ }
};
/* 注意:写入内容会截断到 63 字节 + '\0'。最好在业务侧校验允许字符集与格式。*/
5.4 数组(空格分隔)
static int weights[3] = { 1, 2, 3 };static struct ctl_table tbl[] = {{.procname = "weights",.data = weights,.maxlen = sizeof(weights), // 数组总字节数.mode = 0644,.proc_handler = proc_dointvec, // 或 *_minmax 变体},{ }
};
/* 写法:sysctl -w "my.feature.weights=10 20 30"echo "10 20 30" > /proc/sys/my/feature/weights
*/
6. 并发、可见性与健壮性
-
并发读写:
proc_*
解析器仅负责“把文本转为数值 + 基本检查”,不提供你的业务级同步。- 读写涉及多 CPU 可见性:配合
READ_ONCE()/WRITE_ONCE()
。 - 写入需要与读侧/快路径同步:自旋锁/RCU/原子变量/内存屏障按你的语义选择。
- 读写涉及多 CPU 可见性:配合
-
字符串:谨防越界与非法内容。
proc_dostring
已考虑长度,但语义校验应在业务侧完成(例如只允许[A-Za-z0-9_-]
)。 -
错误返回:
proc_*
写失败会返回负值(如-EINVAL/-ERANGE/-E2BIG
),用户会在sysctl
命令中看到。
7. 安全与权限
mode
合理设置:常见为0644
(root 可写,所有人可读)。如需更严:0600/0640
。- 可配合 LSM/SELinux 做更细粒度限制(对
/proc/sys/my/feature/*
赋类型并限制写者)。 - 不要把安全敏感开关以可写方式随意开放;必要时在 handler 中验证
capable(CAP_SYS_ADMIN)
等能力。
8. 每网络命名空间(per-netns)示例(可选进阶)
如果参数应当按网络命名空间隔离(如协议栈调优项),使用 register_net_sysctl()
:
#include <linux/netdevice.h>
#include <net/net_namespace.h>struct my_net {struct ctl_table_header *hdr;unsigned int net_gain;
};static int my_netns_init(struct net *net)
{struct my_net *mn = kzalloc(sizeof(*mn), GFP_KERNEL);if (!mn) return -ENOMEM;net->gen->ptr[0] = mn; // 示例:用自定义方式挂接到 net(实际按项目基建)static struct ctl_table tbl[] = {{.procname = "gain",.data = &mn->net_gain,.maxlen = sizeof(mn->net_gain),.mode = 0644,.proc_handler = proc_douintvec,},{ }};mn->hdr = register_net_sysctl(net, "my/feature", tbl);if (!mn->hdr) { kfree(mn); return -ENOMEM; }return 0;
}static void my_netns_exit(struct net *net)
{struct my_net *mn = net->gen->ptr[0];if (!mn) return;unregister_net_sysctl_table(mn->hdr);kfree(mn);
}/* 实际项目中,结合 pernet_operations 注册 { .init = my_netns_init, .exit = my_netns_exit } */
只有在确需网络命名空间隔离时使用该接口;否则用普通
register_sysctl()
更简单。
9. 注册与反注册 API 选择(版本提示)
-
推荐:
register_sysctl(const char *path, struct ctl_table *table)
+unregister_sysctl_table()
- 直观、减少手写树根节点;5.10 可用。
-
仍可用但不推荐:
register_sysctl_table(struct ctl_table *table)
- 需要自建树根,维护繁琐;近年文档与示例更多指向
register_sysctl()
。
- 需要自建树根,维护繁琐;近年文档与示例更多指向
10. 与 sysfs/debugfs 的取舍(速记)
- sysctl:系统参数集中地、与发行版运维工具链(
sysctl.d
)天然集成;文本接口。 - sysfs:设备语义稳定 ABI,面向设备实例;文本接口。
- debugfs:开发/排障,非稳定 ABI;样板最少,切勿作为正式接口。
11. 部署与持久化
-
内核选项:
CONFIG_PROC_FS=y
、CONFIG_SYSCTL=y
。 -
加载模块:
modprobe my_sysctl_demo
。 -
一次性设置:
sysctl -w my.feature.level=10 echo 100 > /proc/sys/my/feature/gain
-
持久化(systemd 系统):
-
新建
/etc/sysctl.d/99-my.conf
:my.feature.level=10 my.feature.gain=100
-
使生效:
systemctl restart systemd-sysctl
(或下次开机自动应用)。 -
注意加载顺序:确保你的模块在
systemd-sysctl
执行前已加载(可用/etc/modules-load.d/my.conf
指定模块名)。
-
12. 常见坑与最佳实践
- 并发一致性:handler 不等于锁。对业务关键变量,自己加同步/屏障。
- 范围校验:优先选用
*_minmax
解析器,别把校验散落在其他路径。 - 单位换算:时间类统一采用
*_ms_jiffies
handler,避免重复换算与疏漏。 - 数组写入:需要整体性(原子性)就别用数组 sysctl;可先写 staging,再一次性切换。
- ABI 污染:别在核心命名空间里滥造节点。自定义放在
my/feature
或子系统命名下。 - 安全:敏感项只允许 root 写;必要时再加能力检查与 LSM 策略。
- 跨版本:个别 handler 名称/存在性在极老或极新内核上有差异;编译前
grep -R proc_do.* include/linux/sysctl.h
确认。
13. 一次给全:示例模块(含多类型)
// my_sysctl_full.c
#include <linux/module.h>
#include <linux/sysctl.h>
#include <linux/jiffies.h>static int level = 7;
static unsigned int gain = 42;
static unsigned int gain_min = 0;
static unsigned int gain_max = 1000;
static int timeout_jiffies = 200 * HZ / 1000; // 200ms
static bool enable = true;
static char name_buf[64] = "default";
static int weights[3] = {1,2,3};static struct ctl_table my_feature_table[] = {{.procname = "level",.data = &level,.maxlen = sizeof(level),.mode = 0644,.proc_handler = proc_dointvec,},{.procname = "gain",.data = &gain,.maxlen = sizeof(gain),.mode = 0644,.proc_handler = proc_douintvec_minmax,.extra1 = &gain_min,.extra2 = &gain_max,},{.procname = "timeout_ms",.data = &timeout_jiffies,.maxlen = sizeof(timeout_jiffies),.mode = 0644,.proc_handler = proc_dointvec_ms_jiffies,},{.procname = "enable",.data = &enable,.maxlen = sizeof(enable),.mode = 0644,.proc_handler = proc_dobool,},{.procname = "name",.data = name_buf,.maxlen = sizeof(name_buf),.mode = 0644,.proc_handler = proc_dostring,},{.procname = "weights",.data = weights,.maxlen = sizeof(weights),.mode = 0644,.proc_handler = proc_dointvec, // 或 *_minmax},{ }
};static struct ctl_table_header *hdr;static int __init my_init(void)
{hdr = register_sysctl("my/feature", my_feature_table);if (!hdr)return -ENOMEM;pr_info("my/feature sysctl ready under /proc/sys/my/feature\n");return 0;
}static void __exit my_exit(void)
{unregister_sysctl_table(hdr);
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
用户态快速验证
sysctl -a | grep '^my\.feature\.' # 列出
sysctl -w my.feature.enable=1
sysctl -w my.feature.gain=99
sysctl -w my.feature.timeout_ms=500
sysctl -w 'my.feature.weights=10 20 30'
echo 'myname' > /proc/sys/my/feature/name
cat /proc/sys/my/feature/name