Linux内核学习24--驱动通信(TODO)
1 内核驱动间通信的5种方式
最近调试驱动,突然发现驱动之间的通讯和应用层之间有很大不同。所以整理了一下。根据不同的业务场景,内核驱动通信通常采用以下几种极简模式:
1.1 导出符号 + 函数回调(最直接、最常用)
-
原理:驱动 A 使用
EXPORT_SYMBOL(my_func)将函数暴露给全局,驱动 B 声明后直接调用。 -
场景:就像前面分析的 Pinctrl/SPI 驱动通信、或者网卡驱动调用物理层(PHY)驱动接口。
1.2 内核事件通知链(Notifier Chains)
-
原理:发布-订阅模式(Observer Pattern)。驱动 A 注册一个观察者,驱动 B 发生某个事件(如电源状态改变、网络链路断开)时广播通知,内核会自动按顺序调用注册的回调函数。
-
场景:系统电源管理休眠通知、CPU调频事件广播。
1.3 内核异步事件网关(内核异步通知/Workqueue)
-
原理:驱动 A 向驱动 B 传递一个数据结构(如
struct work_struct),然后唤醒驱动 B 的工作队列或 Tasklet 进行处理。 -
场景:中断下半部(Bottom Half)数据处理、异步 DMA 完成通知。
1.4 共享内存指针(直接数据交换)
-
原理:驱动 A 分配一块内存 Buffer,把指针(甚至包含读写锁)直接传给驱动 B。
-
场景:音视频 Buffer 传递(如 V4L2 框架中的 dma-buf 共享)。
1.5 内核总线/子系统中介(如 Pinctrl, I2C, SPI 框架)
-
原理:驱动 A 和 B 都不直接认识对方,双方都通过内核 Subsystem Core 的标准接口交互。
-
这个也就是很多内核子系统的实现方式了。比如厂商底层和应用驱动。
2 导出符号+函数回调
代码provider.c
#include <linux/module.h>
#include <linux/kernel.h>
int my_add(int a, int b)
{
return a + b;
}
/*
* 导出给其他ko使用
*/
EXPORT_SYMBOL(my_add);
static int __init provider_init(void)
{
printk("provider loaded\n");
return 0;
}
static void __exit provider_exit(void)
{
printk("provider unloaded\n");
}
module_init(provider_init);
module_exit(provider_exit);
MODULE_LICENSE("GPL");
编译生成provider.ko。
consumer.c
#include <linux/module.h>
#include <linux/kernel.h>
/*
* 声明外部函数
*/
extern int my_add(int a, int b);
static int __init consumer_init(void)
{
int result;
result = my_add(10,20);
printk("result=%d\n", result);
return 0;
}
static void __exit consumer_exit(void)
{
printk("consumer unloaded\n");
}
module_init(consumer_init);
module_exit(consumer_exit);
MODULE_LICENSE("GPL");
编译生成consumer.ko
都insmod之后,查看:
cat /proc/kallsyms | grep my_add
结果如下:

此时可以看到,这个符号已经进入kernel symbol table。也可以正常使用了。

3 内核事件
common_notify.h
#ifndef __COMMON_NOTIFY_H
#define __COMMON_NOTIFY_H
#include <linux/notifier.h>
// 创建阻塞通知链头
BLOCKING_NOTIFIER_HEAD(device_event_head);
// 自定义事件类型
#define DEV_EVENT_PLUG_IN 0x01
#define DEV_EVENT_PLUG_OUT 0x02
#endif
发送端:
#include <linux/init.h>
#include <linux/module.h>
#include "common_notify.h"
static int __init sender_init(void)
{
char *msg1 = "外设已插入";
char *msg2 = "外设拔出";
pr_info("sender: 加载成功,开始广播事件\n");
// 广播插入事件
blocking_notifier_call_chain(&device_event_head,
DEV_EVENT_PLUG_IN, msg1);
// 广播拔出事件
blocking_notifier_call_chain(&device_event_head,
DEV_EVENT_PLUG_OUT, msg2);
return 0;
}
static void __exit sender_exit(void)
{
pr_info("sender 模块卸载\n");
}
module_init(sender_init);
module_exit(sender_exit);
MODULE_LICENSE("GPL");
接收端
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include "common_notify.h"
// 事件回调
static int dev_event_cb(struct notifier_block *nb,
unsigned long action, void *data)
{
pr_info("receiver 收到事件 action=0x%lx, 消息:%s\n",
action, (char *)data);
switch(action)
{
case DEV_EVENT_PLUG_IN:
pr_info("执行外设上线初始化\n");
break;
case DEV_EVENT_PLUG_OUT:
pr_info("关闭外设、释放资源\n");
break;
}
return NOTIFY_OK; // 继续执行剩下的回调
}
// 注册块,priority 越大越优先执行
static struct notifier_block dev_nb = {
.notifier_call = dev_event_cb,
.priority = 2,
};
static int __init receiver_init(void)
{
blocking_notifier_chain_register(&device_event_head, &dev_nb);
pr_info("receiver: 成功注册事件监听\n");
return 0;
}
static void __exit receiver_exit(void)
{
blocking_notifier_chain_unregister(&device_event_head, &dev_nb);
pr_info("receiver: 取消监听,模块退出\n");
}
module_init(receiver_init);
module_exit(receiver_exit);
MODULE_LICENSE("GPL");
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)