深入理解linux网络技术内幕读书笔记(四)通知链

概述

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


定义链

通知链表的节点类型为notifier_block,其定义如下:


1:  struct notifier_block {
2:     int (*notifier_call)(struct notifier_block *, unsigned long, void *);
3:     struct notifier_block *next;
4:     int priority;
5:  };
其中最重要的就是notifier_call这个函数指针,表示了这个节点所对应的要运行的那个函数。next指向下一个节点,即当前事件发生时还要继续执行的那些节点。
[注] include/linux/notifier.h


链注册

在通知链注册时,需要有一个链表头,它指向这个通知链表的第一个元素。这样,之后的事件对该链表通知时就会根据这个链表头而找到这个链表中所有的元素。
注册的函数是:


1:  int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n)
也即是将新的节点n加入到nl所指向的链表中去。
卸载的函数是:


1:  int notifier_chain_unregister(strut notifier_block **nl, struct notifier_block *n)
也即是将节点n从nl所指向的链表中删除。


链上的通知事件

当有事件发生时,就使用notifier_call_chain向某个通知链表发送消息。


1:  int notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v)
这个函数是按顺序运行nl指向的链表上的所有节点上注册的函数。简单地说,如下所示:


 1:  struct notifier_block *nb = *n;
 2:  
 3:  while (nb)
 4:  {
 5:      ret = nb->notifier_call(nb, val, v);
 6:      if (ret & NOTIFY_STOP_MASK)
 7:      {
 8:          return ret;
 9:      }
10:      nb = nb->next;
11:  }

网络子系统的通知链

  • inetaddr_chain
    发送有关本地接口上的ipv4地址的插入,删除以及变更的通知信息。
  • netdev_chain
    发送有关网络设备注册状态的通知信息。

包裹函数

大多数通知链都有一组包裹函数,可以用于注册与删除。例如,下列包裹函数可用于向netdev_chain注册:

 1:  /*
 2:   *   Device change register/unregister. These are not inline or static
 3:   *   as we export them to the world.
 4:   */
 5:  
 6:  /**
 7:   *   register_netdevice_notifier - register a network notifier block
 8:   *   @nb: notifier
 9:   *
10:   *   Register a notifier to be called when network device events occur.
11:   *   The notifier passed is linked into the kernel structures and must
12:   *   not be reused until it has been unregistered. A negative errno code
13:   *   is returned on a failure.
14:   *
15:   *   When registered all registration and up events are replayed
16:   *   to the new notifier to allow device to have a race free
17:   *   view of the network device list.
18:   */
19:  
20:  int register_netdevice_notifier(struct notifier_block *nb)
21:  {
22:       struct net_device *dev;
23:       struct net_device *last;
24:       struct net *net;
25:       int err;
26:  
27:       rtnl_lock();
28:       err = raw_notifier_chain_register(&netdev_chain, nb);
29:       if (err)
30:           goto unlock;
31:       if (dev_boot_phase)
32:           goto unlock;
33:       for_each_net(net) {
34:           for_each_netdev(net, dev) {
35:               err = nb->notifier_call(nb, NETDEV_REGISTER, dev);
36:               err = notifier_to_errno(err);
37:               if (err)
38:                   goto rollback;
39:  
40:               if (!(dev->flags & IFF_UP))
41:                   continue;
42:  
43:               nb->notifier_call(nb, NETDEV_UP, dev);
44:           }
45:       }
46:  
47:  unlock:
48:       rtnl_unlock();
49:       return err;
50:  
51:  rollback:
52:       last = dev;
53:       for_each_net(net) {
54:           for_each_netdev(net, dev) {
55:               if (dev == last)
56:                   break;
57:  
58:               if (dev->flags & IFF_UP) {
59:                   nb->notifier_call(nb, NETDEV_GOING_DOWN, dev);
60:                   nb->notifier_call(nb, NETDEV_DOWN, dev);
61:               }
62:               nb->notifier_call(nb, NETDEV_UNREGISTER, dev);
63:               nb->notifier_call(nb, NETDEV_UNREGISTER_BATCH, dev);
64:           }
65:       }
66:  
67:       raw_notifier_chain_unregister(&netdev_chain, nb);
68:       goto unlock;
69:  }
70:  EXPORT_SYMBOL(register_netdevice_notifier);
[注] net/core/dev.c


范例

通知链注册通常发送在感兴趣的内核初始化时。


 1:  static struct notifier_block fib_inetaddr_notifier = {
 2:       .notifier_call = fib_inetaddr_event,
 3:  };
 4:  
 5:  static struct notifier_block fib_netdev_notifier = {
 6:       .notifier_call = fib_netdev_event,
 7:  };
 8:  
 9:  void __init ip_fib_init(void)
10:  {
11:       rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL);
12:       rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL);
13:       rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib);
14:  
15:       register_pernet_subsys(&fib_net_ops);
16:       register_netdevice_notifier(&fib_netdev_notifier);
17:       register_inetaddr_notifier(&fib_inetaddr_notifier);
18:  
19:       fib_hash_init();
20:  }
[注] net/ipv4/fib_frontend.c


测试实例

在这里,写了一个简单的通知链表的代码。


实际上,整个通知链的编写也就两个过程:
首先是定义自己的通知链的头节点,并将要执行的函数注册到自己的通知链中。
其次则是由另外的子系统来通知这个链,让其上面注册的函数运行。


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


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

 1:  #include <asm/uaccess.h>
 2:  #include <linux/types.h>
 3:  #include <linux/kernel.h>
 4:  #include <linux/sched.h>
 5:  #include <linux/notifier.h>
 6:  #include <linux/init.h>
 7:  #include <linux/types.h>
 8:  #include <linux/module.h>
 9:  MODULE_LICENSE("GPL");
10:  
11:  /*
12:  * 定义自己的通知链头结点以及注册和卸载通知链的外包函数
13:  */
14:  
15:  /*
16:  * RAW_NOTIFIER_HEAD是定义一个通知链的头部结点,
17:  * 通过这个头部结点可以找到这个链中的其它所有的notifier_block
18:  */
19:  
20:  static RAW_NOTIFIER_HEAD(test_chain);
21:  
22:  /*
23:  * 自定义的注册函数,将notifier_block节点加到刚刚定义的test_chain这个链表中来
24:  * raw_notifier_chain_register会调用notifier_chain_register
25:  */
26:  
27:  int register_test_notifier(struct notifier_block *nb)
28:  {
29:          return raw_notifier_chain_register(&test_chain, nb);
30:  }
31:  EXPORT_SYMBOL(register_test_notifier);
32:  
33:  int unregister_test_notifier(struct notifier_block *nb)
34:  {
35:          return raw_notifier_chain_unregister(&test_chain, nb);
36:  }
37:  EXPORT_SYMBOL(unregister_test_notifier);
38:  
39:  /*
40:  * 自定义的通知链表的函数,即通知test_chain指向的链表中的所有节点执行相应的函数
41:  */
42:  
43:  int test_notifier_call_chain(unsigned long val, void *v)
44:  {
45:          return raw_notifier_call_chain(&test_chain, val, v);
46:  }
47:  EXPORT_SYMBOL(test_notifier_call_chain);
48:  
49:  /*
50:  * init and exit
51:  */
52:  
53:  static int __init init_notifier(void)
54:  {
55:          printk("init_notifier\n");
56:          return 0;
57:  }
58:  
59:  static void __exit exit_notifier(void)
60:  {
61:          printk("exit_notifier\n");
62:  }
63:  module_init(init_notifier);
64:  module_exit(exit_notifier);
代码2 regchain.c
该代码的作用是将test_notifier1 test_notifier2 test_notifier3这三个节点加到之前定义的test_chain这个通知链表上,同时每个节点都注册了一个函数。
  1:  #include <asm/uaccess.h>
  2:  #include <linux/types.h>
  3:  #include <linux/kernel.h>
  4:  #include <linux/sched.h>
  5:  #include <linux/notifier.h>
  6:  #include <linux/init.h>
  7:  #include <linux/types.h>
  8:  #include <linux/module.h>
  9:  MODULE_LICENSE("GPL");
 10:  
 11:  /*
 12:  * 注册通知链
 13:  */
 14:  
 15:  extern int register_test_notifier(struct notifier_block*);
 16:  
 17:  extern int unregister_test_notifier(struct notifier_block*);
 18:  
 19:  static int test_event1(struct notifier_block *this, unsigned long event, void *ptr)
 20:  {
 21:          printk("In Event 1: Event Number is %d\n", event);
 22:          return 0;
 23:  }
 24:  
 25:  static int test_event2(struct notifier_block *this, unsigned long event, void *ptr)
 26:  {
 27:          printk("In Event 2: Event Number is %d\n", event);
 28:          return 0;
 29:  }
 30:  
 31:  static int test_event3(struct notifier_block *this, unsigned long event, void *ptr)
 32:  {
 33:          printk("In Event 3: Event Number is %d\n", event);
 34:          return 0;
 35:  }
 36:  
 37:  /*
 38:  * 事件1,该节点执行的函数为test_event1
 39:  */
 40:  
 41:  static struct notifier_block test_notifier1 =
 42:  {
 43:          .notifier_call = test_event1,
 44:  };
 45:  
 46:  /*
 47:  * 事件2,该节点执行的函数为test_event1
 48:  */
 49:  
 50:  static struct notifier_block test_notifier2 =
 51:  {
 52:          .notifier_call = test_event2,
 53:  };
 54:  
 55:  /*
 56:  * 事件3,该节点执行的函数为test_event1
 57:  */
 58:  
 59:  static struct notifier_block test_notifier3 =
 60:  {
 61:          .notifier_call = test_event3,
 62:  };
 63:  
 64:  /*
 65:  * 对这些事件进行注册
 66:  */
 67:  
 68:  static int __init reg_notifier(void)
 69:  {
 70:          int err;
 71:          printk("Begin to register:\n");
 72:  
 73:          err = register_test_notifier(&test_notifier1);
 74:          if (err)
 75:          {
 76:                  printk("register test_notifier1 error\n");
 77:                  return -1;
 78:          }
 79:          printk("register test_notifier1 completed\n");
 80:  
 81:          err = register_test_notifier(&test_notifier2);
 82:          if (err)
 83:          {
 84:                  printk("register test_notifier2 error\n");
 85:                  return -1;
 86:          }
 87:          printk("register test_notifier2 completed\n");
 88:  
 89:          err = register_test_notifier(&test_notifier3);
 90:          if (err)
 91:          {
 92:                  printk("register test_notifier3 error\n");
 93:                  return -1;
 94:          }
 95:          printk("register test_notifier3 completed\n");
 96:          return err;
 97:  }
 98:  
 99:  /*
100:  * 卸载刚刚注册了的通知链
101:  */
102:  
103:  static void __exit unreg_notifier(void)
104:  {
105:          printk("Begin to unregister\n");
106:          unregister_test_notifier(&test_notifier1);
107:          unregister_test_notifier(&test_notifier2);
108:          unregister_test_notifier(&test_notifier3);
109:          printk("Unregister finished\n");
110:  }
111:  module_init(reg_notifier);
112:  module_exit(unreg_notifier);
代码3 notify.c
该代码的作用就是向test_chain通知链中发送消息,让链中的函数运行。
 1:  #include <asm/uaccess.h>
 2:  #include <linux/types.h>
 3:  #include <linux/kernel.h>
 4:  #include <linux/sched.h>
 5:  #include <linux/notifier.h>
 6:  #include <linux/init.h>
 7:  #include <linux/types.h>
 8:  #include <linux/module.h>
 9:  MODULE_LICENSE("GPL");
10:  
11:  extern int test_notifier_call_chain(unsigned long val, void *v);
12:  
13:  /*
14:  * 向通知链发送消息以触发注册了的函数
15:  */
16:  
17:  static int __init call_notifier(void)
18:  {
19:          int err;
20:          printk("Begin to notify:\n");
21:  
22:  /*
23:  * 调用自定义的函数,向test_chain链发送消息
24:  */
25:  
26:          printk("==============================\n");
27:          err = test_notifier_call_chain(1, NULL);
28:          printk("==============================\n");
29:          if (err)
30:                  printk("notifier_call_chain error\n");
31:          return err;
32:  }
33:  
34:  
35:  static void __exit uncall_notifier(void)
36:  {
37:          printk("End notify\n");
38:  }
39:  module_init(call_notifier);
40:  module_exit(uncall_notifier);
Makefile文件


1:  obj-m:=buildchain.o regchain.o notify.o
2:  
3:  KERNELDIR:=/lib/modules/$(shell uname -r)/build
4:  
5:  default:
6:          make -C $(KERNELDIR) M=$(shell pwd) modules
运行:


1:  make
2:  
3:  insmod buildchain.ko
4:  insmod regchain.ko
5:  insmod notify.ko
这样就可以看到通知链运行的效果了


下面是我在自己的机器上面运行得到的结果:


 1:  init_notifier
 2:  Begin to register:
 3:  register test_notifier1 completed
 4:  register test_notifier2 completed
 5:  register test_notifier3 completed
 6:  Begin to notify:
 7:  ==============================
 8:  In Event 1: Event Number is 1
 9:  In Event 2: Event Number is 1
10:  In Event 3: Event Number is 1
11:  ==============================


原文地址:https://www.cnblogs.com/mosp/p/3551006.html