Linux proc-modules文件格式与m_show回调
Linux /proc/modules文件格式与m_show回调
/proc/modules是Linux内核暴露已加载模块信息的传统procfs接口,其文件格式和输出由seq_file接口中的m_show()回调完全控制。尽管/sys/module提供了更丰富的sysfs接口,/proc/modules因其简洁的文本格式仍然是lsmod等用户空间工具的首选数据源。
/proc/modules的注册位于kernel/module/procfs.c中的__init proc_modules_init():
static int __init proc_modules_init(void)
{
proc_create_seq("modules", 0, NULL, &modules_op);
return 0;
}
module_init(proc_modules_init);
proc_create_seq()是内核提供的seq_file封装,它在procfs根目录下创建名为"modules"的文件,并将所有读写操作委托给modules_op中定义的seq_operations回调。
struct seq_operations modules_op的定义:
static const struct seq_operations modules_op = {
.start = m_start,
.next = m_next,
.stop = m_stop,
.show = m_show,
};
/proc/modules的典型输出格式如下:
module_name size used_by_count used_by_list
fbdev 24576 0
ext4 589824 2 crc16,mbcache
usb_storage 73728 0
每行包含四列:
(1) 模块名称:最左列,20字符以内左对齐。
(2) 模块大小:模块核心代码段占用内存的字节数,对应mod->core_layout.size,以十进制显示。
(3) 引用计数:当前使用该模块的内核组件数量,通过atomic_read(&mod->refcnt)读取。
(4) 依赖列表:逗号分隔的使用者列表,当引用计数为0时此列为空。
m_show()回调的完整实现:
static int m_show(struct seq_file *m, void *p)
{
struct module *mod = list_entry(p, struct module, list);
char buf[MODULE_FLAGS_BUF_SIZE];
if (p == SEQ_START_TOKEN) {
seq_puts(m, "Module Size Used by\n");
return 0;
}
/* 输出基础信息 */
seq_printf(m, "%-20s%8lu %u ",
mod->name,
mod->core_layout.size,
atomic_read(&mod->refcnt));
/* 输出taint标志 */
module_flags(mod, buf, false);
seq_printf(m, "%s\n", buf);
return 0;
}
seq_file接口的工作机制对于理解/proc/modules的读取行为至关重要。当一个用户空间程序(如lsmod或者cat /proc/modules)读取文件时,内核依次调用:
(1) m_start():加module_mutex锁,返回第一个要显示的模块。
static void *m_start(struct seq_file *m, loff_t *pos)
{
struct module *mod;
loff_t n = 0;
mutex_lock(&module_mutex);
if (!*pos) {
if (list_empty(&module_list))
return SEQ_START_TOKEN;
mod = list_first_entry(&module_list, struct module, list);
(*pos)++;
return mod;
}
list_for_each_entry_continue(mod, &module_list, list) {
if (n++ >= *pos)
return mod;
}
return NULL;
}
(2) m_show():格式化输出当前模块的信息。
(3) m_next():遍历到module_list中的下一个模块。
static void *m_next(struct seq_file *m, void *p, loff_t *pos)
{
struct module *mod;
if (p == SEQ_START_TOKEN)
mod = list_first_entry(&module_list, struct module, list);
else {
mod = list_entry(p, struct module, list);
if (list_is_last(&mod->list, &module_list))
mod = NULL;
else
mod = list_next_entry(mod, list);
}
(*pos)++;
return mod;
}
(4) m_stop():释放module_mutex锁。
static void m_stop(struct seq_file *m, void *p)
{
mutex_unlock(&module_mutex);
}
/proc/modules的读写性能考虑:当系统加载了大量模块(数千个)时,cat /proc/modules可能会因为module_mutex的持有时间较长而短暂阻塞模块加载和卸载操作。seq_file的缓冲区默认大小为PAGE_SIZE字节(通常4096),当输出数据超过单页大小时,内核自动调用m_start()和m_next()进行多次迭代。
输出格式中的"Used by"列通过module_flags()函数生成。该函数还负责输出模块的taint污染标志:
static void module_flags(struct module *mod, char *buf, bool show_state)
{
int bx = 0;
if (mod->taints) {
if (mod->taints & TAINT_PROPRIETARY_MODULE)
buf[bx++] = 'P';
if (mod->taints & TAINT_OOT_MODULE)
buf[bx++] = 'O';
if (mod->taints & TAINT_FORCED_MODULE)
buf[bx++] = 'F';
if (mod->taints & TAINT_CRAP)
buf[bx++] = 'C';
if (mod->taints & TAINT_UNSIGNED_MODULE)
buf[bx++] = 'E';
}
if (show_state) {
switch (mod->state) {
case MODULE_STATE_LIVE: break;
case MODULE_STATE_COMING: buf[bx++] = 'C'; break;
case MODULE_STATE_GOING: buf[bx++] = 'G'; break;
}
}
if (bx)
buf[bx++] = ' ';
buf[bx] = '\0';
/* 附加使用者的模块名称列表 */
if (!list_empty(&mod->source_list)) {
struct module_use *use;
list_for_each_entry(use, &mod->source_list, source_list) {
if (bx >= MODULE_FLAGS_BUF_SIZE - 1)
break;
bx += snprintf(buf + bx, MODULE_FLAGS_BUF_SIZE - bx,
"%s,", use->target->name);
}
if (bx > 0 && buf[bx-1] == ',')
buf[bx-1] = ' ';
}
}
内核自5.x系列以来逐步将模块子系统从kernel/module.c重构为kernel/module/目录下的多个文件,procfs.c独立管理/proc/modules的逻辑。seq_file接口的设计使得/proc/modules可以安全地在模块并发加载和卸载的环境下提供一致的视图,即使遍历期间模块列表发生变化,m_start/stop的锁机制也保证了数据一致性。
用户空间工具可以直接解析/proc/modules的文本行,无需依赖任何内核头文件,这也是该接口历经数十年仍被libkmod等现代工具库作为fallback选项的原因。其格式的稳定性和简洁性使其成为内核模块信息导出的经典设计。
