linux的notifier_block内核通知链

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] 可以是 atomicblockingrawsrcu

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.kosubscriber.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
  • 可以看到,当publisherfire_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:出错了,停止调用
相关推荐
say_fall1 小时前
Git完全入门指南-从概念到实战掌握版本控制的核心
linux·运维·服务器·git·学习
时空自由民.1 小时前
Arm Coretex-M核MCU做IAP/OTA升级时候为什么要做中断向量表地址偏移?
arm开发·单片机·嵌入式硬件
不脱发的程序猿2 小时前
MCU升级固件合并和转换工具
单片机·嵌入式硬件
大明者省2 小时前
手机访问虚拟机里面的网站(从虚拟机桥接网络到宝塔面板可访问)
linux·服务器·网络
Szime2 小时前
CS57167半桥驱动H桥方案,国产替代BOM配单选型
驱动开发
剑神一笑2 小时前
Linux netstat 命令深度解析:从网络连接到端口监控的完整实现
linux·运维·网络
liulilittle2 小时前
TCP UCP v1.0 拥塞控制算法(Linux Kernel CC-A)
linux·网络·网络协议·tcp/ip·c·通信·拥塞控制
wangbing11252 小时前
和挖矿做斗争3
linux·运维·服务器
神秘剑客_CN2 小时前
Ubuntu 26.04使用笔记
linux·笔记·ubuntu