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

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

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

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

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

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

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

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

#include

#include

#include /* printk() */

#include /* 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

#include

#include

#include       /* printk() */

#include           /* 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

#include

#include

#include /* printk() */

#include /* 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

linux 网络函数调用链,Linux通知链机制及实例相关推荐

  1. linux 网络使用log,linux 网络命令last、lastlog、traceroute、netstat

    last /usr/bin/last 语法:last 功能:列出目前与过去登入系统的用户信息 reboot 是重启信息 lastlog lastlog -u 502(用户ID) traceroute ...

  2. linux 虚拟机大量udp请求失败_理解 Linux 网络栈:Linux 网络协议栈简单总结分析...

    1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linu ...

  3. 理解 Linux 网络栈:Linux 网络协议栈简单总结

    1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linu ...

  4. linux网络编程大杂烩==Linux应用编程7

    一.Linux 网络编程框架 1.网络是分层的 (1)OSI 七层模型:应用层.表示层.会话层.传输层.网络层.数据链路层.物理层. (2)网络为什么要分层:互联网及其复杂,需要分层以便更好地实现网络 ...

  5. linux网络编程 ppt,LINUX网络编程.ppt

    <LINUX网络编程.ppt>由会员分享,可在线阅读,更多相关<LINUX网络编程.ppt(47页珍藏版)>请在人人文库网上搜索. 1.LINUX网络编程,行业事业部 黄文举 ...

  6. linux网络协议栈招聘,Linux 网络协议栈开发(一)ping命令

    linux网络开发中比较常用的命令之一是ping,最近一直再查rtl的模块连接问题,使用ping命令后一段时间,模块就卡主了感觉,不能完成基本的网络通信了,所以来查一查,我通常使用了ping命令加上要 ...

  7. linux网络驱动架构,Linux网络体系架构和网卡驱动设计

    Linux网络体系架构 1.Linux的协议栈层次 2.Linux的网络子系统架构 Linux的协议栈层次 Linux的优点之一在于它丰富而稳定的网络协议栈.其范围从协议无关层(例如通用socket层 ...

  8. linux 网络端口状态,Linux下用netstat查看网络状态、端口状态(转)

    转:http://blog.csdn.net/guodongdongnumber1/article/details/11383019 在linux一般使用netstat 来查看系统端口使用情况步. n ...

  9. linux网络共享文件夹,[Linux] - Windows与Linux网络共享文件夹挂载方法

    Windows与Linux网络SMB方式文件夹共享挂载 本示例系统: Windows 2003+ Linux-Centos/Ubuntu 本示例全为命令行操作,如何使用Windows.Linux命令行 ...

最新文章

  1. matlab 霍特林变换,数字图像处理(第3版面向CS2013计算机专业规划教材)
  2. android listview万能适配器
  3. String直接赋字符串和new String的区别
  4. python查询结果写入excel_python实现查询的数据写入到excel
  5. SAP 电商云 Spartacus UI 的响应式 UI 实现细节
  6. this指针 java_彻底理解Java中this指针
  7. 诗和远方:无题(四十五)
  8. python是什么类型的语言-Python是什么?简单了解pythonp-入门
  9. python资源网站_Python 博客网站资源
  10. MTK LK阶段 display框架
  11. 云计算的定义和特点是什么?
  12. 大数据学习总结(2021版)---Mysql基础
  13. python-图片文字识别
  14. linux系统修改ip地址教程。
  15. 基于GB/T 28181 标准的监控摄像头视频接入技术
  16. maven库 导入不进来
  17. 计算机桌面都有说明书,360桌面助手功能使用说明
  18. java neo4j配置_Neo4j教程 - 3 详解 Neo4j 核心配置
  19. 6.java项目-尚医通(6)
  20. ORACLE 数据库日常巡检

热门文章

  1. 怎么样写好头部姿态的研究背景?
  2. VScode 一些快捷操作 删除空行
  3. python For 循环 三种遍历方式
  4. 频谱、能谱、功率谱、倍频程谱、1/3 倍频程谱
  5. interrupt、interrupted 、isInterrupted 详解04
  6. JAVA 命令行运行java项目
  7. 纯CSS3实现常见的时间进度线(竖立方向)
  8. [MRCTF2020]套娃
  9. 硬盘结构,主引导记录MBR,硬盘分区表DPT,主分区、扩展分区和逻辑分区
  10. SaaS是什么?企业采购SaaS有什么好处?