Linux内核通知链

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

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值