您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > linux百科

Linux内核基础 | 通知链机制

时间:2022-08-02 14:29:46  来源:  作者: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的回调函数。

 



Tags:Linux内核   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  Search: Linux内核  点击:(74)  评论:(0)  加入收藏
深入Linux内核:探秘进程实现的神秘世界
在计算机科学的世界中,操作系统是一个无可争议的关键组成部分。而Linux内核作为一款世界著名的开源操作系统内核,其进程管理系统更是备受瞩目。本文将深入剖析Linux内核中如何...【详细内容】
2023-11-24  Search: Linux内核  点击:(339)  评论:(0)  加入收藏
Linux内核显示、加载、卸载等超实用命令
内核模块是 Linux 系统中一种特殊的可执行文件,它可以在运行时动态地加载到内核中或卸载出内核,从而实现内核的扩展和优化。内核模块操作相关的命令主要有以下几种:1.lsmod命令...【详细内容】
2023-10-30  Search: Linux内核  点击:(208)  评论:(0)  加入收藏
一文学会Linux内核的编译和调试
前言虽然我们很多人都是在Linux系统上做应用程序开发,一般接触不到Linux内核代码,但是了解Linux内核的底层实现机制,对应用程序的开发,尤其是性能方面的优化提升会有很大的帮助...【详细内容】
2023-09-09  Search: Linux内核  点击:(338)  评论:(0)  加入收藏
仅8670行代码,Linux内核第一版 (v0.01) 开源代码解读
出品 | OSC开源社区(ID:oschina2013)《Exploring the internals of Linux v0.01》是一篇解读 Linux 内核第一版开源代码的文章。此文今天在 Reddit 和 Hacker News 都冲上了热...【详细内容】
2023-08-14  Search: Linux内核  点击:(232)  评论:(0)  加入收藏
Linux内核中的网络设备驱动
本文将对Linux内核网络设备驱动源码进行详细的分析。首先,我们将介绍网络设备驱动的基本概念和作用,然后讨论Linux内核网络设备驱动的体系结构和实现原理,最后对内核网络设备驱...【详细内容】
2023-05-12  Search: Linux内核  点击:(351)  评论:(0)  加入收藏
Linux内核进程管理与调度:策略优化与实践分析
一、前言今天给大家上点硬货,关于Linux的进程管理和调度是学习和理解Linux的必学知识。为协调多个进程 "同时" 运行,现代操作系统通常使用进程优先级这一基本手段。每个进程都...【详细内容】
2023-05-08  Search: Linux内核  点击:(478)  评论:(0)  加入收藏
Linux内核模块的编译原理
Linux内核是一个开放源代码的操作系统内核,它是基于Unix操作系统的内核,被广泛用于服务器、个人电脑和嵌入式系统。Linux的开放源代码使得人们可以自由地使用、修改和分发Lin...【详细内容】
2023-04-29  Search: Linux内核  点击:(380)  评论:(0)  加入收藏
Linux内核常用保护和绕过技术
内核保护和利用是一个长期对抗的过程,出现了新的利用方法相应的也会出现新的对抗手段。 安全防护并不能完全保证内核是安全的,一旦有危害性更高的漏洞出现,就很容易打破这些保...【详细内容】
2023-02-28  Search: Linux内核  点击:(235)  评论:(0)  加入收藏
Linux内核MMC里的轮询机制
从这篇文章你能学到如何使用MMC框架里的轮询机制做探卡检测,十分简单。1 前言最近遇到客户提的一个问题,大概意思是他们的SDIO Wi-Fi在卸载Wi-Fi驱动后再加载就检测不到Wi-Fi...【详细内容】
2023-02-09  Search: Linux内核  点击:(300)  评论:(0)  加入收藏
▌简易百科推荐
微软 Win11 Linux 子系统(WSL)发布 2.2.2 版本
IT之家 4 月 8 日消息,微软近日更新 Windows Subsystem for Linux(WSL),最新 2.2.2 版本中带来了诸多改进,重点更新了 nft 规则,可以让 IPv6 流量通过 Linux 容器。图源: dev.to,AI...【详细内容】
2024-04-08    IT之家  Tags:Linux   点击:(10)  评论:(0)  加入收藏
从原理到实践:深入探索Linux安全机制
Linux 是一种开源的类Unix操作系统内核,由Linus Torvalds在1991年首次发布,其后又衍生出许多不同的发行版(如Ubuntu、Debian、CentOS等)。前言本文将从用户和权限管理、文件系统...【详细内容】
2024-03-27  凡夫编程  微信公众号  Tags:Linux安全   点击:(26)  评论:(0)  加入收藏
在Linux系统中,如何处理内存管理和优化的问题?
本文对 Linux 内存管理和优化的一些高级技巧的详细介绍,通过高级的内存管理技巧,可以帮助系统管理员和开发人员更好地优化 Linux 系统的内存使用情况,提高系统性能和稳定性。在...【详细内容】
2024-03-26  编程技术汇  微信公众号  Tags:Linux   点击:(18)  评论:(0)  加入收藏
Linux 6.9-rc1 内核发布:AMD P-State 首选核心、BH 工作队列
IT之家 3 月 25 日消息,Linus Torvalds 宣布,Linux 6.9 内核的首个 RC(候选发布)版 Linux 6.9-rc1 发布。▲ Linux 6.9-rc1Linus 表示,Linux 内核 6.9 看起来是一个“相当正常”...【详细内容】
2024-03-25    IT之家  Tags:Linux   点击:(16)  评论:(0)  加入收藏
轻松实现Centos系统的软件包安装管理:yum指令实战详解
yum 是一种用于在 CentOS、Red Hat Enterprise Linux (RHEL) 等基于 RPM 的 Linux 发行版上安装、更新和管理软件包的命令行工具。它可以自动解决软件包依赖关系,自动下载并...【详细内容】
2024-02-27  凡夫贬夫  微信公众号  Tags:Centos   点击:(61)  评论:(0)  加入收藏
Win + Ubuntu 缝合怪:第三方开发者推出“Wubuntu”Linux 发行版
IT之家 2 月 26 日消息,一位第三方开发者推出了一款名为“Wubuntu”的缝合怪 Linux 发行版,系统本身基于 Ubuntu,但界面为微软 Windows 11 风格,甚至存在微软 Windows 徽标。据...【详细内容】
2024-02-27    IT之家  Tags:Ubuntu   点击:(55)  评论:(0)  加入收藏
Linux中磁盘和文件系统工作原理解析
在Linux系统中,一切皆文件的概念意味着所有的资源,包括普通文件、目录以及设备文件等,都以文件的形式存在。这种统一的文件系统管理方式使得Linux系统具有高度的灵活性和可扩展...【详细内容】
2024-02-20  王建立    Tags:Linux   点击:(61)  评论:(0)  加入收藏
Linux子系统概览
inux操作系统是一个模块化的系统,由多个子系统组成。这些子系统协同工作,使Linux能够执行各种任务。了解Linux的子系统有助于更好地理解整个操作系统的运作机制。以下是Linux...【详细内容】
2024-02-01    简易百科  Tags:Linux   点击:(89)  评论:(0)  加入收藏
Linux内核:系统之魂与交互之源
内核,作为任何基于Linux的操作系统的心脏,扮演着至关重要的角色。它不仅是计算机系统软件与硬件之间的桥梁,更是确保系统稳定、高效运行的关键。内核提供了一系列核心功能,为上...【详细内容】
2024-02-01  松鼠宝贝    Tags:Linux内核   点击:(74)  评论:(0)  加入收藏
如何确保Linux进程稳定与持久
在Linux系统中,进程的稳定性与持久性对于维持系统的持续运行至关重要。然而,由于各种原因,进程可能会面临崩溃或系统重启的情况。为了确保关键进程能够持续运行,我们必须采取一...【详细内容】
2024-01-19  松鼠宝贝    Tags:Linux进程   点击:(94)  评论:(0)  加入收藏
站内最新
站内热门
站内头条