<返回更多

Linux内核基础 | 通知链机制

2022-08-02    linux上的码农
加入收藏

一、通知链简介

举个形象的例子:将通知链比喻成”订阅者-发布者“,订阅者将感兴趣的公众号关注并设置提醒,发布者一旦发布某个文章,订阅者即可收到通知看到发布的内容。

linux内核中为了及时响应某些到来的事件,采取了通知链机制。该机制的两个角色的任务:

1、通知者定义通知链

2、被通知者向通知链中注册回调函数

3、当事件发生时,通知者发送通知 (执行通知链上每个调用块上的回调函数)所以通知链是一个单链表,单链表上的节点是调用块,每个调用块上有事件相关的回调函数和调用块的优先级。当事件触发时会按优先级顺序执行该链表上的回调函数。通知链只用于各个子系统之间,不能用于内核和用户空间进行事件的通知。

二、相关细节

1、通知链的类型

原子通知链( Atomic notifier chains ):

通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。

可阻塞通知链( Blocking notifier chains ):

通知链元素的回调函数在进程上下文中运行,允许阻塞。

原始通知链( Raw notifier chains ):

对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。

SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体

本文将以原子通知链进行分析

2、原子通知链与通知块

struct raw_notifier_head {
 struct notifier_block __rcu *head;
};

初始化一个原子通知链使用以下宏定义

#define RAW_NOTIFIER_HEAD(name)     
 struct raw_notifier_head name =    
  RAW_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_INIT(name) {    
  .head = NULL }

例如创建一个设备通知链队列头:

RAW_NOTIFIER_HEAD.NETdev_chain)

struct raw_notifier_head就相当于存放这条通知链单链表头,每一个通知链上的元素也就是通知块如下定义:

struct notifier_block { 
notifier_fn_t notifier_call; //通知调用的函数 
struct notifier_block __rcu *next;//指向下一个通知节点,从而形成链队 
int priority;//优先级,会根据优先级在单链表中排序
};

回调函数接口:

typedef int (*notifier_fn_t)(struct notifier_block *nb,
unsigned long action, void *data);

整个通知链的组织如下图所示:

 

3、向通知链中插入通知块

int raw_notifier_chain_register(struct raw_notifier_head *nh,
  struct notifier_block *n)
{
 return notifier_chain_register(&nh->head, n);
}
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;
 rcu_assign_pointer(*nl, n);
 return 0;
}

4、调用通知链

int raw_notifier_call_chain(struct raw_notifier_head *nh,
  unsigned long val, void *v)
{
 return __raw_notifier_call_chain(nh, val, v, -1, NULL);
}
int __raw_notifier_call_chain(struct raw_notifier_head *nh,
         unsigned long val, void *v,
         int nr_to_call, int *nr_calls)
{
 return notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
}
static int 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;

 nb = rcu_dereference_raw(*nl);
  //循环遍历调用链上的调用块
 while (nb && nr_to_call) {
  next_nb = rcu_dereference_raw(nb->next);

#ifdef CONFIG_DEBUG_NOTIFIERS
  if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
   WARN(1, "Invalid notifier called!");
   nb = next_nb;
   continue;
  }
#endif
//执行该调用块的回调函数
  ret = nb->notifier_call(nb, val, v);

  if (nr_calls)
   (*nr_calls)++;
    //如果该调用块的回调函数返回值为NOTIFY_STOP_MASK则跳出调用链的遍历,也就不执行后面的调用块的回调函数了
  if (ret & NOTIFY_STOP_MASK)
   break;
  nb = next_nb;
  nr_to_call--;
 }
 return ret;
}

三、编写内核模块进行实验

1、案例1

编写内核模块作为被通知者,向内核netdev_chain通知链中插入自定义通知块(在通知块中自定义事件触发的回调函数),源码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
 
//处理网络设备的启动与禁用等事件
int test_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
   struct net_device *dev = (struct net_device *)ptr;
 
 
    switch(event)
    {
        case NETDEV_UP:
             if(dev && dev->name)
                 printk("dev[%s] is upn",dev->name);
             break;
        case NETDEV_DOWN:
             if(dev && dev->name)
                 printk("dev[%s] is downn",dev->name);
                break;
        default:
             break;
    }
 
    return NOTIFY_DONE;
}          
       
                       
struct notifier_block devhandle={
    .notifier_call = test_netdev_event
};
 
static int __init  test_init(void)
{   
    /*
    在netdev_chain通知链上注册消息块 
    netdev_chain通知链是内核中用于传递有关网络设备注册状态的通知信息
 */
    register_netdevice_notifier(&devhandle);
 
    return 0; 
}   
 
static void __exit test_exit(void)
{
    unregister_netdevice_notifier(&devhandle);

    return;
}
 
 
module_init(test_init);
module_exit(test_exit);
 
MODULE_LICENSE("GPL");

Makefile:

obj-m:=Demo.o
                                    
CURRENT_PATH:=$(shell pwd)    
LINUX_KERNEL:=$(shell uname -r)  
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)  
                                    
all:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules  
clean:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean   

将模块插入内核后,将网卡关闭再重启一次,查看日志信息:

dx@ubuntu:~/Linux_Sys_code/Notice/Module3$sudo insmod Demo.ko
 dx@ubuntu:~/Linux_Sys_code/Notice/Module3$ dmesg
[24309.137937] inet[00000000baf272e6] is down
[24313.046209] inet[00000000baf272e6] is up

2、案例2

通过写两个内核模块,其中一个作为通知者一个作为被通知者

module_1.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>

/*
模块功能:1、初始化一个通知链
         2、定义事件的回调函数并向通知链中插入三个通知块(与之前定义的回调函数相对应)
         3、测试通知链:循环遍历通知链的通知块,并同时调用对应的回调函数
*/
static RAW_NOTIFIER_HEAD(test_chain_head);
EXPORT_SYMBOL_GPL(test_chain_head);

//通知块1的执行函数
static int A_call(struct notifier_block *nb, unsigned long event, void *v)
{

    printk("AAAAAAAAAA---------event_A occur!---------AAAAAAAAAAn");
    printk("my priority:%dn",nb->priority);
    return NOTIFY_DONE;
} 
//通知块1:testA
static struct notifier_block testA = {
    .notifier_call = A_call,
    .priority = 7,
};

//通知块2的执行函数
static int B_call(struct notifier_block *nb, unsigned long event, void *v)
{

    printk("BBBBBBBBBB---------event_B occur!---------BBBBBBBBBn");
    printk("my priority:%dn",nb->priority);
    return NOTIFY_STOP_MASK;
} 
//通知块2:testB
static struct notifier_block testB = {
    .notifier_call = B_call,
    .priority = 9,
};

//通知块1的执行函数
static int C_call(struct notifier_block *nb, unsigned long event, void *v)
{
    printk("CCCCCCCCCC---------event_c occur!---------CCCCCCCCCCn");
    printk("my priority:%dn",nb->priority);
    return NOTIFY_DONE;

}
static struct notifier_block testC = {
    .notifier_call = C_call,
    .priority = 6,
};


static int __init my_register(void)
{
    printk("----------register notice chain---------n");
    raw_notifier_chain_register(&test_chain_head,&testA);
    raw_notifier_chain_register(&test_chain_head,&testB);
    raw_notifier_chain_register(&test_chain_head,&testC);
    printk("----------register notice chain done---------n");
    //遍历已经注册的调用链
 struct notifier_block *nb, *next_nb;
    struct raw_notifier_head *tmp = &test_chain_head;
    struct notifier_block *head = tmp->head;
 nb = rcu_dereference_raw(head);
    printk("----Test registed notice call----n");
//循环遍历调用链,测试一下所插入的通知块
 while (nb) {
        int ret = NOTIFY_DONE;
        int index=0;
        next_nb = rcu_dereference_raw(nb->next);
        printk("notice%d fun:%p,priority:%d",++index,nb->notifier_call,nb->priority);
        ret = nb->notifier_call(nb, 1, NULL);          //调用注册的回调函数
        nb = next_nb;
 }
   printk("--------------Module_1 test end-------------n");
    return 0;
}
static void __exit my_unregister(void)
{
    raw_notifier_chain_unregister(&test_chain_head,&testA);
    raw_notifier_chain_unregister(&test_chain_head,&testB);
    raw_notifier_chain_unregister(&test_chain_head,&testC);
}


module_init(my_register);
module_exit(my_unregister);

MODULE_AUTHOR("Dong Xu");
MODULE_LICENSE("GPL");

module_2.c:模拟某事件发生,并调用通知链

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/notifier.h>

/*
模块功能:模拟某事件发生,并调用通知链.

*/

extern struct raw_notifier_head test_chain_head;

//某事件
static int event(unsigned long val)
{
    int ret = raw_notifier_call_chain(&test_chain_head,val,NULL);
    return notifier_to_errno(ret);
}

static int __init my_entry(void)
{
    event(666);//模拟某事件发生
    return 0;
}
static void __exit my_exit(void)
{
    printk("test endn");
}
module_init(my_entry);
module_exit(my_exit);

MODULE_AUTHOR("Dong Xu");
MODULE_LICENSE("GPL");

(module_1与module_2的Makefile可参考上面的Demo1)

运行时先插入module_1再插入module_2结果如下,红框内是module_1中的测试输出日志,绿框内为世界调用通知链时的执行结果日志。

 

从上面可以看到通知链的执行顺序是按照优先级进行的,那么当调用通知链时是否每个通知块上的回调函数都会执行呢?

答案:不是,每个被执行的notifier_block回调函数的返回值可能取值以下几个:

  1. NOTIFY_DONE:表示对相关的事件类型不关心。
  2. NOTIFY_OK:顺利执行。
  3. NOTIFY_BAD:执行有错。
  4. NOTIFY_STOP:停止执行后面的回调函数。
  5. NOTIFY_STOP_MASK:停止执行的掩码

如当返回值NOTIFY_STOP_MASK会停止执行后面优先级低的调用块的函数。

例如把module_1中通知块的回调函数B_call的返回值修改为NOTIFY_STOP_MASK后,重新编译,运行结果如下,只执行了调用链中调用块2的回调函数。

 

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>