Linux内核基础 | 通知链机制

一、通知链简介举个形象的例子:将通知链比喻成”订阅者-发布者“,订阅者将感兴趣的公众号关注并设置提醒,发布者一旦发布某个文章,订阅者即可收到通知看到发布的内容 。
在linux内核中为了及时响应某些到来的事件,采取了通知链机制 。该机制的两个角色的任务:
1、通知者定义通知链
2、被通知者向通知链中注册回调函数
3、当事件发生时,通知者发送通知 (执行通知链上每个调用块上的回调函数)所以通知链是一个单链表,单链表上的节点是调用块,每个调用块上有事件相关的回调函数和调用块的优先级 。当事件触发时会按优先级顺序执行该链表上的回调函数 。通知链只用于各个子系统之间,不能用于内核和用户空间进行事件的通知 。
二、相关细节1、通知链的类型
原子通知链( Atomic notifier chains ):
通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞 。
可阻塞通知链( Blocking notifier chains ):
通知链元素的回调函数在进程上下文中运行,允许阻塞 。
原始通知链( Raw notifier chains ):
对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护 。
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体
本文将以原子通知链进行分析
2、原子通知链与通知块
struct raw_notifier_head { struct notifier_block __rcu *head;};初始化一个原子通知链使用以下宏定义
#define RAW_NOTIFIER_HEAD(name)struct raw_notifier_head name =RAW_NOTIFIER_INIT(name)#define RAW_NOTIFIER_INIT(name) {.head = NULL }例如创建一个设备通知链队列头:
RAW_NOTIFIER_HEAD.NETdev_chain)struct raw_notifier_head就相当于存放这条通知链单链表头,每一个通知链上的元素也就是通知块如下定义:
struct notifier_block { notifier_fn_t notifier_call; //通知调用的函数 struct notifier_block __rcu *next;//指向下一个通知节点,从而形成链队 int priority;//优先级,会根据优先级在单链表中排序};回调函数接口:
typedef int (*notifier_fn_t)(struct notifier_block *nb,unsigned long action, void *data);整个通知链的组织如下图所示:

Linux内核基础 | 通知链机制

文章插图
 
3、向通知链中插入通知块
int raw_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *n){ return notifier_chain_register(&nh->head, n);}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;}4、调用通知链
int raw_notifier_call_chain(struct raw_notifier_head *nh,unsigned long val, void *v){ return __raw_notifier_call_chain(nh, val, v, -1, NULL);}int __raw_notifier_call_chain(struct raw_notifier_head *nh,unsigned long val, void *v,int nr_to_call, int *nr_calls){ return notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);}static int notifier_call_chain(struct notifier_block **nl,unsigned long val, void *v,int nr_to_call, int *nr_calls){ int ret = NOTIFY_DONE; struct notifier_block *nb, *next_nb; nb = rcu_dereference_raw(*nl);//循环遍历调用链上的调用块 while (nb && nr_to_call) {next_nb = rcu_dereference_raw(nb->next);#ifdef CONFIG_DEBUG_NOTIFIERSif (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {WARN(1, "Invalid notifier called!");nb = next_nb;continue;}#endif//执行该调用块的回调函数ret = nb->notifier_call(nb, val, v);if (nr_calls)(*nr_calls)++;//如果该调用块的回调函数返回值为NOTIFY_STOP_MASK则跳出调用链的遍历,也就不执行后面的调用块的回调函数了if (ret & NOTIFY_STOP_MASK)break;nb = next_nb;nr_to_call--; } return ret;} 
三、编写内核模块进行实验1、案例1
编写内核模块作为被通知者,向内核netdev_chain通知链中插入自定义通知块(在通知块中自定义事件触发的回调函数),源码如下:
#include <linux/module.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/netdevice.h>#include <linux/inetdevice.h> //处理网络设备的启动与禁用等事件int test_netdev_event(struct notifier_block *this, unsigned long event, void *ptr){struct net_device *dev = (struct net_device *)ptr;switch(event){case NETDEV_UP:if(dev && dev->name)printk("dev[%s] is upn",dev->name);break;case NETDEV_DOWN:if(dev && dev->name)printk("dev[%s] is downn",dev->name);break;default:break;}return NOTIFY_DONE;}struct notifier_block devhandle={.notifier_call = test_netdev_event}; static int __inittest_init(void){/*在netdev_chain通知链上注册消息块netdev_chain通知链是内核中用于传递有关网络设备注册状态的通知信息 */register_netdevice_notifier(&devhandle);return 0; }static void __exit test_exit(void){unregister_netdevice_notifier(&devhandle);return;}module_init(test_init);module_exit(test_exit); MODULE_LICENSE("GPL");


推荐阅读