一、简单介绍
Linux内核中大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
二、Linux内核通知链框架图
三、内核的四种通知链类型
1. 原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。对应的链表头结构:
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
2. 可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
3. 原始通知链( Raw notifierchains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。对应的链表头:
网络子系统就是该类型,通过以下宏实现head的初始化
static RAW_NOTIFIER_HEAD(netdev_chain);
#define RAW_NOTIFIER_INIT(name) { \
.head= NULL }
#define RAW_NOTIFIER_HEAD(name) \
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)
即:
struct raw_notifier_head netdev_chain = {
.head = NULL;
}
而其回调函数的注册,比如向netdev_chain的注册函数:register_netdevice_notifier。
struct raw_notifier_head {
struct notifier_block *head;
};
5. SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。对应的链表头:
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
五、大家应该注意到上面提到的四种通知链里面都含有一个notifier_block结构,在头文件include/linux/notifier.h里面定义。
notifier_block的数据结构的内容:
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block __rcu *next;
int priority;
};
简单分析一下notifier_block的数据结构:
1. notifier_call:当相应事件发生时应该调用的函数,由被通知方提供,如other_subsys_1;
2. notifier_block *next:用于链接成链表的指针;
3. priority:回调函数的优先级,一般默认为0。
被通知一方(other_subsys_x)通过notifier_chain_register向特定的chain注册回调函数,并且一般而言特定的子系统会用特定的notifier_chain_register包装函数来注册,比如路由子系统使用的是网络子系统的:register_netdevice_notifier来注册他的notifier_block。
七、向事件通知链注册的步骤
1. 申明struct notifier_block结构
2. 编写notifier_call函数
3. 调用特定的事件通知链的注册函数,将notifier_block注册到通知链中
如果内核组件需要处理够某个事件通知链上发出的事件通知,其就该在初始化时在该通知链上注册回调函数。
八、通知子系统有事件发生
inet_subsys是通过notifier_call_chain来通知其他的子系统(other_subsys_x)的。
notifier_call_chain会按照通知链上各成员的优先级顺序执行回调函数(notifier_call_x);回调函数的执行现场在notifier_call_chain进程地址空间。
九、实例测试原始通知链,测试代码如下:
1、notifier_chain_register.c(注:申明一个通知链, 向内核注册通知链, 定义事件; )
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h> static RAW_NOTIFIER_HEAD(notfier_chain_fp); static int invoke_notifiers_chain(unsigned long val, void *v)
{ printk(KERN_DEBUG "invoke_notifiers_chain\n"); return raw_notifier_call_chain(?fier_chain_fp, val, v);
}
EXPORT_SYMBOL(invoke_notifiers_chain); static int register_notifier_chain(struct notifier_block *nb)
{ int err; err = raw_notifier_chain_register(?fier_chain_fp, nb); if(err) {printk(KERN_DEBUG "raw_notifier_chain_register error!\n"); goto out; }elseprintk(KERN_DEBUG "raw_notifier_chain_register success!\n");out: return err;
} EXPORT_SYMBOL(register_notifier_chain); static int __init notifier_chain_init(void)
{ printk(KERN_DEBUG "notifier_chain_init\n"); return 0;
} static void __exit notifier_chain_exit(void)
{ printk(KERN_DEBUG "notifier_chain_exit\n");
} MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("weifanghai"); module_init(notifier_chain_init);
module_exit(notifier_chain_exit);
对应的Makefile
obj-m +=notifier_chain_register.o KDIR := /home/weifanghai/Android_4.4_git/xunwei/kernel/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)all:make -C $(KDIR) M=$(PWD) modulesclean:rm -rf *.o
2、receiver_notifier_chain.c(注: 定义回调函数,定义notifier_block,向tifier_chain_register注册notifier_block)
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h> #include <linux/kernel.h>
#include <linux/fs.h> extern int register_notifier_chain(struct notifier_block *nb);
#define NOTIFIER_TCHAIN_NUMBER 520111 /* realize the notifier_call func */
int receiver_notifier_chain_event(struct notifier_block *nb, unsigned long event, void *v)
{ switch(event){ case NOTIFIER_TCHAIN_NUMBER: printk(KERN_DEBUG "^_^very great!received a notifier chain event!^_^\n"); break; default: break; } return NOTIFY_DONE;
}
/* define a notifier_block */
static struct notifier_block receiver_notifier_chain = { .notifier_call = receiver_notifier_chain_event,
};
static int __init receiver_notifier_chain_init(void)
{ printk(KERN_DEBUG "receiver_notifier_chain_init\n"); register_notifier_chain(&receiver_notifier_chain); return 0;
} static void __exit receiver_notifier_chain_exit(void)
{ printk(KERN_DEBUG "receiver_notifier_chain_exit!\n");
} MODULE_LICENSE("GPL");
MODULE_AUTHOR("weifanghai"); module_init(receiver_notifier_chain_init);
module_exit(receiver_notifier_chain_exit);
对应的Makefile
obj-m +=receiver_notifier_chain.o KDIR := /home/weifanghai/Android_4.4_git/xunwei/kernel/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)all:make -C $(KDIR) M=$(PWD) modulesclean:rm -rf *.o
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h> extern int invoke_notifiers_chain(unsigned long val, void *v);
#define NOTIFIER_TCHAIN_NUMBER 520111 static int __init send_nofifier_chain_init(void)
{ printk(KERN_DEBUG "send_nofifier_chain_init\n"); invoke_notifiers_chain(NOTIFIER_TCHAIN_NUMBER, "your are very beautiful!"); return 0;
} static void __exit send_nofifier_chain_exit(void)
{ printk(KERN_DEBUG "send_nofifier_chain_exit\n");
} MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("weifanghai"); module_init(send_nofifier_chain_init);
module_exit(send_nofifier_chain_exit);
对应Makefile
obj-m +=send_notifier_chain.o KDIR := /home/weifanghai/Android_4.4_git/xunwei/kernel/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)all:make -C $(KDIR) M=$(PWD) modulesclean:rm -rf *.o
运行三个ko,测试打印结果效果图:
小结:从打印信息看成功发送消息和接受消息,到这里关于Linux内核通知链notifier 的学习你应该有初步的认识了吧。