Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为<linux/notifier.h>。事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
内核通知链类型
通知链的核心结构是notifier_block,其数据结构如下:
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
notifier_call:是通知链要执行的函数指针。
next:用来连接其它的通知结构。
priority:是这个通知的优先级。
原子通知链( Atomic notifier chains ):通知链元素的回调函数(当事件发生时要执行的函数)在中断或原子操作上下文中运行,不允许阻塞。数据结构如下:
struct atomic_notifier_head {
spinlock_t lock;
struct notifier_block *head;
};
可阻塞通知链( Blocking notifier chains ):通知链元素的回调函数在进程上下文中运行,允许阻塞。数据结构如下:
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
原始通知链( Raw notifier chains ):对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。数据结构如下:
struct raw_notifier_head {
struct notifier_block *head;
};
SRCU 通知链( SRCU notifier chains ):可阻塞通知链的一种变体。数据结构如下:
struct srcu_notifier_head {
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block *head;
};
逻辑流程
既然是通知链,那么就分为通知者和被通知者,它们的行为如下:
通知者:当指定的事件发生的时候,通知者会通过通知链来将时间发送给被通知者。
被通知者:定义了当事件发生时,对应的事件处理函数。
逻辑流程为:
1、通知者定义通知链。
2、被通知者向通知者定义的通知链中注册事件处理函数。
3、当事件发生的时候,通知者向被通知者发送信息,被通知者来处理该事件。
操作函数
定义通知链:
#define ATOMIC_NOTIFIER_HEAD(name)
原子通知链。
#define BLOCKING_NOTIFIER_HEAD(name)
可阻塞通知链。
#define RAW_NOTIFIER_HEAD(name)
原始通知链。
注册通知链 :
int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *n);
原子通知链。
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n);
可阻塞通知链。
int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *n);
原始通知链。
int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *n);
SRCU通知链。
移除通知链:
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *nb);
原子通知链。
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *nb);
可阻塞通知链。
int raw_notifier_chain_unregister(struct raw_notifier_head *nh, struct notifier_block *nb);
原始通知链。
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *nb);
SRCU通知链。
通知函数:
int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);
原子通知链。
int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v);
可阻塞通知链。
int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
原始通知链。
int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
SRCU通知链。
实例
我们定义3个驱动程序,notifier、recipient和platform,platform中定义通知链,用户程序向notifier发出信号,notifier通过通知链将事件交由recipient进行处理。我们使用的是可阻塞通知链。
platform.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>
MODULE_LICENSE("GPL");
static BLOCKING_NOTIFIER_HEAD(csdn_notifier_list); /* 通知链头 */
int csdn_register_client(struct notifier_block *nb)
{
/* 注册通知链 */
return blocking_notifier_chain_register(&csdn_notifier_list, nb);
}
EXPORT_SYMBOL(csdn_register_client);
int csdn_unregister_client(struct notifier_block *nb)
{
/* 卸载通知链 */
return blocking_notifier_chain_unregister(&csdn_notifier_list, nb);
}
EXPORT_SYMBOL(csdn_unregister_client);
int csdn_notifier_call_chain(unsigned long val, void *v)
{
/* 发送通知 */
return blocking_notifier_call_chain(&csdn_notifier_list, val, v);
}
EXPORT_SYMBOL(csdn_notifier_call_chain);
static __init int csdn_init(void)
{
pr_info("csdn_platform_init\n");
return 0;
}
static __exit void csdn_exit(void)
{
pr_info("csdn_platform_exit\n");
}
module_init(csdn_init);
module_exit(csdn_exit);
platform.h代码
#ifndef __CSDN_PLATFORM_H__
#define __CSDN_PLATFORM_H__
extern int csdn_register_client(struct notifier_block *nb);
extern int csdn_unregister_client(struct notifier_block *nb);
extern int csdn_notifier_call_chain(unsigned long val, void *v);
#endif
csdn_recipient.c代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <asm/uaccess.h>
#include "csdn_platform.h"
MODULE_LICENSE("GPL");
static int minor = 0; /* 次设备号 */
static dev_t csdn_dev;
static struct cdev csdn_cdev;
static struct class *csdn_class = NULL;
static struct device *csdn_device = NULL;
static struct file_operations csdn_fops = {
.owner = THIS_MODULE,
};
static int csdn_callback(struct notifier_block *nb, unsigned long val, void *data) {
pr_info("csdn_callback enter! val: %lu\n", val);
return 0;
}
static struct notifier_block csdn_noti_block = {
.notifier_call = csdn_callback,
};
/* 创建设备号 */
static int csdn_register_chrdev(void)
{
int result;
result = alloc_chrdev_region(&csdn_dev, minor, 1, "csdn_recipient");
if (result < 0) {
pr_err("alloc_chrdev_region failed! result: %d\n", result);
return result;
}
return 0;
}
/* 注册驱动 */
static int csdn_cdev_add(void)
{
int result;
cdev_init(&csdn_cdev, &csdn_fops);
csdn_cdev.owner = THIS_MODULE;
result = cdev_add(&csdn_cdev, csdn_dev, 1);
if (result < 0) {
pr_err("alloc_chrdev_region failed! result: %d\n", result);
unregister_chrdev_region(csdn_dev, 1);
return result;
}
return 0;
}
/* 创建设备节点 */
static int csdn_device_create(void)
{
csdn_class = class_create(THIS_MODULE, "csdn_recipient_class");
if (IS_ERR(csdn_class)) {
pr_err("class_create failed!\n");
goto class_create_fail;
}
csdn_device = device_create(csdn_class, NULL, csdn_dev, NULL, "csdn_recipient");
if (IS_ERR(csdn_device)) {
pr_err("device_create failed!\n");
goto device_create_fail;
}
return 0;
device_create_fail:
class_destroy(csdn_class);
class_create_fail:
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
return -1;
}
static __init int csdn_init(void)
{
int result;
pr_info("csdn_recipient_init\n");
result = csdn_register_chrdev();
if (result < 0) {
return result;
}
result = csdn_cdev_add();
if (result < 0) {
return result;
}
result = csdn_device_create();
if (result < 0) {
return result;
}
csdn_register_client(&csdn_noti_block);
return 0;
}
static __exit void csdn_exit(void)
{
pr_info("csdn_recipient_exit\n");
device_destroy(csdn_class, csdn_dev);
class_destroy(csdn_class);
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
csdn_unregister_client(&csdn_noti_block);
}
module_init(csdn_init);
module_exit(csdn_exit);
notifier.c代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <asm/uaccess.h>
#include "csdn_platform.h"
MODULE_LICENSE("GPL");
#define CSDN_IOC_MAGIC 'x'
#define CSDN_IOC_NOTIFIER _IOW(CSDN_IOC_MAGIC, 0, int)
static int minor = 0; /* 次设备号 */
static dev_t csdn_dev;
static struct cdev csdn_cdev;
static struct class *csdn_class = NULL;
static struct device *csdn_device = NULL;
static long csdn_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int retval = 0;
int ioctl_w_value = 0;
pr_info("csdn_notifier_ioctl");
if (_IOC_TYPE(cmd) != CSDN_IOC_MAGIC) {
return -ENODEV;
}
if (_IOC_DIR(cmd) & _IOC_READ)
retval = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
retval = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (retval)
return -EFAULT;
switch (cmd)
{
case CSDN_IOC_NOTIFIER:
pr_info("CSDN_IOC_NOTIFIER \n");
retval = copy_from_user(&ioctl_w_value, (int *)arg, sizeof(int));
pr_info("retval: %d, ioctl_w_value: %d\n", retval, ioctl_w_value);
csdn_notifier_call_chain(ioctl_w_value, NULL);
break;
default:
pr_warn("unsupport cmd:0x%x\n", cmd);
break;
}
return 0;
}
static struct file_operations csdn_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = csdn_ioctl,
};
/* 创建设备号 */
static int csdn_register_chrdev(void)
{
int result;
result = alloc_chrdev_region(&csdn_dev, minor, 1, "csdn_notifier");
if (result < 0) {
pr_err("alloc_chrdev_region failed! result: %d\n", result);
return result;
}
return 0;
}
/* 注册驱动 */
static int csdn_cdev_add(void)
{
int result;
cdev_init(&csdn_cdev, &csdn_fops);
csdn_cdev.owner = THIS_MODULE;
result = cdev_add(&csdn_cdev, csdn_dev, 1);
if (result < 0) {
pr_err("alloc_chrdev_region failed! result: %d\n", result);
unregister_chrdev_region(csdn_dev, 1);
return result;
}
return 0;
}
/* 创建设备节点 */
static int csdn_device_create(void)
{
csdn_class = class_create(THIS_MODULE, "csdn_notifier_class");
if (IS_ERR(csdn_class)) {
pr_err("class_create failed!\n");
goto class_create_fail;
}
csdn_device = device_create(csdn_class, NULL, csdn_dev, NULL, "csdn_notifier");
if (IS_ERR(csdn_device)) {
pr_err("device_create failed!\n");
goto device_create_fail;
}
return 0;
device_create_fail:
class_destroy(csdn_class);
class_create_fail:
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
return -1;
}
static __init int csdn_init(void)
{
int result;
pr_info("csdn_notifier_init\n");
result = csdn_register_chrdev();
if (result < 0) {
return result;
}
result = csdn_cdev_add();
if (result < 0) {
return result;
}
result = csdn_device_create();
if (result < 0) {
return result;
}
return 0;
}
static __exit void csdn_exit(void)
{
pr_info("csdn_notifier_exit\n");
device_destroy(csdn_class, csdn_dev);
class_destroy(csdn_class);
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
}
module_init(csdn_init);
module_exit(csdn_exit);
Makefile没有多大差别,参考如下:
obj-m := csdn_notifier.o
KERNELDIR ?= /usr/src/linux-headers-4.15.0-47-generic
PWD := $(shell pwd)
SYMBOL_INC = /home/zss/code/notifier/csdn_platform
KBUILD_EXTRA_SYMBOLS += /home/zss/code/notifier/csdn_platform
EXTRA_CFLAGS += -I $(SYMBOL_INC)
TEMP_OBJ=*.o .*.cmd *.mod.c *.order *.symvers .cache.mk .tmp_versions
modules:
$(MAKE) $(EXTRA_CFLAGS) -C $(KERNELDIR) M=$(PWD) modules
rm -rf $(TEMP_OBJ)
clean:
rm -rf *.ko
用户空间代码:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#define DEVICE_NAME "/dev/csdn_notifier"
#define CSDN_IOC_MAGIC 'x'
#define CSDN_IOC_NOTIFIER _IOW(CSDN_IOC_MAGIC, 0, int)
int main(int argc, char const *argv[])
{
int key = 0;
int fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
printf("open failed! error:%s\n", strerror(errno));
return -1;
}
while (1) {
printf("input> ");
scanf("%d", &key);
getchar();
if (ioctl(fd, CSDN_IOC_NOTIFIER, &key) < 0) {
printf("ioctl CSDN_IOC_NOTIFIER failed!\n");
return -1;
}
}
close(fd);
return 0;
}
模块加载和运行结果
模块加载顺序为:csdn_platform -> csdn_notifier -> csdn_recipient。
然后在用户空间输入1、2、3、4,打印结果如下:
[722453.224422] csdn_notifier_ioctl
[722453.224426] CSDN_IOC_NOTIFIER
[722453.224431] retval: 0, ioctl_w_value: 1
[722453.224436] csdn_callback enter! val: 1
[722454.442280] csdn_notifier_ioctl
[722454.442284] CSDN_IOC_NOTIFIER
[722454.442288] retval: 0, ioctl_w_value: 2
[722454.442292] csdn_callback enter! val: 2
[722456.553138] csdn_notifier_ioctl
[722456.553141] CSDN_IOC_NOTIFIER
[722456.553145] retval: 0, ioctl_w_value: 3
[722456.553148] csdn_callback enter! val: 3
[722458.692530] csdn_notifier_ioctl
[722458.692534] CSDN_IOC_NOTIFIER
[722458.692539] retval: 0, ioctl_w_value: 4
[722458.692544] csdn_callback enter! val: 4
源码下载:https://gitee.com/zhangshusheng/csdn_blog/tree/master/notification_chain