在Linux驱动中使用notifier通知链

背景

在驱动分析中经常看到fb_notifier_callback,现在趁有空学习一下。

介绍

linux中的观察者模式是最显然的就是“通知链”模型。

在linux中,如果你想让自己的行为被别人注意到,那么你就要申请一条通知链,然后让所有关注你自己的实体注册到这条通知链上。

最终的效果就是一旦发生一件值得关注的事情,所有的注册者都可以得到通知。

内核中通知链的基础文件就两个:

  • 头文件:include/linux/notifier.h
  • 源文件:kernel/notifier.c

头文件和源文件所有代码加起来不超过1000行,总体来说还是比较好懂。

参考:

我自己花了点时间写了一条类似的通知链,但是跨平台(应用程序、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通知链的更多相关文章

  1. Linux设备驱动中的异步通知与异步I/O

    异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...

  2. Linux驱动中的EPROBE_DEFER是个啥

    ​Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...

  3. linux驱动中printk的使用注意事项

    今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...

  4. Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】

    转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...

  5. Linux驱动中completion接口浅析(wait_for_complete例子,很好)

    completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion:                          DECLARE_CO ...

  6. 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)

    static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .

  7. Linux驱动中常用的宏

    .module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...

  8. Linux驱动中的platform总线分析

    copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...

  9. Linux驱动中获取系统时间

    最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...

  10. Linux内核基础--事件通知链(notifier chain)

    转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-277176 ...

随机推荐

  1. GitLab 升级迁移待办清单

    GitLab 大版本升级测试用例 项目 从模板项目 URL 导入,来创建新的项目 议题 通过 Quick Actions.关联新建.直接新建 模板 关联项 标签 工时 评论 看板 里程碑 分支 通过 ...

  2. HttpClient配置SSL绕过https证书以及双向认证

    HttpClient简介 1.HTTP 协议是 Internet 上使用得最多.最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源.虽然在 JDK 的 java ...

  3. LabView之MQTT协议使用

    一.MQTT概述 MQTT协议是一种消息列队传输协议,采用订阅.发布机制,订阅者只接收自己已经订阅的数据,非订阅数据则不接收,既保证了必要的数据的交换,又避免了无效数据造成的储存与处理.因此在工业物联 ...

  4. Go-Zero从0到1实现微服务项目开发(二)

    前言 书接上回,继续更新GoZero微服务实战系列文章. 上一篇被GoZero作者万总点赞了,更文动力倍增,也建议大家先看巧一篇,欢迎粉丝股东们三连支持一波:Go-zero微服务快速入门和最佳实践(一 ...

  5. 传入一个List集合,返回分页好的数据

    传入一个List集合,返回分页好的数据. 定义分页信息类: package com.cn.common; import java.util.List; public class CommonPage& ...

  6. 基于 OAI 部署私有的 4G EPS

    目录 文章目录 目录 前言 硬件设备要求 运行平台 RF 外设 可编程 SIM 卡 UE 终端 高精度参考时钟 操作系统要求 内核要求 CPU Frequency scaling All-In-One ...

  7. 因为我的一次疏忽而带来的golang1.23新特性

    距离golang 1.23发布还有两个月不到,按照惯例很快要进入1.23的功能冻结期了.在冻结期间不会再添加新功能,已经添加的功能不出大的意外一般也不会被移除.这正好可以让我们提前尝鲜这些即将到来的新 ...

  8. 怎么在线给pdf加盖电子公章

    1前言:由于电子印章在工作中的普及,其方便易用性,也得到大家的认可. 目前我们在公文流转过程中,到最后常常需要在pdf文档进行电子盖章. 2方法:此文,主要是使用一个方便易用的在线pdf印章工具,pa ...

  9. uniapp 图片懒加载的一种方式

    如果是列表页,可以采用前端分页,通过scroll-view 下拉,在绑定图片地址信息,这样就能下拉部分,加载部分图片了. pageQuery() { let currentPage = this.pQ ...

  10. 使用爬虫利器 Playwright,轻松爬取抖查查数据

    使用爬虫利器 Playwright,轻松爬取抖查查数据 我们先分析登录的接口,其中 url 有一些非业务参数:ts.he.sign.secret. 然后根据这些参数作为关键词,定位到相关的 js 代码 ...