linux中生成内核模块时部分函数undefined的应对方法

本文探讨了在Linux内核模块编译过程中遇到的函数未定义警告的原因及解决策略,着重解释了EXPORT_SYMBOL的使用及其重要性,并通过实例展示了如何处理这类警告,确保模块在内核环境下的正确运行。

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

linux中生成内核模块时部分函数undefined的应对方法

在编译生成新的nfs.ko文件时,遇到undefined warning,因为在nfs的代码里调用linux内核提供的函数,刚好这个函数内核版本低还没有这个函数。

在生成内核模块时出现下面这种警告:WARNING: "*******"[*******.ko] undefined!

其中""里是函数名称,[]里是所生成的模块位置。

出现这种警告是由于欲在模块A中调用模块B里的函数,这个函数必须要用EXPORT_SYMBOL(函数名)的形式给导出,不然就会发出这样的警告。

EXPORT_SYMBOL(函数名)怎么用网上有相关文章。这里更关心的是这种警告要不要考虑呢,因为编应用程序时习惯性的不考虑warining,但是这里的警告还是得留下意。

生成内核模块,在编译连接的时候没和内核打交道,可能会找不到某些已经EXPORT_SYMBOL 出来的函数,但是由于你生成的是一个内核模块,所以LD不提示error,而是给出一个warning,寄希望于在insmod的时候,内核能够把这个EXPORT_SYMBOL 连接上。如果insmod的时候它查到了之前警告的undefined的诸多函数,那一切安好,便是晴天;要是它还找不到这些undefined的函数,那天崩地裂,错误连连。

所以说看到这些warning时想一想它警告的这些函数有没有在已经存在的内核当中EXPORT_SYMBOL出来了。


<think>我们正在讨论Linux内核中未使用extern关键字却可以直接引用其他文件中函数的原因。根据用户提供的引用[1],extern关键字的主要作用是引用不在同一个文件中的变量或函数。但在Linux内核中,我们经常看到在头文件中声明函数,然后在源文件中直接使用,而无需在每个使用的地方都写extern。这是因为在函数声明中,extern关键字是隐含的(即默认存在的)。具体来说: 1. **函数声明的隐含extern**:在C语言中,当我们在头文件中声明一个函数(例如 `void my_function(void);`),这个声明默认带有extern属性,即使没有显式写出extern关键字。这意味着该函数可能在其他文件中定义。因此,在Linux内核中,我们通常通过包含头文件来获得函数的声明,而无需额外使用extern。 2. **头文件的作用**:内核头文件中包含了大量的函数声明,这些声明都是extern的(隐式或显式)。例如,在Linux内核中,我们经常看到这样的头文件内容: ```c // 例如:include/linux/sched.h extern void schedule(void); ``` 或者更常见的是省略extern,因为函数声明默认就是extern: ```c void schedule(void); ``` 这两种写法是等价的。 3. **编译链接过程**:在编译单个源文件,编译器遇到一个未在当前文件中定义的函数(比如一个只有声明没有定义的函数),它会生成一个符号引用。在链接阶段,链接器会在所有目标文件中查找这个符号的定义,如果找到(比如在另一个目标文件中定义了该函数),就将引用指向该函数的地址。因此,只要在链接能找到函数的定义,就不需要在每个使用的地方显式写extern。 4. **内核模块中的EXPORT_SYMBOL**:在内核模块中,如果要使用其他模块中的函数,则需要通过EXPORT_SYMBOL将函数导出到内核符号表。此,在调用模块中,我们通常通过包含声明该函数的头文件来使用,而不需要自己写extern(因为头文件中的声明已经隐含了extern)。但是,如果模块之间没有提供头文件,那么我们就需要自己使用extern来声明要使用的函数。 5. **为什么可以省略extern**:因为C语言标准规定,函数声明默认具有外部链接(external linkage)属性,即extern。所以,当我们在一个文件中写: ```c void my_function(void); ``` 编译器会认为这个函数是在其他文件中定义的(除非在当前文件中又给出了定义)。因此,在头文件中,我们通常省略extern,因为它是多余的。 6. **变量的情况不同**:对于全局变量,如果我们想在多个文件中共享,则必须在头文件中使用extern来声明(以避免重复定义)。例如: ```c // 在头文件中 extern int global_variable; ``` 然后在某个源文件中定义: ```c int global_variable = 0; ``` 如果不加extern,在头文件中直接写 `int global_variable;`,那么每个包含该头文件的源文件都会定义一个同名的全局变量,导致链接错误。 总结:在Linux内核中,函数声明默认具有extern属性,所以可以省略extern关键字。因此,我们通过包含头文件(其中包含了函数的声明)来使用其他文件中定义的函数。而EXPORT_SYMBOL的作用则是将函数或变量导出到内核符号表,使得其他模块在加载能够解析到这些符号,但这与extern的用法是不同层面的问题(extern是编译链接层面的,而EXPORT_SYMBOL是模块加载运行的)。 根据以上分析,我们可以这样回答用户的问题:</think>### Linux 内核中未使用 `extern` 却直接引用函数的原理 在 Linux 内核开发中,虽然未显式使用 `extern` 关键字却能直接引用其他文件中的函数,这主要基于以下原理: #### 1. **头文件的隐式 `extern` 声明** 在 C 语言中,**函数声明默认具有 `extern` 属性**。当你在头文件中声明函数: ```c // include/linux/sched.h void schedule(void); // 隐式包含 extern 属性 ``` 等同于显式声明: ```c extern void schedule(void); // 显式 extern ``` 因此,当内核源文件包含该头文件: ```c // kernel/sched/core.c #include <linux/sched.h> // 隐式获得 extern void schedule(); ``` 编译器会自动处理为外部引用,无需额外写 `extern`[^1]。 #### 2. **链接器符号解析机制** 编译和链接过程中: 1. **编译阶段**:每个 `.c` 文件独立编译成 `.o` 目标文件 2. **符号表生成**:编译器在目标文件中记录: - 已定义的符号(函数/变量地址) - 未解析的引用(需外部提供的符号) 3. **链接阶段**:链接器扫描所有目标文件: - 匹配未解析引用与已定义符号 - 将引用绑定到实际地址 例如: ```c // file1.c void func() { ... } // 定义符号 // file2.c void func(); // 声明引用(隐式 extern) void caller() { func(); // 链接器解析地址 } ``` #### 3. **内核的特殊处理** Linux 内核通过以下机制强化符号管理: - **统一符号表**:通过 `System.map` 和 `/proc/kallsyms` 维护全局符号表 - **模块导出机制**:`EXPORT_SYMBOL()` 显式导出符号到内核符号表 ```c // kernel/printk/printk.c void printk(const char *fmt, ...) { ... } EXPORT_SYMBOL(printk); // 导出到全局符号表 ``` - **隐式引用**:未导出的符号仅限同一编译单元(static 作用域) #### 4. **与模块交互的区别** | 场景 | 机制 | 是否需要 `extern` | |--------------------|--------------------------|------------------| | 同一内核镜像内引用 | 头文件隐式声明 + 链接器解析 | 否 | | 跨模块引用 | `EXPORT_SYMBOL` + `extern` | 是 | 例如跨模块调用必须显式声明: ```c // 模块A void shared_func(void) { ... } EXPORT_SYMBOL(shared_func); // 模块B extern void shared_func(void); // 必须显式 extern ``` #### 5. **原理验证实验** ```c // func_def.c #include <linux/module.h> void hidden_func(void) { printk("Hidden function called\n"); } // func_user.c #include <linux/module.h> void hidden_func(void); // 隐式 extern 声明 static int __init test_init(void) { hidden_func(); // 直接调用 return 0; } module_init(test_init); ``` **结果**: 编译报错 `undefined reference to hidden_func`,因为: 1. 未通过 `EXPORT_SYMBOL` 导出符号 2. 未在公共头文件中声明 3. 链接器无法跨编译单元解析符号 ### 总结 Linux 内核中未显式使用 `extern` 却能引用函数的本质是: 1. 头文件中的函数声明**默认具有 `extern` 属性** 2. 链接器在**全局符号表中自动解析引用** 3. `EXPORT_SYMBOL` 机制将符号注册到内核符号表 4. 同一内核镜像内的函数通过**头文件包含+链接解析**实现访问[^1][^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值