在Linux驱动中使用notifier通知链
在Linux驱动中使用notifier通知链
背景
在驱动分析中经常看到fb_notifier_callback
,现在趁有空学习一下。
介绍
linux中的观察者模式是最显然的就是“通知链”模型。
在linux中,如果你想让自己的行为被别人注意到,那么你就要申请一条通知链,然后让所有关注你自己的实体注册到这条通知链上。
最终的效果就是一旦发生一件值得关注的事情,所有的注册者都可以得到通知。
内核中通知链的基础文件就两个:
- 头文件:
include/linux/notifier.h
- 源文件:
kernel/notifier.c
头文件和源文件所有代码加起来不超过1000行,总体来说还是比较好懂。
参考:
- https://blog.csdn.net/Wuhzossibility/article/details/8079025
- https://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html
我自己花了点时间写了一条类似的通知链,但是跨平台(应用程序、Linux内核、单片机)上都可以使用:
介绍
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。
为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。
所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。
图例
对系统A来说,它自己的通知队列上被被人注册了n个回调函数,那么当系统A的某个事件发生时,它必须去遍历自己的事件队列headA,然后依次去尝试执行队列里每个回调函数。
实际上,通知链上的注册的函数不一定都会执行。
对子系统B来说,情况是一样的。
原型定义
核心数据结构
#include <linux/notifier.h>
typedef int (*notifier_fn_t)(struct notifier_block *nb,
unsigned long action, void *data);
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
解析:
notifier_call
:当相应事件发生时应该调用的函数,由被通知方提供;notifier_block *next
:用于链接成链表的指针;priority
:回调函数的优先级,一般默认为0。
拓展
内核代码中一般把通知链命名为xxx_chain
, xxx_nofitier_chain
这种形式。
围绕核心数据结构notifier_block
,内核定义了四种通知链类型。
原子通知链
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。
可阻塞通知链
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
通知链元素的回调函数在进程上下文中运行,允许阻塞。
SRCU 通知链
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};a
可阻塞通知链的一种变体。
原始通知链
struct raw_notifier_head {
struct notifier_block *head;
};
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
网络子系统使用的通知链就是此类型。
API
实际上,除了基本通知链以外,还有其他有3种通知链:原子通知链、可阻塞通知链和原始通知链。
显然,实际上这3种也只是对基本通知链的操作函数进行了封装的。
声明与初始化
在定义自己的通知链的时候,心里必须明确,自己需要一个什么样类型的通知链,是原子的、可阻塞的还是一个原始通知链。
内核中用于定义并初始化不同类通知链的函数分别是:
// 定义并初始化一个名为name的原子通知链
#define ATOMIC_NOTIFIER_HEAD(name) \
struct atomic_notifier_head name = \
ATOMIC_NOTIFIER_INIT(name)
// 定义并初始化一个名为name的阻塞通知链
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
//定义并初始化一个名为name的原始通知链
#define RAW_NOTIFIER_HEAD(name) \
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)
如果我们已经有一个通知链的对象(不通过上述的宏定义来声音通知链),Linux还提供了一组用于初始化一个通知链对象的API
// 分别用于初始化 对应类型的 通知链
ATOMIC_INIT_NOTIFIER_HEAD(name)
BLOCKING_INIT_NOTIFIER_HEAD(name)
RAW_INIT_NOTIFIER_HEAD(name)
/* srcu_notifier_heads must be initialized and cleaned up dynamically */
extern void srcu_init_notifier_head(struct srcu_notifier_head *nh);
下面的声明以及初始化更加常见(以原子通知链为例):
static struct atomic_notifier_head dock_notifier_list;
ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
或者
struct aw9523_kpad_platform_data {
// ...
#if defined(CONFIG_FB)
struct notifier_block fb_notif;
#endif
};
// ...
pdata->fb_notif.notifier_call = fb_notifier_callback;
操作
我们以最普通的通知链来介绍,其他都是类似的。
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n);
static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);
这最基本的三个接口分别实现了对通知链上通知块的注册、卸载和遍历操作。
注册
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n);
描述:被通知一方(other_subsys_x
)通过notifier_chain_register
向特定的chain注册回调函数
参数解析:
卸载
static int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);
描述:被通知一方(other_subsys_x
)通过notifier_chain_unregister
取消chain通知
通知事件发生
static int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int *nr_calls);
描述: 通知者通过notifier_call_chain
来通知其他的子系统(other_subsys_x
)。
notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间。
参数解析:
返回值:
把最后一个被调用的回调函数的返回值作为它的返回值。
NOTIFY_XXX的形式:
// include/linux/notifier.h
/* 对事件视而不见 */
#define NOTIFY_DONE 0x0000
/* 事件正确处理 */
#define NOTIFY_OK 0x0001
/*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */
#define NOTIFY_STOP_MASK 0x8000
/*事件处理出错,不再继续调用回调函数 */
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002)
/* 回调出错,不再继续调用该事件回调函数 */
#define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
其他通知链对应的操作
其他类型的通知链的操作也是类似的:
// 原子通知链
int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb);
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);
// 可阻塞通知链、SRCU通知链
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *nb);
int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb);
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb);
//原始通知链
int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
上述这三类通知链的基本API又构成了内核中其他子系统定义、操作自己通知链的基础。
例如,Netlink定义了一个原子通知链,所以,它对原子通知链的基本API又封装了一层。
可阻塞通知链里的SRCU通知链,由于使用条件较苛刻,限制条件较多,所以使用的机会不是很多,在2.6.32的内核里只有cpufreq.c
在用这种类型的通知链。
使用步骤
步骤很简单:
1、申明struct notifier_block
结构
2、编写notifier_call
函数
3、调用特定的事件通知链的注册函数,将notifier_block
注册到通知链中
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
例子
notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2;
当 test_notifier_chain_2
通过module_init初始化模块时发出事件TESTCHAIN_2_INIT
;
然后 test_notifier_chain_1
作出相应的处理:打印 test_notifier_chain_2
正在初始化。
test_chain_0.c
通知、被通知者的功能实现。
申明一个通知链;
1、 向内核注册通知链;
2、 定义事件;
3、 导出符号,(因而必须最先安装、最后退出)
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
#define TESTCHAIN_INIT 0x52U
static RAW_NOTIFIER_HEAD(test_chain);
/* define our own notifier_call_chain */
static int call_test_notifiers(unsigned long val, void *v)
{
return raw_notifier_call_chain(&test_chain, val, v);
}
EXPORT_SYMBOL(call_test_notifiers);
/* define our own notifier_chain_register func */
static int register_test_notifier(struct notifier_block *nb)
{
int err;
err = raw_notifier_chain_register(&test_chain, nb);
if(err)
goto out;
out:
return err;
}
EXPORT_SYMBOL(register_test_notifier);
static int __init test_chain_0_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_0\n");
return 0;
}
static void __exit test_chain_0_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_0\n");
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_0_init);
module_exit(test_chain_0_exit);
test_chain_1.c
被通知者。
1、定义回调函数;
2、 定义notifier_block;
3、 向chain_0注册notifier_block;
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
extern int register_test_notifier(struct notifier_block *nb);
#define TESTCHAIN_INIT 0x52U
/* realize the notifier_call func */
int test_init_event(struct notifier_block *nb, unsigned long event,
void *v)
{
switch(event){
case TESTCHAIN_INIT:
printk(KERN_DEBUG "I got the chain event: test_chain_2 is on the way of init\n");
break;
default:
break;
}
return NOTIFY_DONE;
}
/* define a notifier_block */
static struct notifier_block test_init_notifier = {
.notifier_call = test_init_event,
};
static int __init test_chain_1_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_1\n");
register_test_notifier(&test_init_notifier); // 由chain_0提供的设施
return 0;
}
static void __exit test_chain_1_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_clain_l\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_1_init);
module_exit(test_chain_1_exit);
test_chain_2.c
通知者。
发出通知链事件
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything() */
extern int call_test_notifiers(unsigned long val, void *v);
#define TESTCHAIN_INIT 0x52U
static int __init test_chain_2_init(void)
{
printk(KERN_DEBUG "I'm in test_chain_2\n");
call_test_notifiers(TESTCHAIN_INIT, "no_use");
return 0;
}
static void __exit test_chain_2_exit(void)
{
printk(KERN_DEBUG "Goodbye to test_chain_2\n");
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");
module_init(test_chain_2_init);
module_exit(test_chain_2_exit);
Makefile
# Comment/uncomment the following line to disable/enable debugging
# DEBUG = y
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif
ifneq ($(KERNELRELEASE),)
# call from kernel build system
obj-m := test_chain_0.o test_chain_1.o test_chain_2.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
depend .depend dep:
$(CC) $(CFLAGS) -M *.c > .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif
测试
$ sudo dmesg -c > /dev/null
$ sudo insmod. /test_chain_0.ko
$ sudo insmod. /test_chain_1.ko
$ sudo insmod. /test_chain_2.ko
$ dmesg
[3215807.747547] I'm in test_chain_0
[3215809.759092] I'm in test_chain_1
[3215811.225148] I'm in test_chain_2
[3215811.225150] I got the chain event: test_chain_2 is on the way of init
$ sudo rmmod. /test_chain_2.ko
$ sudo rmmod. /test_chain_1.ko
$ sudo rmmod. /test_chain_0.ko
$ dmesg
[3215850.606570] Goodbye to test_chain_2
[3215853.346589] Goodbye to test_clain_l
[3215855.510567] Goodbye to test_chain_0
在Linux驱动中使用notifier通知链的更多相关文章
- Linux设备驱动中的异步通知与异步I/O
异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...
- Linux驱动中的EPROBE_DEFER是个啥
Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...
- linux驱动中printk的使用注意事项
今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】
转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)
completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion: DECLARE_CO ...
- 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)
static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .
- Linux驱动中常用的宏
.module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...
- Linux驱动中的platform总线分析
copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...
- Linux驱动中获取系统时间
最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...
- Linux内核基础--事件通知链(notifier chain)
转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-277176 ...
随机推荐
- GitLab 升级迁移待办清单
GitLab 大版本升级测试用例 项目 从模板项目 URL 导入,来创建新的项目 议题 通过 Quick Actions.关联新建.直接新建 模板 关联项 标签 工时 评论 看板 里程碑 分支 通过 ...
- HttpClient配置SSL绕过https证书以及双向认证
HttpClient简介 1.HTTP 协议是 Internet 上使用得最多.最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源.虽然在 JDK 的 java ...
- LabView之MQTT协议使用
一.MQTT概述 MQTT协议是一种消息列队传输协议,采用订阅.发布机制,订阅者只接收自己已经订阅的数据,非订阅数据则不接收,既保证了必要的数据的交换,又避免了无效数据造成的储存与处理.因此在工业物联 ...
- Go-Zero从0到1实现微服务项目开发(二)
前言 书接上回,继续更新GoZero微服务实战系列文章. 上一篇被GoZero作者万总点赞了,更文动力倍增,也建议大家先看巧一篇,欢迎粉丝股东们三连支持一波:Go-zero微服务快速入门和最佳实践(一 ...
- 传入一个List集合,返回分页好的数据
传入一个List集合,返回分页好的数据. 定义分页信息类: package com.cn.common; import java.util.List; public class CommonPage& ...
- 基于 OAI 部署私有的 4G EPS
目录 文章目录 目录 前言 硬件设备要求 运行平台 RF 外设 可编程 SIM 卡 UE 终端 高精度参考时钟 操作系统要求 内核要求 CPU Frequency scaling All-In-One ...
- 因为我的一次疏忽而带来的golang1.23新特性
距离golang 1.23发布还有两个月不到,按照惯例很快要进入1.23的功能冻结期了.在冻结期间不会再添加新功能,已经添加的功能不出大的意外一般也不会被移除.这正好可以让我们提前尝鲜这些即将到来的新 ...
- 怎么在线给pdf加盖电子公章
1前言:由于电子印章在工作中的普及,其方便易用性,也得到大家的认可. 目前我们在公文流转过程中,到最后常常需要在pdf文档进行电子盖章. 2方法:此文,主要是使用一个方便易用的在线pdf印章工具,pa ...
- uniapp 图片懒加载的一种方式
如果是列表页,可以采用前端分页,通过scroll-view 下拉,在绑定图片地址信息,这样就能下拉部分,加载部分图片了. pageQuery() { let currentPage = this.pQ ...
- 使用爬虫利器 Playwright,轻松爬取抖查查数据
使用爬虫利器 Playwright,轻松爬取抖查查数据 我们先分析登录的接口,其中 url 有一些非业务参数:ts.he.sign.secret. 然后根据这些参数作为关键词,定位到相关的 js 代码 ...