linux内核底层原理(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内核底层原理(Linux内核基础通知链机制)(1)

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_NOTIFIERS if (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; }

更多LINUX内核视频教程文档资料免费领取后台私信【内核】自行获取.

linux内核底层原理(Linux内核基础通知链机制)(2)

linux内核底层原理(Linux内核基础通知链机制)(3)

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂

三、编写内核模块进行实验

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 up\n",dev->name); break; case netDEV_DOWN: if(dev && dev->name) printk("dev[%s] is down\n",dev->name); break; default: break; } return NOTIFY_DONE; } struct notifier_block devhandle={ .notifier_call = test_netdev_event }; static int __init test_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");

Makefile:

obj-m:=Demo.o CURRENT_PATH:=$(shell pwd) LINUX_KERNEL:=$(shell uname -r) LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

将模块插入内核后,将网卡关闭再重启一次,查看日志信息:

dx@ubuntu:~/Linux_Sys_code/Notice/Module3$sudo insmod Demo.ko dx@ubuntu:~/Linux_Sys_code/Notice/Module3$ dmesg [24309.137937] inet[00000000baf272e6] is down [24313.046209] inet[00000000baf272e6] is up

2、案例2

通过写两个内核模块,其中一个作为通知者一个作为被通知者

module_1.c:

  • 初始化一个通知链
  • 定义事件的回调函数并向通知链中插入三个通知块(与之前定义的回调函数相对应)
  • 测试通知链:循环遍历通知链的通知块,并同时调用对应的回调函数

#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/notifier.h> /* 模块功能:1、初始化一个通知链 2、定义事件的回调函数并向通知链中插入三个通知块(与之前定义的回调函数相对应) 3、测试通知链:循环遍历通知链的通知块,并同时调用对应的回调函数 */ static RAW_NOTIFIER_HEAD(test_chain_head); EXPORT_SYMBOL_GPL(test_chain_head); //通知块1的执行函数 static int A_call(struct notifier_block *nb, unsigned long event, void *v) { printk("AAAAAAAAAA---------event_A occur!---------AAAAAAAAAA\n"); printk("my priority:%d\n",nb->priority); return NOTIFY_DONE; } //通知块1:testA static struct notifier_block testA = { .notifier_call = A_call, .priority = 7, }; //通知块2的执行函数 static int B_call(struct notifier_block *nb, unsigned long event, void *v) { printk("BBBBBBBBBB---------event_B occur!---------BBBBBBBBB\n"); printk("my priority:%d\n",nb->priority); return NOTIFY_STOP_MASK; } //通知块2:testB static struct notifier_block testB = { .notifier_call = B_call, .priority = 9, }; //通知块1的执行函数 static int C_call(struct notifier_block *nb, unsigned long event, void *v) { printk("CCCCCCCCCC---------event_c occur!---------CCCCCCCCCC\n"); printk("my priority:%d\n",nb->priority); return NOTIFY_DONE; } static struct notifier_block testC = { .notifier_call = C_call, .priority = 6, }; static int __init my_register(void) { printk("----------register notice chain---------\n"); raw_notifier_chain_register(&test_chain_head,&testA); raw_notifier_chain_register(&test_chain_head,&testB); raw_notifier_chain_register(&test_chain_head,&testC); printk("----------register notice chain done---------\n"); //遍历已经注册的调用链 struct notifier_block *nb, *next_nb; struct raw_notifier_head *tmp = &test_chain_head; struct notifier_block *head = tmp->head; nb = rcu_dereference_raw(head); printk("----Test registed notice call----\n"); //循环遍历调用链,测试一下所插入的通知块 while (nb) { int ret = NOTIFY_DONE; int index=0; next_nb = rcu_dereference_raw(nb->next); printk("notice%d fun:%p,priority:%d", index,nb->notifier_call,nb->priority); ret = nb->notifier_call(nb, 1, NULL); //调用注册的回调函数 nb = next_nb; } printk("--------------Module_1 test end-------------\n"); return 0; } static void __exit my_unregister(void) { raw_notifier_chain_unregister(&test_chain_head,&testA); raw_notifier_chain_unregister(&test_chain_head,&testB); raw_notifier_chain_unregister(&test_chain_head,&testC); } module_init(my_register); module_exit(my_unregister); MODULE_AUTHOR("Dong Xu"); MODULE_LICENSE("GPL");

module_2.c:模拟某事件发生,并调用通知链

#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/notifier.h> /* 模块功能:模拟某事件发生,并调用通知链. */ extern struct raw_notifier_head test_chain_head; //某事件 static int event(unsigned long val) { int ret = raw_notifier_call_chain(&test_chain_head,val,NULL); return notifier_to_errno(ret); } static int __init my_entry(void) { event(666);//模拟某事件发生 return 0; } static void __exit my_exit(void) { printk("test end\n"); } module_init(my_entry); module_exit(my_exit); MODULE_AUTHOR("Dong Xu"); MODULE_LICENSE("GPL");

(module_1与module_2的Makefile可参考上面的Demo1)

运行时先插入module_1再插入module_2结果如下,红框内是module_1中的测试输出日志,绿框内为世界调用通知链时的执行结果日志。

linux内核底层原理(Linux内核基础通知链机制)(4)

从上面可以看到通知链的执行顺序是按照优先级进行的,那么当调用通知链时是否每个通知块上的回调函数都会执行呢?

答案:不是,每个被执行的notifier_block回调函数的返回值可能取值以下几个:

  1. NOTIFY_DONE:表示对相关的事件类型不关心。
  2. NOTIFY_OK:顺利执行。
  3. NOTIFY_BAD:执行有错。
  4. NOTIFY_STOP:停止执行后面的回调函数。
  5. NOTIFY_STOP_MASK:停止执行的掩码

如当返回值NOTIFY_STOP_MASK会停止执行后面优先级低的调用块的函数。

例如把module_1中通知块的回调函数B_call的返回值修改为NOTIFY_STOP_MASK后,重新编译,运行结果如下,只执行了调用链中调用块2的回调函数。

linux内核底层原理(Linux内核基础通知链机制)(5)

转载地址:Linux内核基础 | 通知链机制 - Linux内核 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

linux内核底层原理(Linux内核基础通知链机制)(6)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页