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

 

 


目录
相关文章
|
2月前
|
算法 Linux API
【Linux系统编程】Linux下删除文件的 API方式以及文件删除机制差异
【Linux系统编程】Linux下删除文件的 API方式以及文件删除机制差异
43 0
|
7天前
|
算法 大数据 Linux
深入理解Linux内核的进程调度机制
【4月更文挑战第30天】操作系统的核心职能之一是有效地管理和调度进程,确保系统资源的合理分配和高效利用。在众多操作系统中,Linux因其开源和高度可定制的特点,在进程调度机制上展现出独特优势。本文将深入探讨Linux内核中的进程调度器——完全公平调度器(CFS),分析其设计理念、实现原理及面临的挑战,并探索未来可能的改进方向。
|
7天前
|
算法 安全 Linux
深度解析:Linux内核内存管理机制
【4月更文挑战第30天】 在操作系统领域,内存管理是核心功能之一,尤其对于多任务操作系统来说更是如此。本文将深入探讨Linux操作系统的内核内存管理机制,包括物理内存的分配与回收、虚拟内存的映射以及页面替换算法等关键技术。通过对这些技术的详细剖析,我们不仅能够理解操作系统如何高效地利用有限的硬件资源,还能领会到系统设计中的性能与复杂度之间的权衡。
|
10天前
|
XML 安全 Linux
【Linux】深入探究CentOS防火墙(Firewalld):基础概念、常用命令及实例操作
【Linux】深入探究CentOS防火墙(Firewalld):基础概念、常用命令及实例操作
|
14天前
|
缓存 Linux
linux系统缓存机制
linux系统缓存机制
|
15天前
|
Linux 数据安全/隐私保护
Linux常用命令实例带注释
Linux常用命令实例带注释
34 0
|
20天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
25天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
|
2月前
|
缓存 监控 算法
Linux内核的SLAB内存管理机制
Linux内核的SLAB内存管理机制
107 4
|
2月前
|
存储 缓存 监控
Linux内存管理:理解正常波动背后的机制
Linux内存管理:理解正常波动背后的机制
61 0