Linux notifier chain 内核通知链

2018/11/15 linux Driver

Linux notifier chain 内核通知链

notifier chain 出现原因

大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。

notifier chain 基本原理

看起来 notifier 机制那么的高大上,其实别被名字所吓到,notifier 机制其实是非常简单的,本质上只是单向链表的扩充,下面一起来看下 notifier 机制是如何实现相应功能的

  • notifier 实现的源码位置 kernel/notifier.c
  • 重要数据结构 struct notifier_block,内核通知链本质还是链表,该结构体的函数指针 notifier_call 需要我们去填充,priority 是优先级,在加入通知链表时候,会根据链表的优先级进行插入,这样子在遍历链表的时候就可以按照优先级进行遍历(专业说,在通知发生时,就可以进行按照优先级进行函数指针的回调操作)
      typedef	int (*notifier_fn_t)(struct notifier_block *nb,
                  unsigned long action, void *data);
      struct notifier_block {
          notifier_fn_t notifier_call;
          struct notifier_block __rcu *next;
          int priority;
      };
    

notifier chain 使用

通知链包括了:

  • atomic_notifier_head(通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞)
  • blocking_notifier_head (通知链元素的回调函数在进程上下文中运行,允许阻塞)
  • raw_notifier_head (对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护)
  • srcu_notifier_head (可阻塞通知链的一种变体)

说白了都只是基于 notifier_block 进行再一次的封装,下面以 raw_notifier_head 为例,来演示通知链是如何使用的:

  • chain.c,对 kernel/notifier.c 中提供的函数进行初步的封装
      //定义头头结点
      static RAW_NOTIFIER_HEAD(test_chain);
    
      //注册函数,即将节点添加进链表
      int register_test_notifier(struct notifier_block *nb)
      {
        return raw_notifier_chain_register(&test_chain, nb);
      }
      EXPORT_SYMBOL(register_test_notifier);
      //删除链表
      int unregister_test_notifier(struct notifier_block *nb)
      {
        return raw_notifier_chain_unregister(&test_chain,nb);
      }
      EXPORT_SYMBOL(unregister_test_notifier);
    
      //遍历链表,即一次调用链表中的指针
      int test_notifier_call_chain(unsigned long val, void *v)
      {
        return raw_notifier_call_chain(&test_chain, val, v);
      }
      EXPORT_SYMBOL(test_notifier_call_chain);
    
      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);
    
  • regchain.c 代码,用于注册 notifer 事件,其实就是插入链表,test_event* 初始化 notifier_block,之后插入链表
      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\n",event);
        return 0;
      }
    
      static int test_event2(struct notifier_block *this, unsigned longevent, void *ptr)
      {
        printk("In Event 2: Event Number is %d\n",event);
        return 0;
      }
    
      static int test_event3(struct notifier_block *this, unsigned long event, void *ptr)
      {
        printk("In Event 3: Event Number is %d\n",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:\n");
    
        err =register_test_notifier(&test_notifier1);
        if (err)
        {
         printk("register test_notifier1 error\n");
          return-1;
        }
        printk("register test_notifier1completed\n");
    
        err =register_test_notifier(&test_notifier2);
        if (err)
        {
         printk("register test_notifier2 error\n");
          return-1;
        }
        printk("register test_notifier2completed\n");
    
        err =register_test_notifier(&test_notifier3);
        if (err)
        {
         printk("register test_notifier3 error\n");
          return-1;
        }
        printk("register test_notifier3completed\n");
    
        return err;
      }
    
    
      static void __exit unreg_notifier(void)
      {
        printk("Begin to unregister\n");
       unregister_test_notifier(&test_notifier1);
       unregister_test_notifier(&test_notifier2);
       unregister_test_notifier(&test_notifier3);
        printk("Unregister finished\n");
      }
    
      module_init(reg_notifier);
      module_exit(unreg_notifier);
    
  • notify.c 代码,主要调用用 test_notifier_call_chain 就可以通知链表执行回调函数
      extern int test_notifier_call_chain(unsigned long val, void*v);
      static int __init call_notifier(void)
      {
        int err;
        printk("Begin to notify:\n");
    
    
       printk("==============================\n");
        err = test_notifier_call_chain(1, NULL);
       printk("==============================\n");
        if (err)
               printk("notifier_call_chain error\n");
        return err;
      }
    
      static void __exit uncall_notifier(void)
      {
          printk("Endnotify\n");
      }
    
      module_init(call_notifier);
      module_exit(uncall_notifier);
    
  • 打印结果:
      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
      ==============================
    

总结

notifer 机制使用其实很简单,RAW_NOTIFIER_HEAD 初始化链表头,raw_notifier_chain_register 注册进链表中(插入),raw_notifier_call_chain 按照 priority 进行函数执行(遍历)。当然了,如果我们需求实现的时候,要求通过 raw_notifier_call_chain 遍历时候,要通过不同参数执行不同的操作时,我们可以在链表中的回调函数中使用 switch 语句进行区分,这样子就可以进行区分。最后,一个问题,既然 notifer 机制是为了可以在不同的子系统中进行通知调用,那我每次进行通知,又不想每个函数被调用到时,该机制真的友好吗?试想,在每次遍历的耗时,我们又不想调用链表的每个回调函数,这里面带来的耗时操作对系统可不是很友好。所以 Linux 机制是否有其他可以替代?

Search

    Table of Contents