Linux lsmod输出读取module_list遍历与refcnt

📅 2026/6/15 20:34:47
Linux lsmod输出读取module_list遍历与refcnt
Linux lsmod输出读取module_list遍历与refcntlsmod是系统管理员最常用的模块查看工具其输出来自读取/proc/modules伪文件。用户空间的lsmod命令实现非常简洁它打开/proc/modules并按行解析读取每个模块的名称、内存大小、引用计数和依赖关系。但在内核层面lsmod对应的数据来源是全局module_list链表的遍历和模块引用计数refcnt的读取。内核中所有已加载的模块通过struct module中的list_head成员链接成一个全局链表。module_list链表的头节点定义在kernel/module/internal.h中extern struct list_head module_list;每个模块的list字段是链表节点struct module {enum module_state state;struct list_head list; /* 链接到module_list */char name[MODULE_NAME_LEN];struct module_kobject mkobj;struct module_param_attrs *param_attrs;const struct kernel_symbol *syms;const struct kernel_symbol *gpl_syms;const s32 *crcs;const s32 *gpl_crcs;unsigned int num_syms;unsigned int num_gpl_syms;atomic_t refcnt; /* 模块引用计数 */unsigned long *pcpu;unsigned int num_trace_events;...};内核遍历module_list并格式化输出的核心函数位于kernel/module/procfs.c中的m_show()同时也被/proc/modules使用。但lsmod实际读取的数据由/proc/modules提供其seq_file操作接口定义如下static const struct seq_operations modules_op {.start m_start,.next m_next,.stop m_stop,.show m_show};m_start()负责获取module_list的锁并返回迭代起始点static void *m_start(struct seq_file *m, loff_t *pos){struct module *mod;loff_t n 0;mutex_lock(module_mutex);if (*pos 0) {/* 返回第一个模块 */if (list_empty(module_list))return SEQ_START_TOKEN;mod list_first_entry(module_list, struct module, list);return mod;}list_for_each_entry(mod, module_list, list) {if (n *pos)return mod;}return NULL;}m_show()输出每个模块的详细信息lsmod的输出格式即由此函数决定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));/* 输出使用此模块的模块列表 */module_flags(mod, buf, false);seq_printf(m, %s\n, buf);return 0;}引用计数refcnt是atomic_t类型通过atomic_read()读取。模块引用计数的管理涉及三种场景(1) try_module_get()尝试增加引用计数在模块正在卸载时返回0表示获取失败static inline int try_module_get(struct module *module){int ret 1;if (module) {preempt_disable();if (likely(module_is_live(module)))atomic_inc(module-refcnt);elseret 0;preempt_enable();}return ret;}(2) module_put()减少引用计数当引用计数降为0时触发模块卸载清理static inline void module_put(struct module *module){if (module) {preempt_disable();atomic_dec(module-refcnt);wake_up_all(module-exit_wq);preempt_enable();}}(3) __module_get()无条件增加引用计数不检查模块状态。模块的Used by列显示了依赖该模块的其他模块名称。内核通过模块的holders链表维护依赖关系每个使用此模块的模块都会通过add_module_usage()在holders链表中添加一个条目struct module_use {struct list_head source_list;struct list_head target_list;struct module *source;struct module *target;enum module_use_type type;};m_show()中的module_flags()函数遍历holders链表输出依赖者列表static void module_flags(struct module *mod, char *buf, bool show_state){int bx 0;if (mod-taints)bx snprintf(buf bx, MODULE_FLAGS_BUF_SIZE - bx, %s, taint_flags(mod-taints));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 (!list_empty(mod-source_list)) {struct module_use *use;list_for_each_entry(use, mod-source_list, source_list) {bx snprintf(buf bx, MODULE_FLAGS_BUF_SIZE - bx,%s , use-target-name);if (bx MODULE_FLAGS_BUF_SIZE - 1)break;}}buf[bx] \0;}module_list链表的遍历受module_mutex互斥锁保护该锁在m_start()中获取在m_stop()中释放与module_mutex竞争的主要路径包括module loadingload_module()和module unloadingfree_module()。lsmod命令本身只是/proc/modules文件的格式化输出器其用户空间代码极为简短int main(int argc, char **argv){FILE *fp fopen(/proc/modules, r);char line[4096];while (fgets(line, sizeof(line), fp)) {char name[64];unsigned long size;unsigned int refcount;char users[1024];sscanf(line, %s %lu %u %s, name, size, refcount, users);printf(%-19s %8lu %u %s\n, name, size, refcount, users);}fclose(fp);return 0;}通过lsmod输出中的refcnt列管理员可以判断一个模块是否被其他内核子系统或驱动在使用。如果refcnt不为0rmmod操作会失败并返回-EBUSY模块忙。