【Linux 驱动小白教程】四、模块符号导出 EXPORT_SYMBOL + 模块传参完整实操
接上篇:内核 Kconfig & Makefile 配置编译全流程
前面我们学会了驱动模块编译、Kconfig 配置。本篇讲解两个内核模块高频知识点:模块符号导出 EXPORT_SYMBOL、模块命令行传参。实际项目多模块联合开发、调试参数不用重新编译驱动全靠这两套 API,面试经常考。
一、模块符号导出 EXPORT_SYMBOL
1. 什么是符号导出
内核每个.ko模块有自己的函数、全局变量。
默认情况下:A 模块的函数,B 模块无法直接调用。EXPORT_SYMBOL()作用:把本模块的函数 / 全局变量符号导出到内核符号表,其他内核模块就可以调用这个函数。
区分两个宏
EXPORT_SYMBOL(sym)
导出符号,GPL 与非 GPL 模块都可以使用该符号。EXPORT_SYMBOL_GPL(sym)
只允许声明MODULE_LICENSE("GPL")的模块使用;非 GPL 模块调用会内核告警。项目开发优先使用 EXPORT_SYMBOL_GPL。
示例代码 drv_a.c(提供函数,导出符号)
#include<linux/module.h>#include<linux/init.h>// 待导出函数intdrv_add(inta,intb){returna+b;}// 导出符号,其他ko可以调用drv_addEXPORT_SYMBOL_GPL(drv_add);staticint__initdrv_a_init(void){printk("drv_a 模块加载成功\n");return0;}staticvoid__exitdrv_a_exit(void){printk("drv_a 模块卸载成功\n");}module_init(drv_a_init);module_exit(drv_a_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ZKX");MODULE_DESCRIPTION("导出符号模块A");drv_b.c(外部模块调用 drv_a 导出的函数)
#include<linux/module.h>#include<linux/init.h>// 声明外部符号,告诉编译器这个函数在别的模块externintdrv_add(inta,intb);staticint__initdrv_b_init(void){intret=drv_add(10,20);printk("drv_b调用drv_add结果:%d\n",ret);return0;}staticvoid__exitdrv_b_exit(void){printk("drv_b 模块卸载成功\n");}module_init(drv_b_init);module_exit(drv_b_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ZKX");MODULE_DESCRIPTION("使用外部符号模块B");⚠️实操顺序非常重要
编译两个 ko:
drv_a.ko、drv_b.ko先加载提供符号的模块 drv_a.ko
sudoinsmod drv_a.ko# 再加载使用符号的模块sudoinsmod drv_b.ko查看打印
dmesg卸载顺序反过来:先 rmmod drv_b,再 rmmod drv_a
踩坑:先加载 drv_b 会报错:Unknown symbol drv_add。符号还没导出,就调用。
符号存放:内核符号表,
/proc/kallsyms,可以 grep 查看导出符号
cat/proc/kallsyms|grepdrv_add多模块编译注意:编译时需要
Module.symvers,drv_a 编译生成的 Module.symvers 拷贝到 drv_b 编译目录,否则编译阶段报未定义警告。
二、模块传参:module_param 系列 API
应用场景:加载模块
insmod/modprobe的时候,命令行直接给驱动传入参数,不需要重新编译驱动,调试非常方便。
头文件必须引入:
#include<linux/moduleparam.h>四个核心 API
module_param(name, type, perm)普通单个参数module_param_array(name, type, nump, perm)数组参数module_param_string(name, string, len, perm)字符串参数MODULE_PARM_DESC(_parm, desc)参数说明,modinfo 可以看到
参数说明
name:变量名,驱动内部定义的全局变量type:支持类型:byte, short, ushort, int, uint, long, ulong, bool, charp- charp:字符指针,字符串
perm:sysfs 文件权限/sys/module/xxx/parameters/0:不在 sysfs 创建文件,只能命令行传参0644:rw‑r‑‑r‑‑ 用户可读,root 可写;常用 0644
注意:perm 不能写 0777,内核不允许。
nump:module_param_array 专用,输出实际传入数组元素个数,传 NULL 不接收。len:module_param_string,字符串缓冲区最大长度。
完整可运行示例代码 param_drv.c
#include<linux/module.h>#include<linux/init.h>#include<linux/moduleparam.h>// 1.普通单个参数staticintdev_id=1;module_param(dev_id,int,0644);MODULE_PARM_DESC(dev_id,"设备ID,int类型,默认1");staticbool enable_log=false;module_param(enable_log,bool,0644);MODULE_PARM_DESC(enable_log,"是否开启日志打印,bool");// 2.数组参数staticintbuf[8];staticintbuf_cnt;module_param_array(buf,int,&buf_cnt,0644);MODULE_PARM_DESC(buf,"int数组,最多8个元素");//3.字符串参数staticchardev_name[32];module_param_string(dev_name,dev_name,sizeof(dev_name),0644);MODULE_PARM_DESC(dev_name,"设备名字符串");staticint__initparam_drv_init(void){inti;printk("====模块传参打印====\n");printk("dev_id = %d\n",dev_id);printk("enable_log = %d\n",enable_log);printk("dev_name = %s\n",dev_name);printk("数组实际传入元素个数:%d\n",buf_cnt);for(i=0;i<buf_cnt;i++){printk("buf[%d] = %d\n",i,buf[i]);}return0;}staticvoid__exitparam_drv_exit(void){printk("param_drv 模块卸载\n");}module_init(param_drv_init);module_exit(param_drv_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ZKX");MODULE_DESCRIPTION("模块传参demo");实操命令
编译生成param_drv.ko
1. insmod 传递参数
# 传参语法 insmod xxx.ko 变量名=值 数组=1,2,3,4sudoinsmod param_drv.kodev_id=100enable_log=1dev_name="mydev"buf=10,20,30查看打印
dmesg2. modprobe 传参
modprobe 传参不加 ko 后缀
sudomodprobe param_drvdev_id=200dev_name="testmod"buf=1,3,53. modinfo 查看参数描述(MODULE_PARM_DESC 生效)
modinfo param_drv.ko可以看到 parm 一栏,每个参数的说明文字。
4.sysfs 下查看参数(perm=0644 才生成)
ls/sys/module/param_drv/parameters/cat/sys/module/param_drv/parameters/dev_id卸载模块
sudormmod param_drv关键注意点
参数必须定义为全局静态变量 static,局部栈变量不能用于 module_param。
module_param_string:拷贝字符串到缓冲区;charp类型 module_param 只是指针,指向用户传入字符串,不要修改。数组
module_param_array超出数组定义长度的元素会被丢弃。perm=0:sysfs 不生成参数文件,只能加载时赋值,运行时不能改;0644 运行时 root 可以修改 sysfs 文件动态改参数。
MODULE_PARM_DESC 仅仅是注释信息,给 modinfo 读取,不影响代码逻辑。
本篇知识点总结
模块符号导出
| 宏 | 作用 |
|---|---|
EXPORT_SYMBOL(sym) | 导出符号,任意模块都可以调用 |
EXPORT_SYMBOL_GPL(sym) | 仅 GPL 协议模块可调用,项目推荐 |
实操要点:先加载导出符号的模块,再加载使用符号的模块;卸载顺序反过来;查看符号
cat /proc/kallsyms。
模块传参
| API | 功能 |
|---|---|
#include <linux/moduleparam.h> | 模块传参头文件,必须包含 |
module_param(name,type,perm) | 单个变量传参 |
module_param_array(name,type,nump,perm) | 数组传参,nump 返回实际元素数量 |
module_param_string(name,string,len,perm) | 字符串拷贝到缓冲区 |
MODULE_PARM_DESC(parm,desc) | 参数描述,modinfo 查看 |
传参类型:
int、bool、charp(字符串指针);perm 常用0644或者0;insmod/modprobe 加载时变量=值传参。
以上为作者知识总结,教学不易,点赞收藏达标持续更新!
