You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核模块引用计数始终为0问题求助

问题根因

你遇到的模块引用计数始终为0的问题,核心是**struct file_operations的.owner字段自动维护引用计数的逻辑,仅在通过标准字符设备注册接口(register_chrdev/cdev_add)关联fops时生效,proc_create接口不会自动处理该引用计数逻辑**。
很多开发者误以为只要设置了.owner = THIS_MODULE,所有场景下打开文件都会自动给模块加引用,实际上procfs是虚拟文件系统,它的内部文件打开流程默认不会触发try_module_get对owner的计数操作,所以哪怕用户态持有打开的fd、甚至做了mmap映射,模块的use count还是0,rmmod能直接卸载触发空指针崩溃。
另外还有一个容易遗漏的点:mmap建立的用户态页映射,就算fd关闭了,只要映射没解除,用户态访问映射内存还是会调用模块里的fault回调等逻辑,这部分也需要额外持有模块引用,不然fd关了之后计数掉0,还是能被卸载。

解决方案

不要依赖fops的owner自动计数,手动在open、mmap回调里维护模块引用计数,在release、munmap对应的vma关闭逻辑里释放计数,确保只要有用户态持有fd或者持有mmap映射,模块引用计数就不为0,无法被卸载。
具体需要实现三个核心逻辑:

  • 实现.open回调:打开文件时调用try_module_get(THIS_MODULE),给模块计数+1
  • 实现.release回调:关闭文件时调用module_put(THIS_MODULE),给模块计数-1
  • 实现.mmap回调时,在完成remap_pfn_range等映射操作前额外调用一次try_module_get(THIS_MODULE),同时给映射的vm_operations_struct设置.close回调,在munmap触发vma关闭时调用module_put(THIS_MODULE),避免mmap后用户态关闭fd但映射还存在时计数不准的问题。
修正后代码

module.c

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/mm.h>

static struct proc_dir_entry *proc_file;

static void test_vma_close(struct vm_area_struct *vma)
{
    // mmap映射解除时,释放之前持有的模块引用
    module_put(THIS_MODULE);
}

static const struct vm_operations_struct test_vm_ops = {
    .close = test_vma_close,
};

static int test_open(struct inode *inode, struct file *file)
{
    // 打开文件时增加模块引用
    try_module_get(THIS_MODULE);
    return 0;
}

static int test_release(struct inode *inode, struct file *file)
{
    // 关闭文件描述符时释放模块引用
    module_put(THIS_MODULE);
    return 0;
}

static int test_mmap(struct file *file, struct vm_area_struct *vma)
{
    // 建立mmap映射时额外增加一次模块引用,防止fd关闭后映射仍存在时模块被卸载
    try_module_get(THIS_MODULE);
    vma->vm_ops = &test_vm_ops;

    // 此处替换为实际业务的mmap逻辑,比如调用remap_pfn_range映射内核内存到用户态
    // remap_pfn_range(vma, vma->vm_start, pfn, vma->vm_end - vma->vm_start, vma->vm_page_prot);
    return 0;
}

static const struct file_operations test_file_ops = {
    .owner = THIS_MODULE,
    .open = test_open,
    .release = test_release,
    .mmap = test_mmap
};

static int __init initialize(void) {
    int error = 0;

    proc_file = proc_create(THIS_MODULE->name, 0444, NULL, &test_file_ops);
    if (!proc_file) {
        error = -EIO;
    }

    return error;
}

static void __exit teardown(void) {
    proc_remove(proc_file);
}

module_init(initialize);
module_exit(teardown);
MODULE_LICENSE("GPL");

Makefile

obj-m += test.o
test-objs := module.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

注意:原Makefile里使用SUBDIRS=是旧版本内核的写法,新版本内核直接用M=指定模块目录即可,不需要额外加EXTRA_CFLAGS引用用户态头文件,内核模块编译会自动使用内核源码树的头文件,添加用户态头文件路径反而会引发编译冲突。

验证方式
  • 加载模块后,用户态打开/proc下对应的模块文件,执行lsmod可以看到模块引用计数为1,此时执行rmmod会直接返回Module is in use无法卸载
  • 对文件做mmap映射后就算关闭fd,只要映射没通过munmap解除,模块引用计数始终保持为1,无法被卸载
  • 关闭fd、解除所有mmap映射后,引用计数回到0,才能正常rmmod卸载,不会触发系统崩溃。

内容的提问来源于stack exchange,提问作者Anton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 18:27:40