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

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐