系統化、可直接上手的 /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