zoukankan      html  css  js  c++  java
  • 内核通知链原理及机制

    转载:http://blog.chinaunix.net/uid-25871104-id-3086446.html

     

    一、概念:

       大 多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系 统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。通知链表是一个函数链表, 链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这 个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不 多。

    二、数据结构:

    通知链有四种类型:

    1. 原子通知链( Atomic notifier chains):通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。对应的链表头结构:

    struct atomic_notifier_head
    {
        spinlock_tlock;
        structnotifier_block *head;
    };

    1.  
    2. 可阻塞通知链( Blocking notifier chains):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:

    struct blocking_notifier_head
    {
        structrw_semaphore rwsem;
        structnotifier_block *head;
    };

    1.  
    2. 原始通知链( Raw notifier chains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:

    struct raw_notifier_head
    {
        structnotifier_block *head;
    };

    1.  
    2. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:

    struct srcu_notifier_head
    {
        struct mutexmutex;
        structsrcu_struct srcu;
        structnotifier_block *head;
    };

    通知链的核心结构:

    struct notifier_block
    {
        int(*notifier_call)(struct notifier_block *, unsigned long, void*);
        structnotifier_block *next;
        intpriority;
    };

    其 中notifier_call是通知链要执行的函数指针,next用来连接其它的通知结构,priority是这个通知的优先级,同一条链上的 notifier_block{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain,xxx_nofitier_chain这种形式的 变量名。

    三、运作机制:

    通知链的运作机制包括两个角色:

    1. 被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
    2. 通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。

    包括以下过程:

    1. 通知者定义通知链。
    2. 被通知者向通知链中注册回调函数。
    3. 当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数)。

    被通知者调用 notifier_chain_register函数注册回调函数,该函数按照优先级将回调函数加入到通知链中:

    static int notifier_chain_register(structnotifier_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);
       
        return0;
    }

    注销回调函数则使用 notifier_chain_unregister函数,即将回调函数从通知链中删除:

    static int notifier_chain_unregister(structnotifier_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;
    }

    通知者调用 notifier_call_chain函数通知事件的到达,这个函数会遍历通知链中所有的元素,然后依次调用每一个的回调函数(即完成通知动作):

    static int __kprobes notifier_call_chain(structnotifier_block **nl, unsigned long val, void *v, int nr_to_call,int *nr_calls)
    {
        int ret =NOTIFY_DONE;
        structnotifier_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--;
        }
       
        returnret;
    }

       参 数nl是通知链的头部,val表示事件类型,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参数类型也不一样,例如当通知一个网卡被 注册时,v就指向net_device结构,nr_to_call表示准备最多通知几个,-1表示整条链都通知,nr_calls非空的话,返回通知了多 少个。

       每个被执行的notifier_block回调函数的返回值可能取值为以下几个:

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

       Notifier_call_chain()把最后一个被调用的回调函数的返回值作为它的返回值。

    四、举例应用:

    在这里,写了一个简单的通知链表的代码。实际上,整个通知链的编写也就两个过程:

    1. 首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。
    2. 其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。

         这里将第一个过程分成了两步来写,第一步是定义了头节点和一些自定义的注册函数(针对该头节点的),第二步则是使用自定义的注册函数注册了一些通知链节点。分别在代码buildchain.c与regchain.c中。发送通知信息的代码为notify.c。

    代码1buildchain.c。它的作用是自定义一个通知链表test_chain,然后再自定义两个函数分别向这个通知链中加入或删除节点,最后再定义一个函数通知这个test_chain链:

    #include<asm/uaccess.h>
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/notifier.h>
    #include <linux/init.h>
    #include <linux/types.h>
    #include <linux/module.h>
    MODULE_LICENSE("GPL");




    static RAW_NOTIFIER_HEAD(test_chain);


    int register_test_notifier(struct notifier_block *nb)
    {
      returnraw_notifier_chain_register(&test_chain, nb);
    }
    EXPORT_SYMBOL(register_test_notifier);

    int unregister_test_notifier(struct notifier_block *nb)
    {
      returnraw_notifier_chain_unregister(&test_chain,nb);
    }
    EXPORT_SYMBOL(unregister_test_notifier);


    int test_notifier_call_chain(unsigned long val, void *v)
    {
      returnraw_notifier_call_chain(&test_chain, val, v);
    }
    EXPORT_SYMBOL(test_notifier_call_chain);


    static int __init init_notifier(void)
    {
      printk("init_notifier ");
      return 0;
    }

    static void __exit exit_notifier(void)
    {
       printk("exit_notifier ");
    }

    module_init(init_notifier);
    module_exit(exit_notifier);

    代码2 regchain.c。该代码的作用是将test_notifier1test_notifier2test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数:

    #include<asm/uaccess.h>
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/notifier.h>
    #include <linux/init.h>
    #include <linux/types.h>
    #include <linux/module.h>
    MODULE_LICENSE("GPL");


    extern int register_test_notifier(struct notifier_block*);
    extern int unregister_test_notifier(struct notifier_block*);

    static int test_event1(struct notifier_block *this, unsigned longevent, void *ptr)
    {
      printk("In Event 1: Event Number is %d ",event);
      return 0;
    }

    static int test_event2(struct notifier_block *this, unsigned longevent, void *ptr)
    {
      printk("In Event 2: Event Number is %d ",event);
      return 0;
    }

    static int test_event3(struct notifier_block *this, unsigned longevent, void *ptr)
    {
      printk("In Event 3: Event Number is %d ",event);
      return 0;
    }


    static struct notifier_block test_notifier1 =
    {
       .notifier_call = test_event1,
    };


    static struct notifier_block test_notifier2 =
    {
       .notifier_call = test_event2,
    };


    static struct notifier_block test_notifier3 =
    {
       .notifier_call = test_event3,
    };


    static int __init reg_notifier(void)
    {
      int err;
      printk("Begin to register: ");
     
      err =register_test_notifier(&test_notifier1);
      if (err)
      {
       printk("register test_notifier1 error ");
        return-1;
      }
      printk("register test_notifier1completed ");

      err =register_test_notifier(&test_notifier2);
      if (err)
      {
       printk("register test_notifier2 error ");
        return-1;
      }
      printk("register test_notifier2completed ");

      err =register_test_notifier(&test_notifier3);
      if (err)
      {
       printk("register test_notifier3 error ");
        return-1;
      }
      printk("register test_notifier3completed ");
     
      return err;
    }


    static void __exit unreg_notifier(void)
    {
      printk("Begin to unregister ");
     unregister_test_notifier(&test_notifier1);
     unregister_test_notifier(&test_notifier2);
     unregister_test_notifier(&test_notifier3);
      printk("Unregister finished ");
    }

    module_init(reg_notifier);
    module_exit(unreg_notifier);

    代码3notify.c。该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行:

    #include<asm/uaccess.h>
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/notifier.h>
    #include <linux/init.h>
    #include <linux/types.h>
    #include <linux/module.h>
    MODULE_LICENSE("GPL");

    extern int test_notifier_call_chain(unsigned long val, void*v);


    static int __init call_notifier(void)
    {
      int err;
      printk("Begin to notify: ");

     
     printk("============================== ");
      err = test_notifier_call_chain(1, NULL);
     printk("============================== ");
      if (err)
             printk("notifier_call_chain error ");
      return err;
    }

    static void __exit uncall_notifier(void)
    {
        printk("Endnotify ");
    }

    module_init(call_notifier);
    module_exit(uncall_notifier);

    Makefile文件:

    obj-m:=buildchain.o regchain.o notify.o
    CURRENT_PATH := $(shell pwd)
    LINUX_KERNEL := $(shell uname -r)
    KERNELDIR := /usr/src/linux-headers-$(LINUX_KERNEL)

    all:
    make -C $(KERNELDIR) M=$(CURRENT_PATH) modules

    clean:

    make -C $(KERNELDIR) M=$(CURRENT_PATH) clean

    运行(注意insmod要root权限):

    make

    insmod buildchain.ko
    insmod regchain.ko
    insmod notify.ko

    这样就可以看到通知链运行的效果了:

    init_notifier
    Begin to register:
    register test_notifier1 completed
    register test_notifier2 completed
    register test_notifier3 completed
    Begin to notify:
    ==============================
    In Event 1: Event Number is 1
    In Event 2: Event Number is 1
    In Event 3: Event Number is 1
    ==============================

     

     

    附:

    以下是一些宏来初始化各种类型的通知头结构, 一般在程序中使用:

    #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 }
    #defineRAW_NOTIFIER_INIT(name) {    
      .head = NULL }

    注意, 没有定义scru通知头结构的初始化, 因为scru是不能静态初始化的.

     

    以下这些宏用来直接定义通知头结构:

    #defineATOMIC_NOTIFIER_HEAD(name)    
     struct atomic_notifier_head name=   
      ATOMIC_NOTIFIER_INIT(name)
    #defineBLOCKING_NOTIFIER_HEAD(name)    
     struct blocking_notifier_head name=   
      BLOCKING_NOTIFIER_INIT(name)
    #defineRAW_NOTIFIER_HEAD(name)     
     struct raw_notifier_head name=    
      RAW_NOTIFIER_INIT(name)

     

    4. 扩展的通知块操作


    扩展的通知块操作功能和基本通知块类似, 但使用了扩展的结构中的参数保证操作的安全


    4.1 原子通知块


    4.1.1 登记

     

     

    // 只是在基本通知登记操作前后加锁解锁进行保护
    int atomic_notifier_chain_register(struct atomic_notifier_head*nh,
      struct notifier_block *n)
    {
     unsigned long flags;
     int ret;

    // 加锁
     spin_lock_irqsave(&nh->lock,flags);
     ret =notifier_chain_register(&nh->head,n);
    // 解锁
     spin_unlock_irqrestore(&nh->lock,flags);
     return ret;
    }

    EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);


    4.1.2 撤销


    // 只是在基本通知块撤销操作前后加锁解锁进行保护
    int atomic_notifier_chain_unregister(struct atomic_notifier_head*nh,
      struct notifier_block *n)
    {
     unsigned long flags;
     int ret;
    // 加锁
     spin_lock_irqsave(&nh->lock,flags);
     ret =notifier_chain_unregister(&nh->head,n);
    // 解锁
     spin_unlock_irqrestore(&nh->lock,flags);
    // 同步rcu, 等待一个grace period
     synchronize_rcu();
     return ret;
    }

    EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);


    4.1.3 原子回调


    这个函数是在原子操作上下文中调用, 是不能阻塞的
     
    int __kprobes atomic_notifier_call_chain(structatomic_notifier_head *nh,
      unsigned long val, void*v)
    {
     int ret;

    // 禁止了抢占
     rcu_read_lock();
    // 使用基本通知块回调
     ret =notifier_call_chain(&nh->head, val,v);
    // 允许抢占
     rcu_read_unlock();
     return ret;
    }

    EXPORT_SYMBOL_GPL(atomic_notifier_call_chain);


    4.2 可阻塞通知块


    4.2.1 登记

    int blocking_notifier_chain_register(structblocking_notifier_head *nh,
      struct notifier_block *n)
    {
     int ret;

     
    // 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本登记函数
    // 不用处理信号灯, 因为此时是不能阻塞
     if (unlikely(system_state ==SYSTEM_BOOTING))
      returnnotifier_chain_register(&nh->head,n);
    // 使用信号灯进行同步, 可能阻塞
     down_write(&nh->rwsem);
    // 基本登记函数
     ret =notifier_chain_register(&nh->head,n);
     up_write(&nh->rwsem);
     return ret;
    }

    EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);


    4.2.2 撤销

    该函数是在进程处理过程中调用,可阻塞:

    int blocking_notifier_chain_unregister(structblocking_notifier_head *nh,
      struct notifier_block *n)
    {
     int ret;

     
    // 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本撤销函数
    // 不用处理信号灯, 因为此时是不能阻塞
     if (unlikely(system_state ==SYSTEM_BOOTING))
      returnnotifier_chain_unregister(&nh->head,n);

    // 使用信号灯进行同步, 可能阻塞
     down_write(&nh->rwsem);
    // 基本撤销函数
     ret =notifier_chain_unregister(&nh->head,n);
     up_write(&nh->rwsem);
     return ret;
    }

    EXPORT_SYMBOL_GPL(blocking_notifier_chain_unregister);


    4.2.3 回调


    在进行上下文中调用, 可以阻塞:

     
    int blocking_notifier_call_chain(struct blocking_notifier_head*nh,
      unsigned long val, void*v)
    {
     int ret;
    // 信号灯同步
     down_read(&nh->rwsem);
    // 进行基本回调处理
     ret =notifier_call_chain(&nh->head, val,v);
     up_read(&nh->rwsem);
     return ret;
    }

    EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);


    4.3 原始通知块操作


    和基本原始块操作完全相同:


    int raw_notifier_chain_register(struct raw_notifier_head *nh,
      struct notifier_block *n)
    {
     returnnotifier_chain_register(&nh->head,n);
    }

    EXPORT_SYMBOL_GPL(raw_notifier_chain_register);

    int raw_notifier_chain_unregister(structraw_notifier_head *nh,
      struct notifier_block *n)
    {
     returnnotifier_chain_unregister(&nh->head,n);
    }

    EXPORT_SYMBOL_GPL(raw_notifier_chain_unregister);


    int raw_notifier_call_chain(struct raw_notifier_head *nh,
      unsigned long val, void*v)
    {
     returnnotifier_call_chain(&nh->head, val,v);
    }

    EXPORT_SYMBOL_GPL(raw_notifier_call_chain);


    4.4 SRCU通知块操作


    4.4.1 登记

    必须在进程的上下文中调用, 和blocking通知类似

    int srcu_notifier_chain_register(structsrcu_notifier_head *nh,
      struct notifier_block *n)
    {
     int ret;

     
     if (unlikely(system_state ==SYSTEM_BOOTING))
      returnnotifier_chain_register(&nh->head,n);

     mutex_lock(&nh->mutex);
     ret =notifier_chain_register(&nh->head,n);
     mutex_unlock(&nh->mutex);
     return ret;
    }

    EXPORT_SYMBOL_GPL(srcu_notifier_chain_register);


    4.4.2 撤销


    必须在进程的上下文中调用, 和blocking通知类似

    int srcu_notifier_chain_unregister(structsrcu_notifier_head *nh,
      struct notifier_block *n)
    {
     int ret;

     
     if (unlikely(system_state ==SYSTEM_BOOTING))
      returnnotifier_chain_unregister(&nh->head,n);

     mutex_lock(&nh->mutex);
     ret =notifier_chain_unregister(&nh->head,n);
     mutex_unlock(&nh->mutex);
     synchronize_srcu(&nh->srcu);
     return ret;
    }

    EXPORT_SYMBOL_GPL(srcu_notifier_chain_unregister);


    4.4.3 回调


    在进程的上下文中调用, 可以阻塞:

    int srcu_notifier_call_chain(structsrcu_notifier_head *nh,
      unsigned long val, void*v)
    {
     int ret;
     int idx;
    // 使用srcu读锁来加锁
     idx =srcu_read_lock(&nh->srcu);
     ret =notifier_call_chain(&nh->head, val,v);
     srcu_read_unlock(&nh->srcu,idx);
     return ret;
    }

    EXPORT_SYMBOL_GPL(srcu_notifier_call_chain);

    4.4.4 初始化

    因为SRCU通知不能通过宏来初始化,必须要专门定义一个初始化函数来初始化srcu的通知块参数:

    void srcu_init_notifier_head(structsrcu_notifier_head *nh)
    {
    // 初始化锁
     mutex_init(&nh->mutex);
    // 初始化scru结构
     if(init_srcu_struct(&nh->srcu)< 0)
      BUG();
     nh->head = NULL;
    }

    EXPORT_SYMBOL_GPL(srcu_init_notifier_head);

  • 相关阅读:
    selenium webdriver 执行Javascript
    selenium webdriver 定位元素 第一部分
    selenium webdriver 模拟鼠标悬浮
    JaveWeb 公司项目(3)----- 通过Thrift端口获取数据库数据
    JaveWeb 公司项目(2)----- 类模态窗口显示DIV并将DIV放置在屏幕正中间
    JaveWeb 公司项目(1)----- 使Div覆盖另一个Div完成切换效果
    Intellij Idea修改css文件即时更新生成效果
    Intellij idea 2017 图标含义
    下载安装tomcat和jdk,配置运行环境,与Intellij idea 2017关联
    IntelliJ IDEA Tomcat中端口被占用的问题
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/3797279.html
Copyright © 2011-2022 走看看