THIS_MODULE详解

本文深入探讨内核模块中的structmodule结构,详细解释其各部分含义及作用,帮助开发者更好地理解和使用内核模块。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转帖网址:

http://blog.csdn.net/a954423389/archive/2010/12/27/6101369.aspx 

源码位置:

@ kernel/module.c

@ include/linux/module.h


结构体struct module在内核中代表一个内核模块,通过insmod(实际执行init_module系统调用)把自己编写的内核模块插入内核时,模块便与一个 struct module结构体相关联,并成为内核的一部分。下面是结构体struct module的完整定义,接下来会逐个解释:

    struct module

    {

        enum module_state state;

        struct list_head list;

        char name[MODULE_NAME_LEN];

 

        struct module_kobject mkobj;

        struct module_param_attrs *param_attrs;

        const char *version;

        const char *srcversion;

 

        const struct kernel_symbol *syms;

        unsigned int num_syms;

        const unsigned long *crcs;

 

        const struct kernel_symbol *gpl_syms;

        unsigned int num_gpl_syms;

        const unsigned long *gpl_crcs;

 

        unsigned int num_exentries;

        const struct exception_table_entry *extable;

 

        int (*init)(void);

        void *module_init;

        void *module_core;

        unsigned long init_size, core_size;

        unsigned long init_text_size, core_text_size;

        struct mod_arch_specific arch;

        int unsafe;

        int license_gplok;

 

#ifdef CONFIG_MODULE_UNLOAD

        struct module_ref ref[NR_CPUS];

        struct list_head modules_which_use_me;

        struct task_struct *waiter;

        void (*exit)(void);

#endif

 

#ifdef CONFIG_KALLSYMS

        Elf_Sym *symtab;

        unsigned long num_symtab;

        char *strtab;

        struct module_sect_attrs *sect_attrs;

#endif

        void *percpu;

        char *args;

    };

    我们插入一个内核模块,一般会使用工具insmod,该工具实际上调用了系统调用init_module,在该系统调用函数中,首先调用 load_module,把用户空间传入的整个内核模块文件创建成一个内核模块,返回一个struct module结构体。内核中便以这个结构体代表这个内核模块。

    state是模块当前的状态。它是一个枚举型变量,可取的值为:MODULE_STATE_LIVE,MODULE_STATE_COMING,MODULE_STATE_GOING。分别表示模块当前正常使用中(存活状态),模块当前正在被加载,模块当前正在被卸载。load_module函数中完成模块的部分创建工作后,把状态置为MODULE_STATE_COMING,sys_init_module函数中完成模块的全部初始化工作后(包括把模块加入全局的模块列表,调用模块本身的初始化函数),把模块状态置为MODULE_STATE_LIVE,最后,使用rmmod工具卸载模块时,会调用系统调用delete_module,会把模块的状态置为MODULE_STATE_GOING。这是模块内部维护的一个状态。

    list是作为一个列表的成员,所有的内核模块都被维护在一个全局链表中,链表头是一个全局变量struct module *modules。任何一个新创建的模块,都会被加入到这个链表的头部,通过modules->next即可引用到。

    name是模块的名字,一般会拿模块文件的文件名作为模块名。它是这个模块的一个标识。

    另外,还要介绍一下宏THIS_MODULE,它的定义如下是#define THIS_MODULE (&__this_module),__this_module是一个struct module变量,代表当前模块,跟current有几分相似。可以通过THIS_MODULE宏来引用模块的struct module结构,试试下面的模块:

    #include <linux/module.h>

 

    MODULE_LICENSE("Dual BSD/GPL");

 

    static int hello_init(void)

    {

        unsigned int cpu = get_cpu();

        struct module *mod;

        printk(KERN_ALERT "this module: %p==%p/n", &__this_module, THIS_MODULE );

        printk(KERN_ALERT "module state: %d/n", THIS_MODULE->state );

        printk(KERN_ALERT "module name: %s/n", THIS_MODULE->name );

        list_for_each_entry(mod, *(&THIS_MODULE->list.prev), list )

                printk(KERN_ALERT "module name: %s/n", mod->name );

        return 0;

    }

 

    static void hello_exit(void)

    {

        printk(KERN_ALERT "module state: %d/n", THIS_MODULE->state );

    }

 

    module_init(hello_init);

    module_exit(hello_exit);

 

owner是一个struct module *类型的结构体指针,现在告诉你的是每个struct module结构体在内核里都代表了一个内核模块,就像十七大里的每个代表都代表了一批人,至于代表了什么人,选他们的人才知道,同样,每个struct module结构体代表了什么模块,对它进行初始化的模块才知道。当然,初始化这个结构不是写驱动的人该做的事,是在刚才略过的那个从insmod或 modprobe到你驱动的xxx_init函数的曲折过程中做的事。insmod命令执行后,会调用kernel/module.c里的一个系统调用init_module,它会调用load_module函数,将用户空间传入的整个内核模块文件创建成一个内核模块,并返回一个struct module结构体,从此,内核中便以这个结构体代表这个内核模块。

再看看THIS_MODULE宏是什么意思,它在include/linux/module.h里的定义是

85 #define THIS_MODULE (&__this_module)

    是一个struct module变量,代表当前模块,与那个著名的current有几分相似,可以通过THIS_MODULE宏来引用模块的struct module结构,比如使用THIS_MODULE->state可以获得当前模块的状态。现在你应该明白为啥在那个岁月里,你需要毫不犹豫毫不迟疑的将struct usb_driver结构里的owner设置为THIS_MODULE了吧,这个owner指针指向的就是你的模块自己。那现在owner咋就说没就没了那?这个说来可就话长了,咱就长话短说吧。不知道那个时候你有没有忘记过初始化owner,反正是很多人都会忘记,大家都把注意力集中到probe、 disconnect等等需要动脑子的角色上面了,这个不需要动脑子,只需要花个几秒钟指定一下的owner反倒常常被忽视,这个就是容易得到的往往不去珍惜,不容易得到的往往日日思量着去争取。于是在2006年的春节前夕,在咱们都无心工作无心学习等着过春节的时候,Greg坚守一线,去掉了 owner,于是千千万万个写usb驱动的人再也不用去时刻谨记初始化owner了。咱们是不用设置owner了,可core里不能不设置,struct usb_driver结构里不是没有owner了么,可它里面嵌的那个struct device_driver结构里还有啊,设置了它就可以了。于是Greg同时又增加了usb_register_driver()这么一层,usb_register()可以通过将参数指定为THIS_MODULE去调用它,所有的事情都挪到它里面去做。反正usb_register() 也是内联的,并不会增加调用的开销。

其余相关网址,可供参考:

http://hi.baidu.com/_huangshuijing/blog/item/253f9a9516183f0c7bf480c5.html

http://www.embedu.org/Column/Column92.htm

<think>嗯,用户的问题是关于Linux内核模块中THIS_MODULE如何在不同驱动程序中定位自身的。这是一个相当专业的驱动开发问题,用户应该是在编写或研究内核模块时遇到了相关困惑。 从用户提供的引用材料来看,引用[1]详细解释了模块初始化函数如何通过__this_module结构体与内核交互,引用[2]则区分了静态编译和动态加载两种方式,引用[3]提到module_init()宏的特殊处理,这些都很关键。 用户真正的困惑点可能在于:当多个驱动模块同时存在时,内核如何确保每个模块的THIS_MODULE都指向正确的模块数据结构。这涉及到内核模块加载机制的核心原理。 首先需要明确THIS_MODULE的本质。在Linux内核中,THIS_MODULE实际上是一个宏,展开后就是指向当前模块的module结构体指针。这个结构体在内核加载模块时动态创建,包含了模块的所有元数据。 关键机制在于重定位技术。当模块被insmod加载时: 1. ELF文件中的符号引用会被动态解析 2. 特别地,__this_module这个特殊符号会被替换为内核分配的module结构体地址 3. 模块内所有THIS_MODULE引用都指向这个唯一地址 这个替换过程发生在模块重定位阶段。引用[1]提到的".init"段初始化函数指针也是类似原理。不同模块的THIS_MODULE之所以互不冲突,是因为每个模块都有自己的重定位表,内核会为每个模块单独分配module结构体。 用户可能还隐含想知道如何验证这一点。建议可以通过查看/proc/kallsyms来观察不同模块的THIS_MODULE实际地址,或者在内核日志中打印THIS_MODULE的值来验证其唯一性。 考虑到用户的技术水平,解释时需要避免过多底层细节,但又要保证准确性。重点突出“每个模块独立分配module结构体”和“重定位时替换THIS_MODULE”这两个关键点。</think>在 Linux 内核驱动程序中,`THIS_MODULE` 宏用于获取指向当前模块自身的 `struct module` 结构体的指针。其定位自身的机制主要依赖于编译器和内核模块加载器的协作,具体如下: ### 定位机制详解 1. **特殊符号 `__this_module` 的声明**: * 每个内核模块的源代码中(通常在由 `MODULE` 宏自动生成的部分)会声明一个名为 `__this_module` 的外部符号: ```c extern struct module __this_module; ``` * 这个声明告诉编译器,`__this_module` 是一个在别处定义的 `struct module` 类型的变量。 2. **模块对象文件的特殊 Section**: * 当使用 `obj-m` 编译选项将代码编译成可加载模块(`.ko` 文件)时,编译器会创建一个名为 `.gnu.linkonce.this_module` 的特殊 Section。 * **关键点**:在这个 Section 中,编译器会放置一个 **该模块独有的** `struct module` 类型变量的定义,并将这个变量命名为 `__this_module`。例如: ```c struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, // 指向模块初始化函数 .exit = cleanup_module, // 指向模块退出函数 (如果定义了) // ... 填充其他成员 ... }; ``` * 这个 `__this_module` 变量包含了该模块的元信息:模块名 (`name`)、初始化函数指针 (`init`)、退出函数指针 (`exit`)、许可证 (`license`)、作者 (`author`)、描述 (`description`) 等。 3. **`THIS_MODULE` 宏的定义**: * `THIS_MODULE` 在 Linux 内核头文件(通常是 `linux/export.h` 或相关文件)中定义为一个简单的宏: ```c #define THIS_MODULE (&__this_module) ``` * 因此,在驱动程序的代码中,任何使用 `THIS_MODULE` 的地方,都会被预处理器替换为 `&__this_module`,即获取当前模块 `__this_module` 变量的地址。 4. **内核模块加载器 (`insmod/modprobe`) 的作用**: * 当用户使用 `insmod` 或 `modprobe` 加载一个 `.ko` 文件时,内核的模块加载器开始工作。 * 加载器执行的关键步骤包括: * **分配内存**:在内核空间为模块的代码、数据和 `__this_module` 结构体分配内存。 * **重定位 (Relocation)**:这是核心步骤。加载器解析模块对象文件中的重定位表(`.rela.gnu.linkonce.this_module` 或其他相关 Section)。对于所有引用 `__this_module` 的地方(也就是所有使用 `THIS_MODULE` 的地方),加载器会计算出**本次加载为这个特定模块实例分配的 `__this_module` 结构体在内存中的实际地址**,并用这个真实地址**覆盖**掉目标代码中原来占位的地址(通常是 0 或其他无效值)。 * **初始化**:加载器填充 `__this_module` 结构体的其他成员(如依赖关系列表),并最终调用模块的初始化函数 (`init_module`)。 5. **不同驱动中的独立性**: * 每个独立编译的内核模块(`.ko` 文件)在其对象文件中**都有自己的 `.gnu.linkonce.this_module` Section**,其中包含**自己唯一的 `__this_module` 变量定义**。 * 当内核加载模块 A 时,它只处理模块 A 对象文件中的重定位项。所有模块 A 代码中引用 `__this_module`(即 `THIS_MODULE`)的地方,都会被重定位到内核为模块 A 分配的 `__this_module` 结构体的真实地址上。 * 当内核加载模块 B 时,过程完全独立。它为模块 B 分配新的内存,创建模块 B 自己的 `__this_module` 实例,并将模块 B 代码中所有 `THIS_MODULE` 引用重定位到这个新地址上。 * 因此,模块 A 中的 `THIS_MODULE` 总是指向模块 A 自己的 `struct module`,模块 B 中的 `THIS_MODULE` 总是指向模块 B 自己的 `struct module`,两者互不干扰。 ### 总结 `THIS_MODULE` 能准确定位当前模块自身的核心机制在于: 1. **每个模块独有**:每个可加载模块(`.ko`)在编译时,在其对象文件内部定义了一个名为 `__this_module` 的 `struct module` 变量(位于特定 Section)。 2. **宏绑定**:`THIS_MODULE` 宏简单定义为 `&__this_module`。 3. **动态重定位**:内核模块加载器在加载模块时,负责将该模块内部所有引用 `__this_module`(即 `THIS_MODULE`)的指令,**动态修正**为指向内核为本次加载所分配的、属于该模块实例的 `struct module` 结构体的真实内存地址。 这种机制确保了,无论系统中同时加载了多少个不同的驱动程序模块,每个模块内部的 `THIS_MODULE` 总能正确地指向**代表自身**的那个 `struct module` 对象。这为模块引用计数、资源管理(如 `try_module_get(THIS_MODULE)` / `module_put(THIS_MODULE)`)以及访问模块元数据提供了基础[^1][^2][^3]。 --- **相关问题** 1. **`THIS_MODULE` 的主要用途是什么?** (例如,在 `cdev_add`, `try_module_get`, `module_put` 等函数中的作用) 2. **如果驱动程序被静态编译进内核(而不是作为模块),`THIS_MODULE` 的值会是什么?** 这会对引用计数操作 (`try_module_get`/`module_put`) 产生什么影响? 3. **`struct module` 结构体主要包含哪些重要的成员信息?** 除了 `name`, `init`, `exit` 之外,还有哪些关键字段? 4. **内核模块加载器在重定位过程中,除了处理 `__this_module`,还需要处理哪些类型的符号引用?** (例如,导出的内核函数、变量等) 5. **`module_init` 宏和 `__this_module` 的 `.init` 成员之间有什么关系?** `module_init` 是如何将开发者的初始化函数挂接到模块结构上的? [^1]: 这个文件中定义了一个struct module类型的变量`__this_module`, 并且`.init`成员已经被填上了模块初始化函数的名字"init_module", `.exit`成员也是这样。这样当内核把模块载入自己的地址空间, 完成链接器的工作之后, 这个`.init`字段就指向真实的模块初始化函数地址了... `if (mod->init != NULL) ret = do_one_initcall(mod->init);`。 [^2]: 模块代码有两种运行方式,一个是静态编译连接进内核,在系统启动过程中进行初始化;另外一个是编译成可动态加载的module,通过`insmod`动态加载重定位到内核。这两种方式可以在Makefile中通过`obj-y`或`obj-m`选项进行选择。 [^3]: 内核为满足不同初始化等级,设计了1~7共7个等级...还有其它一些宏,用于各种任务需求,如模块加载宏`module_init()`, `module_exit()`,其处理又略有不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值