linux内核通知链原理及应用
来源:互联网 发布:js div鼠标点击事件 编辑:程序博客网 时间:2024/04/30 16:11
一、linux通知链介绍
内核的很多子系统(例如:进程调度、内存管理、虚拟文件系统、路由子系统等)之间具有很强的相互依赖性,因此,其中一个子系统侦测到的或者产生的事件,其他子系统可能都有兴趣,为了完成这种交互需求,linux使用了所谓的通知链(notification chain)
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
注意:通知链只在内核子系统之间使用,内核和用户空间之间的通知信息则是依赖其他机制,例如ioctl。
二、linux内核通知链的基本数据结构
通知链列表元素的类型是notifier_block,其定义如下:
struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block __rcu *next; int priority;};
Notifier_call是要执行的函数,next用于链接列表的元素,而priority代表的是该函数的优先级,较高优先级的函数会先被执行,但是在实际中,注册时几乎都不理会notifier_block定义中的priority,意味着获取其默认值0,因此,执行的次序仅依赖于注册次序。
通知链有四种类型:
1、 原子通知链( Atomicnotifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。对应的链表头结构:
structatomic_notifier_head{ spinlock_t lock; struct notifier_block *head;};
2、 可阻塞通知链(Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
structblocking_notifier_head{ struct rw_semaphore rwsem; struct notifier_block *head;};
3、 原始通知链( Rawnotifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
structraw_notifier_head{ struct notifier_block *head;};
4、 SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
structsrcu_notifier_head{ struct mutex mutex; struct srcu_struct srcu; struct notifier_block *head;};
三、linux内核通知链注册、执行相关的函数
通知链的运作机制包括两个角色:
被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。
包括以下过程:
通知者定义通知链。
被通知者向通知链中注册回调函数。
当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)。
1、通知链注册函数
被通知者调用 notifier_chain_register 函数注册回调函数,该函数按照优先级将回调函数加入到通知链中:
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;}
2、注销回调函数
注销回调函数则使用 notifier_chain_unregister 函数,即将回调函数从通知链中删除
staticint notifier_chain_unregister(struct notifier_block **nl, struct notifier_block*n){ while ((*nl) != NULL) { if ((*nl) == n) { rcu_assign_pointer(*nl,n->next); return 0; } nl = &((*nl)->next); } return -ENOENT;}
3、通用通知函数
通知者调用 notifier_call_chain 函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):static int __kprobes 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(*nl); while (nb && nr_to_call) { next_nb = rcu_dereference(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)++; if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK) break; nb = next_nb; nr_to_call--; } return ret;}
参数nl是通知链的头部,val表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样,例如当通知一个网卡被注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多少个。
每个被执行的notifier_block回调函数的返回值可能取值为以下几个:
NOTIFY_DONE:表示对相关的事件类型不关心。
NOTIFY_OK:顺利执行。
NOTIFY_BAD:执行有错。
NOTIFY_STOP:停止执行后面的回调函数。
NOTIFY_STOP_MASK:停止执行的掩码。
Notifier_call_chain()把最后一个被调用的回调函数的返回值作为它的返回值。
四、通知链的应用举例
在这里写一个简单的通知链的代码,通知链的编写分为两个过称:
1、 首先定义自己的通知链的头结点,并将执行的函数注册到自己的通知链中。
2、 其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。
代码chain_init.c,它的作用是自定义一个通知链表subsystem_chain,然后再自定义两个函数分别向这个通知链表加入或者删除节点,最后再定义函数通知这个subsystem_chain链。
#include <linux/kernel.h>#include <linux/sched.h>#include <linux/notifier.h>#include <linux/init.h>#include <linux/types.h>#include <linux/module.h>#include <asm/uaccess.h>#include <linux/types.h>/* * * 定义自己的通知链头结点以及注册和卸载通知链的外包函数 * *//* * * RAW_NOTIFIER_HEAD是定义一个通知链的头部结点, * * 通过这个头部结点可以找到这个链中的其它所有的notifier_block * */static RAW_NOTIFIER_HEAD(subsystem_chain);/* * * 自定义的注册函数,将notifier_block节点加到刚刚定义的subsystem_chain这个链表中来 * * raw_notifier_chain_register会调用notifier_chain_register * */int register_subsystem_notifier(struct notifier_block *nb){ return raw_notifier_chain_register(&subsystem_chain, nb);}EXPORT_SYMBOL(register_subsystem_notifier);int unregister_subsystem_notifier(struct notifier_block *nb){ return raw_notifier_chain_unregister(&subsystem_chain, nb);}EXPORT_SYMBOL(unregister_subsystem_notifier);/* * * 自定义的通知链表的函数,即通知subsystem_chain指向的链表中的所有节点执行相应的函数 * */int call_subsystem_notifiers(unsigned long val, void *v){ return raw_notifier_call_chain(&subsystem_chain, val, v);}EXPORT_SYMBOL(call_subsystem_notifiers);/* * * init and exit * */static int __init init_notifier(void){ printk("init_notifier\n"); return 0;}static void __exit exit_notifier(void){ printk("exit_notifier\n");}module_init(init_notifier);module_exit(exit_notifier);MODULE_LICENSE("GPL");
代码chain_server.c,该代码的作用是将subsystem_notifier节点加到之前定义的subsystem_chain链上。
#include <linux/kernel.h>#include <linux/sched.h>#include <linux/notifier.h>#include <linux/init.h>#include <linux/types.h>#include <linux/module.h>#include <asm/uaccess.h>#include <linux/types.h>#define SUBSYSTEM_CHAIN_INIT 0x90U /* * * 注册通知链 * */extern int register_subsystem_notifier(struct notifier_block*);extern int unregister_subsystem_notifier(struct notifier_block*);static int subsystem_event(struct notifier_block *nb, unsigned long event,void *v) { switch(event) { case SUBSYSTEM_CHAIN_INIT: printk(KERN_INFO"=====>Get subsystem chain event SUBSYSTEM_CHAIN_INIT\n"); break; default: printk(KERN_ERR"Unknown event num %ld\n", event); } return NOTIFY_DONE; } /* * * 事件,该节点执行的函数为subsystem_event * */static struct notifier_block subsystem_notifier ={ .notifier_call =subsystem_event,};/* * * 对这些事件进行注册 * */static int __init chain_server_register(void){ int err; printk("Begin to register:\n"); err = register_subsystem_notifier(&subsystem_notifier); if (err) { printk("register subsystem_notifier error\n"); return -1; } printk("register subsystem_notifier completed\n"); return err;}/* * * 卸载刚刚注册了的通知链 * */static void __exit chain_server_unregister(void){ printk("Begin to unregister\n"); unregister_subsystem_notifier(&subsystem_notifier); printk("Unregister finished\n");}module_init(chain_server_register);module_exit(chain_server_unregister);MODULE_LICENSE("GPL");
代码chain_client.c,该代码的作用就是向subsystem_chain通知链中发送消息,让链中的函数运行。
#include <linux/kernel.h>#include <linux/sched.h>#include <linux/notifier.h>#include <linux/init.h>#include <linux/types.h>#include <linux/module.h>#include <asm/uaccess.h>#include <linux/types.h>#define SUBSYSTEM_CHAIN_INIT 0x90U extern int call_subsystem_notifiers(unsigned long val, void *v);/* * * 向通知链发送消息以触发注册了的函数 * */static int __init chain_client_register(void){ int err; printk("Begin to notify:\n"); /* * * 调用自定义的函数,向subsystem_chain链发送消息 * */ // printk(" chain client init (subsystem client)\n"); err=call_subsystem_notifiers(SUBSYSTEM_CHAIN_INIT, NULL); if (err) printk("notifier_call_chain error\n"); return err;}static void __exit chain_client_unregister(void){ printk("End notify\n");}module_init(chain_client_register);module_exit(chain_client_unregister);MODULE_LICENSE("GPL");
Makefile文件
obj-m:=chain_init.o chain_client.o chain_server.oKERNEL:=/lib/modules/`uname -r`/buildall:make -C $(KERNEL) M=`pwd` modulesinstall:make -C $(KERNEL) M=`pwd` modules_installclean:make -C $(KERNEL) M=`pwd` cleanrm -rf Module.*
编译并加载模块
make
insmod chain_init.c
insmod chain_server.c
insmod chain_client.c
Dmesg查看内核打印
- linux内核通知链原理及应用
- Linux内核通知链原理及机制
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- Linux内核通知链机制的原理及实现
- 内核通知链原理及机制
- 内核通知链原理及机制
- 内核通知链原理及机制
- 内核通知链原理及机制
- 内核通知链原理及机制
- 嵌入式Linux之我行——内核通知链机制的原理及实现(转载)
- linux内核里的工作队列及内核通知链
- 内核通知链机制的原理及实现
- 内核通知链机制的原理及实现
- android 拍照并将图片剪裁、压缩并显示
- QQ游戏百万人同时在线服务器架构实现
- 服务器TIME_WAIT和CLOSE_WAIT详解和解决办法
- Accord.NET - Readme.md
- UIActionSheet常用属性(控件8.3之后失效)
- linux内核通知链原理及应用
- “DROP TABLE IF EXISTS” in Oracle and SQL Server
- 天杀 的pfx证书 提取公钥秘钥 加密签名
- ETL(数据仓库技术)
- Java点击下载按钮弹出下载文件对话框
- FileThreadPool
- org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.io.Serializable]:
- MFC中禁掉一个对话框中的所有控件
- 1EditText初始不弹出软键盘,只有光标显示,点击再弹出;2android:imeOptions