Linux内核--通知链事件notifier chain

参考:https://blog.youkuaiyun.com/wuhzossibility/article/details/807902

1.1概述
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制,告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为了满足这样的需求,内核实现了事件通知链机制(notification chain)
通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel下。
通知链表位于kernel/notifier.c,对应的头文件为include/linux/notifier.h
事件通知链表是一个事件处理函数的列表,每个通知链都与某个或者某些事件相关,当特定的事件发生时候,就调用相应的事件通知链中的回调函数,进行相应的处理。
这里写图片描述

1.2 数据结构
如图1,Linux的网络子系统一共有3哥通知链:
表示ivp4地址发生变化时候,inetaddr_chain
表示ipv6地址发生变化时候,inet6addr_chain
表示设备注册、状态变化时候,netdev_chain

在这些链表中,都是一个个notifier_block结构:
struct notifier_block {
       int (*notifier_call)(struct notifier_block *, unsigned long, void *);
       struct notifier_block *next;
       int priority;
};
其中:
1.notifier_call:当相应事件发生时候应该调用的函数,由被通知方提供,如other_subsys_1
2.notifier_block *next:用于链接成链表的指针;
3.priority:回调函数的优先级,一般默认为0

内核代码中一般把通知链命名为xxx_chain,xxx_notifier_chain这种形式的变量名。围绕核心数据结构notifier_block,内核定义了四种通知链类型:
1.原子通知链(atomic notifier chains):通知链元素的回调函数(当事件发生时候要执行的函数),在中断或者原子操作上下文中运行,不允许阻塞。对应的链表表头结构:
struct atomic_notifier_head {
        spinlock_t  lock;
        struct  notifier_block *head;
};
2.可阻塞通知链(Blocking notifier chains):通知链元素的回调函数在进程上下文中运行,允许阻塞,对应的链表头:
struct  blocking_notifier_head {
        struct  rw_semaphore  rwsem;
        struct  notifier_block   *head;
};
3.原始通知链(Raw notifier chains):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
网络子系统就是该类型,通过以下宏实现head的:而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier
static RAW_NOTIFIER_HEAD(netdev_chain);
#define RAW_NOTIFIER_INIT(name)     {      \
              .head= NULL }
#define RAW_NOTIFIER_HEAD(name)         \      //调用他就好了
struct raw_notifier_head name =         \
                     RAW_NOTIFIER_INIT(name)   
即:
struct raw_notifier_head netdev_chain = {
          .head = NULL;
}
4.SRCU通知链(SUCR notifier chains):可阻塞通知链的一种变体,对应的链表头:
struct  srcu_notifier_head {
        struct  mutex mutex;
        struct  srcu_struct  srcu;
        struct  notifier_block  *head;
};

1.3运行机理
被通知一方(other_subsys_x)—-通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifer_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block.
1.3.1向事件通知链注册的步骤
1.声明struct notifier_block结构
2.编写notifier_call函数
3.调用特定的,事件通知链的注册函数(notifier_chain_register被包装了),将notifier_block注册到通知链中
如果内核组件需要处理某个事件通知链上的事件通知,就该在初始化话时候(probe的时候),在该通知链上注册回调函数。把这个模块挂上去,通知一来,就进行回调函数的调用。
1.3.2 通知子系统有事件发生
notifier_call_chain:是inet_subsys用来通知其他的子系统(other_subsys_x),有事件发生
它会按照通知链上各个成员的优先级顺序执行回调函数(notifier_call_x);
回调函数的执行现场在notifier_call_chain进程地址空间,其返回值是NOTIFY_XXX的形式,在include/linux/notifier.h中:

struct  srcu_notifier_head {
        struct  mutex mutex;
        struct  srcu_struct  srcu;
        struct  notifier_block  *head;
};
 notifier_call_chain捕获并返回最后一个事件处理函数的返回值;注意:notifier_call_chain可能同时被不同的cpu调用,故而调用者必须保持互斥。

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

/* netdevice notifier chain */
#define NETDEV_UP  0x0001  /* 激活一个网络设备 */
#define NETDEV_DOWN  0x0002f /* 停止一个网络设备,所有对该设备的引用都应释放 */
#define NETDEV_REBOOT       0x0003       /* 检查到网络设备接口硬件崩溃,硬件重启 */
#define NETDEV_CHANGE       0x0004  /* 网络设备的数据包队列状态发生改变 */
#define NETDEV_REGISTER  0x0005   /*一个网络设备事例注册到系统中,但尚未激活 */
#define NETDEV_UNREGISTER       0x0006       /*网络设备驱动已卸载 */
#define NETDEV_CHANGEMTU      0x0007  /*MTU发生了改变 */
#define NETDEV_CHANGEADDR    0x0008  /*硬件地址发生了改变 */
#define NETDEV_GOING_DOWN   0x0009  /*网络设备即将注销,有dev->close报告,通知相关子系统处理 */
#define NETDEV_CHANGENAME   0x000A  /*网络设备名改变 */
#define NETDEV_FEAT_CHANGE    0x000B  /*feature网络硬件功能改变 */
#define NETDEV_BONDING_FAILOVER 0x000C  /*    */
#define NETDEV_PRE_UP        0x000D  /*    */
#define NETDEV_BONDING_OLDTYPE  0x000E              /*    */
#define NETDEV_BONDING_NEWTYPE  0x000F      /*    */

1.4简单的一例:
通过上面所述,notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:
当 test_notifier_chain_2通过module_init初始化模块时发出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相应的处理:打印 test_notifier_chain_2正在初始化。

/* test_chain_0.c :0. 申明一个通知链;1. 向内核注册通知链;2. 定义事件; 3. 导出符号,因而必需最后退出*/

#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");
//  call_test_notifiers(TESTCHAIN_EXIT, (int *)NULL);
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_0_init);
module_exit(test_chain_0_exit);

/* test_chain_1.c :1. 定义回调函数;2. 定义notifier_block;3. 向chain_0注册notifier_block;*/
#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);<span style="white-space:pre;"> </span>// 由chain_0提供的设施
    return 0;
}

static void __exit test_chain_1_exit(void)
{
    printk(KERN_DEBUG "Goodbye to test_clain_l\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_1_init);
module_exit(test_chain_1_exit);

/* test_chain_2.c:发出通知链事件*/

#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 v2");
MODULE_AUTHOR("fishOnFly");

module_init(test_chain_2_init);
module_exit(test_chain_2_exit);

# Makefile

# Comment/uncomment the following line to disable/enable debugging
# DEBUG = y


# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
  DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
  DEBFLAGS = -O2
endif


ifneq ($(KERNELRELEASE),)
# call from kernel build system

obj-m   := test_chain_0.o test_chain_1.o test_chain_2.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif



clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

depend .depend dep:
    $(CC) $(CFLAGS) -M *.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko
[wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko


[wang2@iwooing: notifier_chian]$ dmesg

[ 5950.112649] I'm in test_chain_0
[ 5956.766610] I'm in test_chain_1
[ 5962.570003] I'm in test_chain_2
[ 5962.570008] I got the chain event: test_chain_2 is on the way of init

[ 6464.042975] Goodbye to test_chain_2
[ 6466.368030] Goodbye to test_clain_l
[ 6468.371479] Goodbye to test_chain_0
其中:
static RAW_NOTIFIER_HEAD(test_chain); 注册通知链
static struct notifier_block test_init_notifier = {  
    .notifier_call = test_init_event,  
};赋值block
raw_notifier_chain_register(&test_chain, nb);  绑定通知链和block
raw_notifier_call_chain(&test_chain, val, v); 向通知链发信息,说事情来了,执行通知链上各个block的回调函数

1.5内核已经总结定义了一些链:
死亡提醒:通过register_die_notifier注册,当内核函数触发了一个陷阱或者为例错误发送,由oops页错误或者断点命中引发。
网络设备提醒:通过register_netdevice_notifier注册:网络接口启动或关闭时产生。
cpu频率提醒:通过cpufreq_register_notifier注册:当处理器频率跃变时分发出去。
因特网地址提醒:通过register_inetaddr_notifier注册:当网络接口的IP地址发生变化被检测时发送

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值