lib/sys_info.c:sys_info 调试掩码 sysctl 子系统概览
介绍
lib/sys_info.c 为 sys_info() 调试输出提供配置接口与执行路径:sys_info_sysctl_init() 在子系统初始化阶段注册 kernel_sys_info sysctl 项,由 sysctl_sys_info_handler() 统一处理读写,内部通过位掩码选择任务、内存、定时器、锁、ftrace、全 CPU 回溯等信息的收集范围。
历史与背景
- 诞生动机:为内核调试路径提供可配置的统一入口,通过位掩码决定
sys_info() 触发时的输出范围。
- 迭代里程碑:引入 sysctl 处理器与早期 initcall 注册,确保调试接口在大多数驱动加载前即可使用。
- 社区与应用:常用于现场调试或自动化诊断脚本,结合 SysRq/延迟工作触发,以收集当前系统状态。
核心原理与设计
- sysctl 表注册:
sys_info_sysctls 定义 kernel_sys_info 项,绑定到全局位掩码 kernel_si_mask,读写均由 sysctl_sys_info_handler 代理。
- 读写处理器:
- 写入路径:
sys_info_write_handler() 解析逗号分隔名称列表为位掩码,通过 WRITE_ONCE 更新全局默认值。
- 读取路径:
sys_info_read_handler() 将当前掩码逆向编码为名称列表字符串返回用户态,依赖 READ_ONCE 获取快照。
- 初始化时序:
subsys_initcall 级别执行,确保 /proc/sys/kernel/kernel_sys_info 在用户空间早期就绪。
使用场景
- 推荐:调试脚本按需控制
sys_info() 输出范围;现场问题分析中快速切换默认掩码。
- 不推荐/注意:高频并发修改掩码(未加锁,仅保证原子可见性);安全敏感场景需限制写权限。
对比分析
- 与内核启动参数:启动参数只能在引导时设置,
kernel_sys_info 可运行期动态调整,更灵活。
- 与专用调试开关:位掩码可组合多种诊断输出,粒度细于单一布尔开关。
sys_info_sysctls —— kernel_sys_info sysctl 表定义
1 2 3 4 5 6 7 8 9 10
| static const struct ctl_table sys_info_sysctls[] = { { .procname = "kernel_sys_info", .data = &kernel_si_mask, .maxlen = sizeof(kernel_si_mask), .mode = 0644, .proc_handler = sysctl_sys_info_handler, }, };
|
sys_info_sysctl_init —— kernel_sys_info sysctl 注册入口
1 2 3 4 5 6 7 8 9 10
|
static int __init sys_info_sysctl_init(void) { register_sysctl_init("kernel", sys_info_sysctls); return 0; } subsys_initcall(sys_info_sysctl_init);
|
sysctl_sys_info_handler —— sysctl 读写分派入口
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
|
int sysctl_sys_info_handler(const struct ctl_table *ro_table, int write, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table table; unsigned int i; size_t maxlen;
maxlen = 0; for (i = 0; i < ARRAY_SIZE(si_names); i++) maxlen += strlen(si_names[i]) + 1;
char *names __free(kfree) = kzalloc(maxlen, GFP_KERNEL); if (!names) return -ENOMEM;
table = *ro_table; table.data = names; table.maxlen = maxlen;
if (write) return sys_info_write_handler(&table, buffer, lenp, ppos, ro_table->data); else return sys_info_read_handler(&table, buffer, lenp, ppos, ro_table->data); }
|
sys_info_write_handler —— 将名称列表写入掩码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
static int sys_info_write_handler(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, unsigned long *si_bits_global) { unsigned long si_bits; int ret;
ret = proc_dostring(table, 1, buffer, lenp, ppos); if (ret) return ret;
si_bits = sys_info_parse_param(table->data);
WRITE_ONCE(*si_bits_global, si_bits);
return 0; }
|
sys_info_read_handler —— 将掩码编码为名称列表
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
|
static int sys_info_read_handler(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, unsigned long *si_bits_global) { unsigned long si_bits; unsigned int len = 0; char *delim = ""; unsigned int i;
si_bits = READ_ONCE(*si_bits_global);
for_each_set_bit(i, &si_bits, ARRAY_SIZE(si_names)) { if (*si_names[i]) { len += scnprintf(table->data + len, table->maxlen - len, "%s%s", delim, si_names[i]); delim = ","; } }
return proc_dostring(table, 0, buffer, lenp, ppos); }
|