当前位置: 首页 > news >正文

【Linux 驱动小白教程】四、模块符号导出 EXPORT_SYMBOL + 模块传参完整实操

接上篇:内核 Kconfig & Makefile 配置编译全流程
前面我们学会了驱动模块编译、Kconfig 配置。本篇讲解两个内核模块高频知识点:模块符号导出 EXPORT_SYMBOL模块命令行传参。实际项目多模块联合开发、调试参数不用重新编译驱动全靠这两套 API,面试经常考。

一、模块符号导出 EXPORT_SYMBOL

1. 什么是符号导出

内核每个.ko模块有自己的函数、全局变量。
默认情况下:A 模块的函数,B 模块无法直接调用
EXPORT_SYMBOL()作用:把本模块的函数 / 全局变量符号导出到内核符号表,其他内核模块就可以调用这个函数。

区分两个宏

  1. EXPORT_SYMBOL(sym)
    导出符号,GPL 与非 GPL 模块都可以使用该符号

  2. 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");

⚠️实操顺序非常重要

  1. 编译两个 ko:drv_a.kodrv_b.ko

  2. 先加载提供符号的模块 drv_a.ko

sudoinsmod drv_a.ko# 再加载使用符号的模块sudoinsmod drv_b.ko
  1. 查看打印dmesg

  2. 卸载顺序反过来:先 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

  1. module_param(name, type, perm)普通单个参数

  2. module_param_array(name, type, nump, perm)数组参数

  3. module_param_string(name, string, len, perm)字符串参数

  4. 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

查看打印

dmesg
2. modprobe 传参

modprobe 传参不加 ko 后缀

sudomodprobe param_drvdev_id=200dev_name="testmod"buf=1,3,5
3. 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

关键注意点

  1. 参数必须定义为全局静态变量 static,局部栈变量不能用于 module_param。

  2. module_param_string:拷贝字符串到缓冲区;charp类型 module_param 只是指针,指向用户传入字符串,不要修改。

  3. 数组module_param_array超出数组定义长度的元素会被丢弃。

  4. perm=0:sysfs 不生成参数文件,只能加载时赋值,运行时不能改;0644 运行时 root 可以修改 sysfs 文件动态改参数。

  5. 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 加载时变量=值传参。

以上为作者知识总结,教学不易,点赞收藏达标持续更新!

http://www.jsqmd.com/news/1401051/

相关文章:

  • 办卡储值哪家划算?跑7家实测 汉阳餐厅推荐收好
  • Python pip镜像源配置全攻略:原理、方法与实战技巧
  • Meta Muse Glimmer 30B开源大模型:Apache 2.0许可下的本地部署与实战指南
  • Firefox数据恢复全攻略:从配置文件原理到书签、历史、标签页找回
  • 珠海发电机回收公司哪家靠谱?五星回收三大品牌深度对比与避坑指南 - 广东再生资源回收
  • 2026年8月上海债务纠纷律所:借贷合同违约应对与诉讼策略制定指南 - 品牌深度评测
  • GLM ZCode深度解析:AI编程助手如何重塑开发流程与成本策略
  • 直播录屏自动化处理:FFmpeg工具链与Python脚本实践指南
  • AI智能体开发实战:从架构设计到工程落地的核心指南
  • 基于OpenClaw与AI Agent的腾讯云COS智能管理实践
  • 销售周报不再靠回忆,讯飞AI录音卡通话自动生成客户跟进清单 - 城刊速递
  • 2026年7月贵阳市二手房价格深度分析报告
  • 分布式事务seata框架下at/tcc/sage模式代码
  • 票据销毁是什么?为什么要销毁票据? - 生态测评师
  • 【Bug已解决】[Security] Incomplete Fix for CVE-2026-44513: community Pipeline Branch Bypasses trust_remo…
  • Vue 3 中集成 mxGraph 图形库:从原理到工程实践
  • 树莓派Debian系统打造复古游戏站:从系统优化到模拟器配置全攻略
  • 企业级壁纸管理:商用图库筛选与批量处理方案
  • Python自动化监控网页更新并发送提醒
  • 毕业论文AI检测率高原因分析与降重技巧
  • 谷歌云TPU即服务实战指南:从环境配置到性能调优
  • OpenSSL 密码工具套件:openssl 命令完整详解(加密 / 密钥 / 证书实战)
  • 2026年7月遵义市二手房价格深度分析报告
  • 天道15,16集
  • Python学习系统工程:从环境搭建到实战项目的完整路径设计
  • 上海分手协议履行纠纷律所:2026年8月分手协议违约责任法律认定 - 品牌深度评测
  • day26-即梦全流程
  • ANSYS 2024 R2安装与多版本共存部署指南
  • LLM API监控工具Vergilant:实时告警与成本控制实践
  • 从Alpha-Beta剪枝到增量评估:构建高性能五子棋AI的核心技术解析