Linux通知链机制及实例

 

Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,要使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。内核实现了事件通知链机制(notification chain)。

 

通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。

 

通知链是一个函数列表,当给定事件发生的时候予以执行。每条通知链都有被通知者和拥有者。拥有者定义列表,被通知的子系统选择要执行的函数。网络子系统有3个通知链,如下图:

Linux通知链机制及实例

 

1.  数据结构   
Linux网络子系统中有3个通知链,表示ipv4地址发送变化时的inetaddr_chain,表示ipv6地址发生变化的inet6addr_chain,表示设备注册、状态变化的netdev_chain。

 

      链中都是一个个notifier_block结构。

 

任何内核子系统都可以对该链条注册的一个回调函数以接收通知信息。

 

      通知链列表元素的类型是notifier_block

 

      定义在include/linux/notifier.h文件中。

 

struct notifier_block {

 

        notifier_fn_t notifier_call;

 

        struct notifier_block __rcu *next;

 

        int priority;

 

};

 

      notifier_call是要执行的函数,由被通知方提供,next用于链接列表的元素,而priority代表的是该函数的优先级。

 

      通用函数notifier_chain_register予以注册,定义在kernel/notifier.c。

 

Linux内核中通知链,一般命名为xxx_chain或者,xxx_notifier_chian。内核有四种类型的通知链链表表头。

 

  • 原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:atomic_notifier_head
  • 可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:blocking_notifier_head
  • 原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:raw_notifier_head,网络子系统就是该类型
  • SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:srcu_notifier_head.

 

struct atomic_notifier_head {

 

        spinlock_t lock;

 

        struct notifier_block __rcu *head;

 

};

 

 

 

struct blocking_notifier_head {

 

        struct rw_semaphore rwsem;

 

        struct notifier_block __rcu *head;

 

};

 

 

 

struct raw_notifier_head {

 

        struct notifier_block __rcu *head;

 

};

 

 

 

struct srcu_notifier_head {

 

        struct mutex mutex;

 

        struct srcu_struct srcu;

 

        struct notifier_block __rcu *head;

 

};

 

2.    注册回调函数
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,一般子系统会用特定的notifier_chain_register包装函数来注册,如网络子系统是使用register_netdevice_notifier来注册他的notifier_block。

 

3.    使用示例
向事件通知链注册步骤如下:

 

1. 申明struct notifier_block结构

 

2. 编写notifier_call函数

 

3. 调用事件通知链的注册函数,将notifier_block注册到通知链中

 

如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。

 

3.1    通知子系统
inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。

 

notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间;其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:

 

#define NOTIFY_DONE            0x0000          /* Don't care */

 

#define NOTIFY_OK              0x0001          /* Suits me */

 

#define NOTIFY_STOP_MASK        0x8000          /* Don't call further */

 

#define NOTIFY_BAD              (NOTIFY_STOP_MASK|0x0002)

 

                                                /* Bad/Veto action */

 

notifier_call_chain捕获并返回最后一个事件处理函数的返回值, 并可能同时被不同的cpu调用,调用者须保证互斥。

 

3.2    事件链表
对于网络子系统而言,事件常以NETDEV_XXX命名,用于描述网络设备状态(dev->flags)、传送队列状态(dev->state)、设备注册状态(dev->reg_state),以及设备的硬件功能特性(dev->features),位于文件include/linux/notifier.h中:

 

#define NETDEV_UP      0x0001  /* For now you can't veto a device up/down */

 

#define NETDEV_DOWN    0x0002

 

#define NETDEV_REBOOT  0x0003  /* Tell a protocol stack a network interface

 

                                  detected a hardware crash and restarted

 

                                  - we can use this eg to kick tcp sessions

 

                                  once done */

 

#define NETDEV_CHANGE  0x0004  /* Notify device state change */

 

#define NETDEV_REGISTER 0x0005

 

#define NETDEV_UNREGISTER      0x0006

 

#define NETDEV_CHANGEMTU        0x0007 /* notify after mtu change happened */

 

#define NETDEV_CHANGEADDR      0x0008

 

#define NETDEV_GOING_DOWN      0x0009

 

#define NETDEV_CHANGENAME      0x000A

 

#define NETDEV_FEAT_CHANGE      0x000B

 

#define NETDEV_BONDING_FAILOVER 0x000C

 

#define NETDEV_PRE_UP          0x000D

 

#define NETDEV_PRE_TYPE_CHANGE  0x000E

 

#define NETDEV_POST_TYPE_CHANGE 0x000F

 

#define NETDEV_POST_INIT        0x0010

 

#define NETDEV_UNREGISTER_FINAL 0x0011

 

#define NETDEV_RELEASE          0x0012

 

#define NETDEV_NOTIFY_PEERS    0x0013

 

#define NETDEV_JOIN            0x0014

 

#define NETDEV_CHANGEUPPER      0x0015

 

#define NETDEV_RESEND_IGMP      0x0016

 

#define NETDEV_PRECHANGEMTU    0x0017 /* notify before mtu change happened */

 

#define NETDEV_CHANGEINFODATA  0x0018

 

#define NETDEV_BONDING_INFO    0x0019

 

#define NETDEV_PRECHANGEUPPER  0x001A

 

#define NETDEV_CHANGELOWERSTATE 0x001B

 

#define NETDEV_UDP_TUNNEL_PUSH_INFO    0x001C

 

#define NETDEV_UDP_TUNNEL_DROP_INFO    0x001D

 

#define NETDEV_CHANGE_TX_QUEUE_LEN      0x001E

 

    实例代码如下,来自网络,并整理。

 

3.3    模块0-chain0.c
定义两个函数,一个是注册函数register_test_notifier,一个发送事件函数call_test_notifiers

 

#include <linux/notifier.h>

 

#include <linux/module.h>

 

#include <linux/init.h>

 

#include <linux/kernel.h> /* printk() */

 

#include <linux/fs.h> /* everything() */

 

 

 

#define TESTCHAIN_INIT 0x52U

 

static RAW_NOTIFIER_HEAD(test_chain);

 

 

 

/* define our own notifier_call_chain */

 

static int call_test_notifiers(unsigned long val, void *v)

 

{

 

            return raw_notifier_call_chain(&test_chain, val, v);

 

}

 

EXPORT_SYMBOL(call_test_notifiers);

 

 

 

/* define our own notifier_chain_register func */

 

 static int register_test_notifier(struct notifier_block *nb)

 

{

 

            int err;

 

                err = raw_notifier_chain_register(&test_chain, nb);

 

 

 

                    if(err)

 

                                    goto out;

 

 

 

out:

 

                        return err;

 

}

 

 

 

EXPORT_SYMBOL(register_test_notifier);

 

 

 

static int __init test_chain_0_init(void)

 

{

 

            printk(KERN_DEBUG "I'm in test_chain_0\n");

 

 

 

                return 0;

 

}

 

static void __exit test_chain_0_exit(void)

 

{

 

            printk(KERN_DEBUG "Goodbye to test_chain_0\n");

 

}

 

 

 

MODULE_LICENSE("GPL");

 

module_init(test_chain_0_init);

 

module_exit(test_chain_0_exit);

 

 

 

3.4    模块1-chain1.c
定义notifier_block的test_init_notifier,其回调函数为test_init_event。

 

然后调用模块0中的事件注册函数register_test_notifier,向模块进行事件订阅。当事件发生时会后调用函数test_init_event.

 

#include <linux/notifier.h>

 

#include <linux/module.h>

 

#include <linux/init.h>

 

#include <linux/kernel.h>      /* printk() */

 

#include <linux/fs.h>          /* everything() */

 

extern int register_test_notifier (struct notifier_block *nb);

 

#define TESTCHAIN_INIT 0x52U

 

/* realize the notifier_call func */

 

int

 

test_init_event (struct notifier_block *nb, unsigned long event, void *v)

 

{

 

  switch (event)

 

    {

 

    case TESTCHAIN_INIT:

 

      printk (KERN_DEBUG

 

              "I got the chain event: test_chain_2 is on the way of init\n");

 

      break;

 

    default:

 

      break;

 

    }

 

  return NOTIFY_DONE;

 

}

 

 

 

/* define a notifier_block */

 

static struct notifier_block test_init_notifier = {

 

  .notifier_call = test_init_event,

 

};

 

 

 

static int __init

 

test_chain_1_init (void)

 

{

 

  printk (KERN_DEBUG "I'm in test_chain_1\n");

 

  register_test_notifier (&test_init_notifier);

 

  return 0;

 

}

 

static void __exit

 

test_chain_1_exit (void)

 

{

 

  printk (KERN_DEBUG "Goodbye to test_clain_l\n");

 

}

 

 

 

MODULE_LICENSE ("GPL");

 

 

 

module_init (test_chain_1_init);

 

module_exit (test_chain_1_exit);

 

 

 

3.5    模块2-chain2.c
调用模块0的事件发送函数call_test_notifiers,事件发送后,订阅时间的模块1会调用其自己的函数test_init_event,输出字符串。

 

#include <linux/notifier.h>

 

#include <linux/module.h>

 

#include <linux/init.h>

 

#include <linux/kernel.h> /* printk() */

 

#include <linux/fs.h> /* everything() */

 

 

 

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

 

#define TESTCHAIN_INIT 0x52U

 

 

 

static int __init test_chain_2_init(void)

 

{

 

            printk(KERN_DEBUG "I'm in test_chain_2\n");

 

                call_test_notifiers(TESTCHAIN_INIT, "no_use");

 

 

 

                    return 0;

 

}

 

 

 

static void __exit test_chain_2_exit(void)

 

{

 

            printk(KERN_DEBUG "Goodbye to test_chain_2\n");

 

}

 

 

 

MODULE_LICENSE("GPL");

 

module_init(test_chain_2_init);

 

module_exit(test_chain_2_exit);

 

      然后可以依次插入模块chain0.ko,chain1.ko,chain2.ko。

 

输出如下:

 

[38086.518853] I'm in test_chain_0

 

[40723.535358] I'm in test_chain_1

 

[40731.758722] I'm in test_chain_2

 

[40731.758724] I got the chain event: test_chain_2 is on the way of init