Linux内核模块通信 | 符号导出解析
内容提要
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权限才能看到真实地址。