Linux内核通知链 是一种基于**发布-订阅模式的观察者机制,主要用于解决内核不同部分之间的依赖和通信问题。++当一个模块(发布者)** 的状态发生变化,所有对该事件感兴趣的模块**(订阅者)**都能自动得到通知,从而实现了模块间的松耦合++。
接下来,我们深入介绍它的核心数据结构、类型和具体用法。
核心数据结构:notifier_block
notifier_block 是通知链的核心,每个需要接收通知的"订阅者"模块都需要定义并初始化这样一个结构体,在内核头文件 include/linux/notifier.h 中定义。
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
-
notifier_call:最重要的成员,是一个函数指针。当事件发生时,订阅者要执行的特定操作就通过这个函数被调用。 -
priority:指定了该订阅者的优先级。值越大,在通知链中就越靠前,也就越先被通知。 -
next:用于将多个notifier_block结构体链接成链表。
通知链的四大金刚
为了让代码在不同场景下都能安全、高效地执行,Linux内核将通知链分为了四种类型 ,其主要区别在于运行上下文 和锁机制的不同。
| 类型 | 定义结构体 | 运行环境 | 特点与应用 |
|---|---|---|---|
| 原子通知链 | atomic_notifier_head |
中断或原子上下文 | 回调函数不能阻塞 ,由自旋锁spinlock_t保护,用于对时间要求极为苛刻的事件,如键盘中断处理。 |
| 可阻塞通知链 | blocking_notifier_head |
进程上下文 | 回调函数可以阻塞 (如执行文件I/O操作),由读写信号量rw_semaphore保护,是最常用的类型,如USB设备的热插拔通知。 |
| 原始通知链 | raw_notifier_head |
无限制,但需调用者自行加锁 | 不提供任何内置的同步机制,链表的操作完全由使用者负责同步,性能最高,但需要开发人员完全掌控并发情况。 |
| SRCU通知链 | srcu_notifier_head |
进程上下文 | 可阻塞通知链的一个变种,基于SRCU(Sleepable Read-Copy Update,可睡眠RCU)机制实现,适用于需要更灵活读者-写者锁的场景。 |
核心API介绍
为了实现从初始化到注册、通知和注销的完整生命周期,内核提供了一套与通知链类型一一对应的API。
-
初始化 :使用
[TYPE]_NOTIFIER_HEAD(name)宏来静态定义一个通知链表头。 -
注册与注销 :使用
[type]_notifier_chain_register/unregister来增加或移除一个订阅者。 -
发送通知 :当事件发生时,发布者调用
[type]_notifier_call_chain来遍历链表,依次执行每个订阅者的回调函数。
API命名规则:
[type]可以是atomic、blocking、raw或srcu。
Demo:一个完整的内核通知链实例
下面,我们将通过一个自定义的 TEST_NOTIFIER_CHAIN 阻塞通知链来演示整个过程。
1. 定义事件和模块信息
// notifier_demo.h
#ifndef _NOTIFIER_DEMO_H
#define _NOTIFIER_DEMO_H
#include <linux/notifier.h>
// 定义通知链的事件类型
#define NOTIFIER_DEMO_EVENT_1 0x01
#define NOTIFIER_DEMO_EVENT_2 0x02
// 声明一个外部的可阻塞通知链表头,它将在发布者模块中被定义
extern struct blocking_notifier_head test_chain;
#endif
2. 发布者模块 (publisher.c)
发布者模块负责定义通知链表头,提供注册、注销接口,并在特定事件发生时调用通知函数
// publisher.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include "notifier_demo.h"
// 1. 定义并初始化一个可阻塞通知链的链表头
BLOCKING_NOTIFIER_HEAD(test_chain);
EXPORT_SYMBOL_GPL(test_chain); // 导出符号,供订阅者使用
// 2. 定义供订阅者调用的注册和注销接口
int register_test_notifier(struct notifier_block *nb) {
return blocking_notifier_chain_register(&test_chain, nb);
}
EXPORT_SYMBOL_GPL(register_test_notifier);
int unregister_test_notifier(struct notifier_block *nb) {
return blocking_notifier_chain_unregister(&test_chain, nb);
}
EXPORT_SYMBOL_GPL(unregister_test_notifier);
// 模拟一个事件函数,它会被调用以触发通知
static void fire_notifier_event(void) {
printk(KERN_INFO "publisher: firing event NOTIFIER_DEMO_EVENT_1\n");
// 3. 当事件发生时,调用通知链,通知所有订阅者
blocking_notifier_call_chain(&test_chain, NOTIFIER_DEMO_EVENT_1, NULL);
}
// 模块加载时,创建一个内核线程来模拟事件发生
static int __init publisher_init(void) {
printk(KERN_INFO "publisher: module loaded\n");
fire_notifier_event();
return 0;
}
static void __exit publisher_exit(void) {
printk(KERN_INFO "publisher: module unloaded\n");
}
module_init(publisher_init);
module_exit(publisher_exit);
MODULE_LICENSE("GPL");
3. 订阅者模块 (subscriber.c)
订阅者模块定义自己的 notifier_block,实现回调函数,并通过发布者提供的 register_test_notifier 接口进行注册。
// subscriber.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include "notifier_demo.h"
// 1. 实现订阅者的回调函数
static int test_callback(struct notifier_block *nb,
unsigned long action, void *data) {
switch (action) {
case NOTIFIER_DEMO_EVENT_1:
printk(KERN_INFO "subscriber: received EVENT_1\n");
break;
case NOTIFIER_DEMO_EVENT_2:
printk(KERN_INFO "subscriber: received EVENT_2\n");
break;
default:
printk(KERN_INFO "subscriber: received unknown event\n");
break;
}
// 2. 返回NOTIFY_OK表示正确处理
return NOTIFY_OK;
}
// 3. 定义并初始化notifier_block结构体
static struct notifier_block test_notifier = {
.notifier_call = test_callback,
.priority = 10, // 可选:设置高优先级
};
static int __init subscriber_init(void) {
printk(KERN_INFO "subscriber: module loaded\n");
// 4. 使用发布者提供的接口进行注册
register_test_notifier(&test_notifier);
return 0;
}
static void __exit subscriber_exit(void) {
printk(KERN_INFO "subscriber: module unloaded\n");
// 5. 使用发布者提供的接口进行注销
unregister_test_notifier(&test_notifier);
}
module_init(subscriber_init);
module_exit(subscriber_exit);
MODULE_LICENSE("GPL");
4. 测试结果
按 publisher.ko → subscriber.ko 的顺序加载模块。
# dmesg 输出示例
[ 1234.567890] publisher: module loaded
[ 1234.567891] publisher: firing event NOTIFIER_DEMO_EVENT_1
[ 1234.567892] subscriber: module loaded
[ 1234.567893] subscriber: received EVENT_1
-
可以看到,当
publisher在fire_notifier_event中调用blocking_notifier_call_chain时,内核自动遍历test_chain链表。 -
因为
subscriber已经通过register_test_notifier将自己的test_notifier挂载到test_chain上,所以它的test_callback函数被成功执行。
总结与应用场景
Linux内核通知链机制提供了一种高效、灵活的模块间通信方式。在内核源码中,通知链的应用非常广泛:
-
USB子系统:当USB设备插入或拔出时,通过通知链告知其他相关模块。
-
LCD与触摸屏驱动:LCD屏幕状态改变(亮/灭)时,通过通知链通知TP驱动,以实时开启或关闭触摸功能。
-
网络设备事件:网卡启用或禁用时,通知那些需要感知网络状态变化的协议栈部分。
理解并能灵活运用 notifier_block 是进行Linux内核驱动开发和子系统定制的重要基础。
下面结合一下RK3562的rk818_battery.c电池和充电rk818_charger.c代码说一下电池温度NTC检测温度变化后改变充电电流的情况:
通知链的发出端:
step1:
在k818_battery.c电池驱动中工作队列启动rk818_battery_work(struct work_struct *work)
static int rk818_battery_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(rk818_battery_of_match, &pdev->dev);
struct rk818_battery *di;
struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent);
int ret;
if (!of_id) {
dev_err(&pdev->dev, "Failed to find matching dt id\n");
return -ENODEV;
}
di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
.........
//创建工作队列
di->bat_monitor_wq = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk818-bat-monitor-wq");
INIT_DELAYED_WORK(&di->bat_delay_work, rk818_battery_work); //初始化工作队列
queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work,
msecs_to_jiffies(TIMER_MS_COUNTS * 5));
.........
BAT_INFO("driver version %s\n", DRIVER_VERSION);
return ret;
}
static void rk818_battery_work(struct work_struct *work)
{
struct rk818_battery *di =
container_of(work, struct rk818_battery, bat_delay_work.work);
rk818_bat_update_info(di);
....
rk818_bat_update_temperature(di); //工作队列中轮询电池温度检测
....
rk818_bat_debug_info(di);
queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work,
msecs_to_jiffies(di->monitor_ms)); //等待monitor_ms时间后再次启动工作队列
}
Step2: 发出通知rk818_bat_temp_notifier_callback(di->temperature / 10);
static void rk818_bat_update_temperature(struct rk818_battery *di)
{
static int old_temp, first_time = 1;
u32 ntc_size, *ntc_table;
int i, res, temp;
ntc_table = di->pdata->ntc_table;
ntc_size = di->pdata->ntc_size;
di->temperature = VIRTUAL_TEMPERATURE;
if (ntc_size) {
......
/* if first in, init old_temp */
temp = (i + di->pdata->ntc_degree_from) * 10;
if (first_time == 1) {
di->temperature = temp;
old_temp = temp;
first_time = 0;
}
/*
* compare with old one, it's invalid when over 50
* and we should use old data.
*/
if (abs(temp - old_temp) > 50)
temp = old_temp;
else
old_temp = temp;
di->temperature = temp;
DBG("<%s>. temperature = %d\n",
__func__, di->temperature);
//重点函数,当NTC温度事件发生时,调用通知链,通知所有订阅者
rk818_bat_temp_notifier_callback(di->temperature / 10);
.....
}
}
Step3: 追溯一下内核通知链在驱动相关初始化rk818_bat_temp_notifier_callback
// 1. 定义并初始化一个可阻塞通知链的链表头
static BLOCKING_NOTIFIER_HEAD(rk818_bat_notifier_chain);
// 2. 定义供订阅者调用的注册接口
int rk818_bat_temp_notifier_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&rk818_bat_notifier_chain, nb);
}
EXPORT_SYMBOL_GPL(rk818_bat_temp_notifier_register);
//3. 定义供订阅者调用的注销接口
int rk818_bat_temp_notifier_unregister(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&rk818_bat_notifier_chain, nb);
}
EXPORT_SYMBOL_GPL(rk818_bat_temp_notifier_unregister);
// 模拟一个事件函数,它会被调用以触发通知
static void rk818_bat_temp_notifier_callback(int temp)
{
//真正的发出通知函数:当事件发生时,调用通知链,通知所有订阅者,将temp温度传递出去
blocking_notifier_call_chain(&rk818_bat_notifier_chain, temp, NULL);
}
通知链的接收端:
Step1: 使用发布者提供的接口进行注册:rk818_cg_register_temp_notifier(cg);
static int rk818_charger_probe(struct platform_device *pdev)
{
struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent);
struct rk818_charger *cg;
int ret;
cg = devm_kzalloc(&pdev->dev, sizeof(*cg), GFP_KERNEL);
if (!cg)
return -ENOMEM;
..........
ret = rk818_cg_register_temp_notifier(cg); //使用发布者提供的接口进行注
if (ret) {
dev_err(cg->dev, "register temp notify failed!\n");
goto notify_fail;
}
..........
ret = rk818_cg_init_irqs(cg);
if (ret) {
dev_err(cg->dev, "init irqs failed!\n");
goto irq_fail; //初始化失败,跳转到irq_fail,使用发布者提供的接口进行注销
}
CG_INFO("driver version: %s\n", CG_DRIVER_VERSION);
return 0;
irq_fail:
rk818_bat_temp_notifier_unregister(&cg->temp_nb); //使用发布者提供的接口进行注销
notify_fail:
/* type-c only */
if (cg->pdata->extcon) {
cancel_delayed_work_sync(&cg->host_work);
cancel_delayed_work_sync(&cg->discnt_work);
}
cancel_delayed_work_sync(&cg->usb_work);
..........
destroy_workqueue(cg->finish_sig_wq);
if (cg->pdata->extcon) {
extcon_unregister_notifier(cg->cable_edev, EXTCON_CHG_USB_SDP,
&cg->cable_cg_nb);
..........
} else {
rk_bc_detect_notifier_unregister(&cg->bc_nb);
}
return ret;
}
Step2: rk818_bat_temp_notifier_register(&cg->temp_nb);
static int rk818_cg_register_temp_notifier(struct rk818_charger *cg)
{
int ret;
if (!cg->pdata->tc_count)
return 0;
//Note: struct notifier_block temp_nb;
//1. 实现订阅者的回调函数,接收到通知信息后的回调函数,重点
cg->temp_nb.notifier_call = rk818_cg_temperature_notifier_call,
//2. 重点函数:使用发布者提供的接口进行注册
ret = rk818_bat_temp_notifier_register(&cg->temp_nb);
if (ret) {
dev_err(cg->dev,
"battery temperature notify register failed:%d\n", ret);
return ret;
}
CG_INFO("enable set charge current by temperature\n");
return 0;
}
Step3: 接收通知,实现订阅者的回调函数rk818_cg_temperature_notifier_call
static int rk818_cg_temperature_notifier_call(struct notifier_block *nb,
unsigned long temp, void *data)
{
struct rk818_charger *cg =
container_of(nb, struct rk818_charger, temp_nb);
static int temp_triggered, config_index = -1;
int i, up_temp, down_temp, cfg_current;
int now_temp = temp;
u8 usb_ctrl, chrg_ctrl1;
//接收通知信息,更新电池温度
DBG("%s: receive notify temperature = %d\n", __func__, now_temp);
for (i = 0; i < cg->pdata->tc_count; i++) {
up_temp = cg->pdata->tc_table[i].temp_up;
down_temp = cg->pdata->tc_table[i].temp_down;
cfg_current = cg->pdata->tc_table[i].chrg_current;
if (now_temp >= down_temp && now_temp <= up_temp) {
/* Temp range or charger are not update, return */
if (config_index == i && !cg->charger_changed)
return NOTIFY_DONE;
config_index = i;
cg->charger_changed = 0;
temp_triggered = 1;
if (cg->pdata->tc_table[i].set_chrg_current) {
rk818_cg_set_chrg_current(cg, cfg_current);//根据不同NTC温度,设置充电电流
CG_INFO("temperature = %d'C[%d~%d'C], "
"chrg current = %d\n",
now_temp, down_temp, up_temp,
chrg_cur_sel_array[cfg_current] *
cg->res_div);
} else {
rk818_cg_set_input_current(cg, cfg_current);
CG_INFO("temperature = %d'C[%d~%d'C], "
"input current = %d\n",
now_temp, down_temp, up_temp,
chrg_cur_input_array[cfg_current]);
}
return NOTIFY_DONE;
}
}
/*
* means: current temperature not covers above case, temperature rolls
* back to normal range, so restore default value
*/
if (temp_triggered) {
temp_triggered = 0;
config_index = -1;
rk818_cg_set_chrg_current(cg, cg->chrg_current);
if (cg->ac_in || cg->dc_in)
rk818_cg_set_input_current(cg, cg->chrg_input);
else
rk818_cg_set_input_current(cg, INPUT_CUR450MA);
usb_ctrl = rk818_reg_read(cg, RK818_USB_CTRL_REG);
chrg_ctrl1 = rk818_reg_read(cg, RK818_CHRG_CTRL_REG1);
CG_INFO("roll back temp %d'C, current chrg = %d, input = %d\n",
now_temp,
chrg_cur_sel_array[(chrg_ctrl1 & 0x0f)] * cg->res_div,
chrg_cur_input_array[(usb_ctrl & 0x0f)]);
}
return NOTIFY_DONE; //返回NOTIFY_OK表示正确处理
}
完毕,欢迎讨论!
返回值很重要:
- •
NOTIFY_DONE:继续调用链上的下一个回调 - •
NOTIFY_OK:成功处理,继续调用 - •
NOTIFY_STOP:停止调用链 - •
NOTIFY_BAD:出错了,停止调用