Notifier 一文搞懂Linux内核通知链

引入在linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其他子系统产生的事件比较感兴趣 。因此内核引入了notifier机制,当然了notifier机制只能用在内核子系统之间,不能用在内核与应用层之间 。比如当系统suspend的时候,就会使用到notifier机制来通知系统的内核线程进行suspend 。
嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧 。

Notifier 一文搞懂Linux内核通知链

文章插图
 
需要的朋友私信【内核】即可领取 。
内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
内核实现的notifier机制代码位于kernel/kernel/notifier.c,同时此机制的代码量也不是很多只有600行左右 。
数据结构内核使用struct notifier_block结构代表一个notifier
typedef int (*notifier_fn_t)(struct notifier_block *nb,unsigned long action, void *data);struct notifier_block { notifier_fn_t notifier_call; struct notifier_block __rcu *next; int priority;};notifier_call: 代表当事件发生之后调用的回调函数 。
next: 用来链接同一个类型的notifier 。
priority: notifier chain的优先级 。对应的数字越大优先级越高,就优先执行 。
同时内核也提供了四种不同类型的notifier chain
  • 原子通知链(Atomic notifier chains)
struct atomic_notifier_head { spinlock_t lock; struct notifier_block __rcu *head;};可以看到原子notifier chain只是对notifier_block的一个封装 。同时atomic notifier chain的回调函数需要运行在中断上下文/原子上下文中,而且不能睡眠 。
很明显因为atomic_notifer_head其中的spin_lock的特点就是不能睡眠 。
  • 可阻塞通知链(Blocking notifier chains)
struct blocking_notifier_head { struct rw_semaphore rwsem; struct notifier_block __rcu *head;};blocking_notifier_head其中包含了读写信号量成员rwsem,而信号量的特定就是运行在进程上下文,而且还可以睡眠 。同理Blocking notifier chains的回调函数特征一样 。
  • 原始通知链(Raw notifier chains)
struct raw_notifier_head { struct notifier_block __rcu *head;};raw_notifier_head的特点是对回调函数,register, unregister都没有任何限制,所有的保护机制都需要调用者维护 。
  • SRCU通知链(SRCU notifier chains)
struct srcu_notifier_head { struct mutex mutex; struct srcu_struct srcu; struct notifier_block __rcu *head;};SRCU通知链是block notifier chain的一种变体,采用SRCU(Sleepable Read-Copy Update)代替rw-semphore来保护chains
notifier chain初始化内核提供了一套宏用来初始化各个类型的通知链
#define ATOMIC_INIT_NOTIFIER_HEAD(name) do {spin_lock_init(&(name)->lock);(name)->head = NULL;} while (0)#define BLOCKING_INIT_NOTIFIER_HEAD(name) do {init_rwsem(&(name)->rwsem);(name)->head = NULL;} while (0)#define RAW_INIT_NOTIFIER_HEAD(name) do {(name)->head = NULL;} while (0)以上是动态初始化各个类型的通知链,当然了有动态初始化,也就有静态初始化
#define ATOMIC_NOTIFIER_INIT(name) {.lock = __SPIN_LOCK_UNLOCKED(name.lock),.head = NULL }#define BLOCKING_NOTIFIER_INIT(name) {.rwsem = __RWSEM_INITIALIZER((name).rwsem),.head = NULL }#define RAW_NOTIFIER_INIT(name) {.head = NULL }/* srcu_notifier_heads cannot be initialized statically */#define ATOMIC_NOTIFIER_HEAD(name)struct atomic_notifier_head name =ATOMIC_NOTIFIER_INIT(name)#define BLOCKING_NOTIFIER_HEAD(name)struct blocking_notifier_head name =BLOCKING_NOTIFIER_INIT(name)#define RAW_NOTIFIER_HEAD(name)struct raw_notifier_head name =RAW_NOTIFIER_INIT(name)通过注释可以知道SRCU通知链不能使用静态的方法,因此内核提供了一个动态的初始化函数,
void srcu_init_notifier_head(struct srcu_notifier_head *nh){ mutex_init(&nh->mutex); if (init_srcu_struct(&nh->srcu) < 0)BUG(); nh->head = NULL;}注册/注销通知链内核提供的最基本的注册通知链的函数
/* * Notifier chain core routines.The exported routines below * are layered on top of these, with Appropriate locking added. */static int notifier_chain_register(struct notifier_block **nl,struct notifier_block *n){ while ((*nl) != NULL) {if (n->priority > (*nl)->priority)break;nl = &((*nl)->next); } n->next = *nl; rcu_assign_pointer(*nl, n); return 0;}上述的操作就是通过判断priority的大小,然后将大的插入带链表头,小的插入在链表末尾 。


推荐阅读