Linux proc-modules文件格式与m_show回调

📅 2026/6/22 13:31:48
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_listfbdev 24576 0ext4 589824 2 crc16,mbcacheusb_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;elsemod 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选项的原因。其格式的稳定性和简洁性使其成为内核模块信息导出的经典设计。