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查看内核打印




 




0 0
原创粉丝点击