Linux内核模块通信 | 符号导出解析

💡 原文中文,约18900字,阅读约需45分钟。
📝

内容提要

Linux内核提供了多种模块间通讯的方式,其中最便捷的方式之一是通过函数或变量符号导出,然后直接调用。内核维护着一个内核符号表,即kallsyms,其中包含了所有Linux内核中的导出符号。要启用kallsyms,需要在编译内核时将CONFIG_KALLSYMS设置为y。导出符号时,需要在模块函数定义之后使用EXPORT_SYMBOL(函数名),并在调用该函数的模块中使用extern进行声明。

🔎

延伸解读

符号导出与模块依赖管理

导出符号会建立模块间的依赖关系。加载时,必须先加载定义符号的模块,再加载使用符号的模块,否则会出现Unknown symbol错误。卸载时顺序相反,先卸载调用模块,再卸载被调用模块,否则会因模块被占用而无法卸载。通过lsmod或modinfo可以查看模块的依赖关系,帮助排查加载和卸载问题。

EXPORT_SYMBOL与EXPORT_SYMBOL_GPL的许可差异

EXPORT_SYMBOL导出的符号对所有内核模块可见,而EXPORT_SYMBOL_GPL导出的符号仅允许声明了GPL兼容许可证的模块使用。如果调用模块未包含MODULE_LICENSE("GPL"),加载时会报Unknown symbol错误。这一机制体现了GPL协议的传染性,要求使用GPL符号的模块也必须以GPL兼容许可证发布。

内核符号表与符号类型解析

内核符号表kallsyms记录了所有导出符号,可通过/proc/kallsyms查看,但普通用户看到的地址为0,需root权限才能查看真实地址。符号类型中,大写字母表示全局符号,可被其他模块引用;小写字母表示局部符号,仅在本模块内可见。使用EXPORT_SYMBOL导出的符号会显示为大写,而未导出的则为小写,这有助于判断符号是否可供外部使用。

模块编译时的符号查找顺序

编译内核模块时,符号查找按顺序进行:首先在本模块符号表中查找,然后在内核全局符号表中查找,最后在模块目录下的Module.symvers文件中查找。如果使用KBUILD_EXTRA_SYMBOLS指定其他模块的Module.symvers,必须使用绝对路径,否则会因解析起点错误而导致编译失败。理解这一顺序有助于解决模块编译时的符号未定义问题。

❓

Q&A

Linux内核模块如何进行通信?

Linux内核模块可以通过导出符号的方式进行通信,使用EXPORT_SYMBOL导出函数或变量,其他模块通过extern声明来调用。

什么是kallsyms,如何启用它?

kallsyms是Linux内核的符号表,包含所有导出符号。要启用它,需要在编译内核时将CONFIG_KALLSYMS设置为y。

EXPORT_SYMBOL和EXPORT_SYMBOL_GPL有什么区别?

EXPORT_SYMBOL将符号对所有内核代码公开,而EXPORT_SYMBOL_GPL仅允许GPL许可的模块调用这些符号。

如何解决Unknown symbol错误?

Unknown symbol错误通常是因为未正确加载依赖模块,确保先加载被调用模块,再加载调用模块。

模块的全局符号如何导出?

模块的全局符号必须在模块文件的全局部分定义,并使用EXPORT_SYMBOL导出,调用模块需进行extern声明。

如何查看内核符号表中的符号?

可以通过访问/proc/kallsyms查看内核符号表中的符号,但需要root权限才能看到真实地址。

🏷️

标签

➡️

继续阅读