开发者社区> sky-heaven> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Linux内核基础--事件通知链(notifier chain)good【转】

简介: 转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html 阅读目录(Content) 1.1. 概述 1.2.数据结构  1.3.  运行机理 1.
+关注继续查看

转载:

http://blog.csdn.net/wuhzossibility/article/details/8079025

http://blog.chinaunix.net/uid-27717694-id-4286337.html

 

内核通知链

1.1. 概述

       Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。

       通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于 kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区 几百行。

       事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。

 

1.2.数据结构

如图 1中所示,Linux的网络子系统一共有3个通知链:表示ipv4地址发生变化时的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_nofitier_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 notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:

网络子系统就是该类型,通过以下宏实现head的初始化

复制代码
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;
}
复制代码

而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier。

struct  raw_notifier_head {
        struct  notifier_block   *head;
};
 

4.  SRCU 通知链( SRCU 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注册回调函数,并且一般而言特 定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统 的:register_netdevice_notifier来注册他的notifier_block。

1.3.1.  向事件通知链注册的步骤

1. 申明struct notifier_block结构

2. 编写notifier_call函数

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

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

1.3.2.  通知子系统有事件发生

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         /* 对事件视而不见 */
#define NOTIFY_OK          0x0001         /* 事件正确处理 */
#define NOTIFY_STOP_MASK  0x8000         /*由notifier_call_chain检查,看继续调用回调函数,还是停止,_BAD和_STOP中包含该标志 */
#define NOTIFY_BAD        (NOTIFY_STOP_MASK|0x0002)       /*事件处理出错,不再继续调用回调函数 */
/*
 *Clean way to return from the notifier and stop further calls.
 */
#define NOTIFY_STOP             (NOTIFY_OK|NOTIFY_STOP_MASK)    /*  回调出错,不再继续调用该事件回调函数 */
复制代码

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_0、 test_notifier_chain_1、test_notifier_chain_2;当 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
复制代码

 


 

 一、概述

内核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即在某个子系统内发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了notification chain机制。
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。

二、结构体


1. notifier_block
//action参数表示发生的事件类型,因为一个chain可能支持多个事件,此参数用来对事件进行区分
//void *data用来存放私有信息,其具体信息取决于特定的事件

复制代码
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; //用于同一个chain中的notifier_block的链接
    int priority;//表示notifier_call函数的优先级,在事件发生时先调用高优先级的回调函数。
};
复制代码

notifier_call返回值为int类型,可能的返回值(include/linux/notifier.h文件中定义了这些常值)

复制代码
//对该事件不感兴趣
#define NOTIFY_DONE 0x0000
//成功响应该事件
#define NOTIFY_OK 0x0001
//该回调函数返回后停止处理后续notifier block
#define NOTIFY_STOP_MASK 0x8000
//出错,回调函数返回后停止处理后续notifier block
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002)
//成功响应事件,回调函数返回后停止处理后续notifier block
#define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
复制代码

 

2. 围绕核心数据结构notifier_block,内核定义了四种通知链类型:
1)原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:

struct atomic_notifier_head {
    spinlock_t lock;
    struct notifier_block __rcu *head;
};

 

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

struct blocking_notifier_head {
    struct rw_semaphore rwsem;
    struct notifier_block __rcu *head;
};

 

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

struct raw_notifier_head {
    struct notifier_block __rcu *head;
};

 

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

复制代码
struct srcu_notifier_head {
    struct mutex mutex;
    struct srcu_struct srcu;
    struct notifier_block __rcu *head;
};
复制代码

 

三、操作过程


被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block。
1.向事件通知链注册的步骤
(1)申明struct notifier_block结构
(2)编写notifier_call函数
(3)调用特定的事件通知链的注册函数,通过notifier_chain_register()将notifier_block注册到通知链中。实际上notification chain就是一组函数列表。通常notification chain的名字的格式为xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。

复制代码
static int notifier_chain_register(struct notifier_block **nl,struct notifier_block *n)
{
    while ((*nl) != NULL) {
        //判断优先权值, 优先权值越大位置越靠前
        if (n->priority > (*nl)->priority)
            break;
        nl = &((*nl)->next);
    }
    n->next = *nl;//将节点n链接到链表nl中的合适位置
    rcu_assign_pointer(*nl, n);
    return 0;
}
复制代码

 

2.通知子系统有事件发生
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
inet_subsys是通过notifier_call_chain来通知其他的子系统的。notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间.

复制代码
static int __kprobes notifier_call_chain(struct notifier_block **nl,unsigned long val, void *v,
                                        int nr_to_call, int *nr_calls)
{
    int ret = NOTIFY_DONE;
    struct notifier_block *nb, *next_nb;
    //取通知链中的notifier_block
    nb = rcu_dereference_raw(*nl);
     
    while (nb && nr_to_call) {
        next_nb = rcu_dereference_raw(nb->next);
         //调用回调函数
        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--;
    }
    return ret;
}
复制代码

 

该函数将一个通知块结构从通知链表中拆除:
static int notifier_chain_unregister(struct notifier_block **nl,struct notifier_block *n)

四、实例

1. 定义通知链并初始化

复制代码
static struct srcu_notifier_head cpufreq_transition_notifier_list;
srcu_init_notifier_head(&cpufreq_transition_notifier_list);
void srcu_init_notifier_head(struct srcu_notifier_head *nh)
{
    mutex_init(&nh->mutex);
    if (init_srcu_struct(&nh->srcu) < 0)
        BUG();
    nh->head = NULL;
}
复制代码

 

2. 定义结构体和回调函数

复制代码
static struct notifier_block __clk_cpufreq_notifier_block = {
    .notifier_call = __clk_cpufreq_notifier
};

static int __clk_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data)
{
    printk("dump cpu freq\n",);
    return 0;
}
复制代码

 

2. 注册
调用封装函数cpufreq_register_notifier(&__clk_cpufreq_notifier_block,CPUFREQ_TRANSITION_NOTIFIER)注册

复制代码
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list)
{
    int ret;

    WARN_ON(!init_cpufreq_transition_notifier_list_called);

    switch (list) {
    case CPUFREQ_TRANSITION_NOTIFIER:
        //将我们定义的notifier_block结构注册到cpufreq_transition_notifier_list链中
        ret = srcu_notifier_chain_register(&cpufreq_transition_notifier_list, nb);
        break;
    case CPUFREQ_POLICY_NOTIFIER:
        ret = blocking_notifier_chain_register(
                &cpufreq_policy_notifier_list, nb);
        break;
    default:
        ret = -EINVAL;
    }

    return ret;
}
复制代码

 

3.通知事件发生

复制代码
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state)
{
    struct cpufreq_policy *policy;

    BUG_ON(irqs_disabled());

    freqs->flags = cpufreq_driver->flags;
    pr_debug("notification %u of frequency transition to %u kHz\n",state, freqs->new);

    policy = per_cpu(cpufreq_cpu_data, freqs->cpu);
    switch (state) {
    case CPUFREQ_PRECHANGE:
        //......
        //向通知链进行事件通知,最终会调用notifier_call_chain()函数,最终执行回调函数
        srcu_notifier_call_chain(&cpufreq_transition_notifier_list,CPUFREQ_PRECHANGE, freqs);
        adjust_jiffies(CPUFREQ_PRECHANGE, freqs);
        break;

        //.......
    }
}
复制代码

 

【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
linux基础命令---dmesg显示内核信息
dmesg       dmesg指令用来打印和控制内核的输出信息,这些信息保存早ring buffer中。       此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、Fedora、SUSE、openSUSE。
1318 0
linux下挂载U盘【转】
转自:http://www.cnblogs.com/yeahgis/archive/2012/04/05/2432779.html 一.Linux挂载U盘:1、插入u盘到计算机,如果目前只插入了一个u盘而且你的硬盘不是scsi的硬盘接口的话,那它的硬件名称为:sda1。
855 0
+关注
sky-heaven
我是一个技术爱好者,喜欢分享交流技术心得
文章
问答
文章排行榜
最热
最新
相关电子书
更多
从 Linux 系统内核层面来解决实际问题的实战经验
立即下载
ECS系统指南之Linux系统诊断
立即下载
Decian GNU/Linux安全合规之路
立即下载