lib/sys_info.c:sys_info 调试掩码 sysctl 子系统概览

介绍

lib/sys_info.csys_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
/** @brief sysctl 表项,暴露 kernel_sys_info 位掩码。 */
static const struct ctl_table sys_info_sysctls[] = {
{
.procname = "kernel_sys_info", /**< /proc/sys/kernel/kernel_sys_info 路径名。 */
.data = &kernel_si_mask, /**< 默认位掩码的存储位置。 */
.maxlen = sizeof(kernel_si_mask), /**< 允许写入的最大字节数。 */
.mode = 0644, /**< 读写权限:root 可写,其他可读。 */
.proc_handler = sysctl_sys_info_handler, /**< 读写均委托给自定义处理器。 */
},
};

sys_info_sysctl_init —— kernel_sys_info sysctl 注册入口

1
2
3
4
5
6
7
8
9
10
/**
* @brief 在子系统初始化阶段注册 kernel_sys_info sysctl。
* @note 单核场景下无并行 init 竞争;读写处理器内部访问 kernel_si_mask 未加锁,依赖 READ/WRITE_ONCE 保证可见性。
*/
static int __init sys_info_sysctl_init(void)
{
register_sysctl_init("kernel", sys_info_sysctls); /**< 挂载到 /proc/sys/kernel/ 命名空间。 */
return 0; /**< initcall 约定:返回 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
/**
* @brief 统一处理 kernel_sys_info 的读写,依据 write 标志分派具体处理器。
* @param ro_table sysctl 描述项(只读模板),包含数据指针与长度。
* @param write 非零表示写入路径,零表示读取路径。
* @param buffer 用户态缓冲区。
* @param lenp 读写长度指针。
* @param ppos 偏移指针,遵循 sysctl 接口约定。
* @return 0 表示成功,负值表示错误。
*/
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; /**< 复制模板,以便修改 data/maxlen 指针。 */
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
/**
* @brief 解析用户态逗号分隔名称列表,转换为位掩码并写入全局 kernel_si_mask。
* @note 访问 kernel_si_mask 未加锁,依赖 WRITE_ONCE;单核场景仅需保证与中断上下文的可见性。
*/
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
/**
* @brief 读取当前 kernel_si_mask,将位掩码编码为逗号分隔的名称列表写回用户态。
* @note 依赖 READ_ONCE 获取掩码;单核场景下无跨 CPU 并发导致的撕裂风险。
*/
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); /**< 按 sysctl 读取路径返回字符串。 */
}