Linux通知链机制及实例

简介:


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

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

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

23025ad91fef433fbc6379955efefdb06288c16d

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。内核有四种类型的通知链链表表头。

l   原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:atomic_notifier_head

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

l   原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:raw_notifier_head,网络子系统就是该类型

l   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

 

 


目录
相关文章
|
10天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
29 5
|
13天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
31 6
|
20天前
|
消息中间件 存储 Linux
|
3月前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
2月前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
48 0
|
3月前
|
存储 Linux 网络安全
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
|
4月前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。
|
4月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
213 2
|
5月前
|
存储 缓存 Linux
Linux VFS机制详解
Linux VFS机制详解
162 1
|
5月前
|
Linux
Linux异步io机制 io_uring
Linux异步io机制 io_uring
79 1
下一篇
无影云桌面