linux notifier
来源:互联网 发布:多处理器编程的艺术 编辑:程序博客网 时间:2024/05/01 01:41
Linux庞大系统中,各个模块是相对独立的,那么模块间通信该如何做呢?当然你也可以使用全局资源,如果这样的话系统缺少独立性,会带来稳定性问题的。如果你说,使用共享内存,进程通信等,那么你曲解我的意思了,因为你说的大多是user space的,而我说的是内核模块级别的。notifier_chain,对就是它,实质上这个机制就是一个回调函数链表的操作,回调函数的注册,注销,调用。源系统处(比如A子系统)进行定义初始化和回调函数调用,被通知处(比如B子系统)进行回调函数的注册和注销,那么当A系统发生某种事件是,就调用通知链中所有回调函数,B系统中注册的回调函数就会得到执行。一旦执行回调函数,它会从链表头依次执行每一个回调函数,那么依次执行是一次性全部执行完?执行过程的任意时间都可睡眠?这些需求也就产生了4种类型的notifier_chain。
结构体定义:
- struct notifier_block {
- int (*notifier_call)(struct notifier_block *, unsigned long, void *);
- struct notifier_block __rcu *next;
- int priority;
- };
-
- 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;
- };
notifier_chain的API使用基本四大步骤:定义初始化、注册、调用和注销,而这些操作的基本单位是notifier_block,这个基本单位中包含了回调函数notifier_call,后继notifier_block指针,优先级priority(默认0,数字越大优先级越高,越靠近链表的头结点,越优先得到执行)。初始化:
#include <linux/notifier.h>
- #define ATOMIC_NOTIFIER_HEAD(name) \
- struct atomic_notifier_head name = \
- ATOMIC_NOTIFIER_INIT(name)
- #define BLOCKING_NOTIFIER_HEAD(name) \
- struct blocking_notifier_head name = \
- BLOCKING_NOTIFIER_INIT(name)
- #define RAW_NOTIFIER_HEAD(name) \
- struct raw_notifier_head name = \
- RAW_NOTIFIER_INIT(name)
-
- extern void srcu_init_notifier_head(struct srcu_notifier_head *nh);
- #define srcu_cleanup_notifier_head(name) \
- cleanup_srcu_struct(&(name)->srcu);
经过定义及初始化后,一类的链表的头就形成了,注册就是增加节点,执行就是遍历节点,唯一要说明的就是,srcu链需要动态的定义,其他三中不需要。
注册和注销:
- int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *n);
- int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *n)
- int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n);
-
- int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *n);
- int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *n);
- int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *n);
- int raw_notifier_chain_unregister(struct raw_notifier_head *nh, struct notifier_block *n);
- int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *n);
- int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *n);
其中注册和注销都调用了最基本的函数如下:
-
-
-
-
-
- static int notifier_chain_register(struct notifier_block **nl,
- struct notifier_block *n)
- {
- while ((*nl) != NULL) {
- if (n->priority > (*nl)->priority)
- break;
- nl = &((*nl)->next);
- }
- n->next = *nl;
- rcu_assign_pointer(*nl, n);
- return 0;
- }
-
- static int notifier_chain_cond_register(struct notifier_block **nl,
- struct notifier_block *n)
- {
- while ((*nl) != NULL) {
- if ((*nl) == n)
- return 0;
- if (n->priority > (*nl)->priority)
- break;
- nl = &((*nl)->next);
- }
- n->next = *nl;
- rcu_assign_pointer(*nl, n);
- return 0;
- }
-
- static int notifier_chain_unregister(struct notifier_block **nl,
- struct notifier_block *n)
- {
- while ((*nl) != NULL) {
- if ((*nl) == n) {
- rcu_assign_pointer(*nl, n->next);
- return 0;
- }
- nl = &((*nl)->next);
- }
- return -ENOENT;
- }
调用:
当A系统发生事件时,会调用下面函数之一的方法来,执行通知链中的所有回调函数,那么所有注册的地方都会得到执行,除前一个函数执行返回值含有NOTIFY_STOP_MASK标志。其中参数val是一个整形,用户自定义含义的传入值,参数v是一个void*类型,用户自定义任何类型含义,一般为平台结构体指针,使用的时候需要强制转换。
-
- extern int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);
- extern int __atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
-
- extern int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v);
- <span style="white-space:pre"> </span>extern int __blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
-
- extern int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
- extern int __raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
-
- extern int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
- extern int __srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
回调函数返回值:
- #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)
-
-
-
-
- #define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
在notifier_call_chain函数中去依次执行每一个注册的回调函数,并以前一个回调函数的返回值为判断依据,是否继续调用,最后一个执行函数的返回值作为notifier_call_chain的返回值。当前标准API中,只关注了NOTIFY_STOP_MASK,其他的在notifier_call_chain中未做处理。
典型用例:
在linux中,液晶显示器会提供一个fb_notify,当显示器发生某种事件时,会调用notifier_call_chain,这样注册的回调函数得到执行,回调函数根据显示的framebuffer状态执行你预想的动作。
它选用通知链类型的是blocking_notifier_chain。
-
-
-
-
-
-
-
-
-
-
-
-
- #include <linux/fb.h>
- #include <linux/notifier.h>
- #include <linux/export.h>
-
-
- static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
-
-
-
-
-
- int fb_register_client(struct notifier_block *nb)
- {
- return blocking_notifier_chain_register(&fb_notifier_list, nb);
- }
- EXPORT_SYMBOL(fb_register_client);
-
-
-
-
-
- int fb_unregister_client(struct notifier_block *nb)
- {
- return blocking_notifier_chain_unregister(&fb_notifier_list, nb);
- }
- EXPORT_SYMBOL(fb_unregister_client);
-
-
-
-
-
- int fb_notifier_call_chain(unsigned long val, void *v)
- {
- return blocking_notifier_call_chain(&fb_notifier_list, val, v);
- }
- EXPORT_SYMBOL_GPL(fb_notifier_call_chain);
假如framebuffer为A子系统,触屏ft5x06为B子系统,现想要做到触屏伴随显示屏息屏而休眠,亮屏而唤醒。
B子系统(触屏)/被通知者,代码如下:
- kernel\drivers\input\touchscreen\ft5x06_ts.c
-
- #if defined(CONFIG_FB)
- static int fb_notifier_callback(struct notifier_block *self,
- unsigned long event, void *data)
- {
- struct fb_event *evdata = data;
- int *blank;
- struct ft5x06_ts_data *ft5x06_data =
- container_of(self, struct ft5x06_ts_data, fb_notif);
-
- if (evdata && evdata->data && event == FB_EVENT_BLANK &&
- ft5x06_data && ft5x06_data->client) {
- blank = evdata->data;
- if (*blank == FB_BLANK_UNBLANK)
- ft5x06_ts_resume(&ft5x06_data->client->dev);
- else if (*blank == FB_BLANK_POWERDOWN)
- ft5x06_ts_suspend(&ft5x06_data->client->dev);
- }
-
- return 0;
- }
- #elif defined(CONFIG_HAS_EARLYSUSPEND)
-
-
-
- #endif
-
- static int ft5x06_ts_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
- {
-
-
- #if defined(CONFIG_FB)
- data->fb_notif.notifier_call = fb_notifier_callback;
-
- err = fb_register_client(&data->fb_notif);
-
- if (err)
- dev_err(&client->dev, "Unable to register fb_notifier: %d\n",
- err);
- #endif
-
-
- }
- static int __devexit ft5x06_ts_remove(struct i2c_client *client)
- {
-
-
- #if defined(CONFIG_FB)
-
- if (fb_unregister_client(&data->fb_notif))
- dev_err(&client->dev, "Error occurred while unregistering fb_notifier.\n");
- #elif defined(CONFIG_HAS_EARLYSUSPEND)
- unregister_early_suspend(&data->early_suspend);
- #endif
-
-
- }
A子系统(framebuffer)/通知者,代码如下:- int fb_blank(struct fb_info *info, int blank)
- {
- int ret = -EINVAL;
-
- if (blank > FB_BLANK_POWERDOWN)
- blank = FB_BLANK_POWERDOWN;
-
- if (info->fbops->fb_blank)
- ret = info->fbops->fb_blank(blank, info);
-
- if (!ret) {
- struct fb_event event;
-
- event.info = info;
- event.data = ␣
-
- fb_notifier_call_chain(FB_EVENT_BLANK, &event);
- }
-
- return ret;
- }
-
-
- static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
- unsigned long arg)
- {
-
-
- case FBIOBLANK:
- if (!lock_fb_info(info))
- return -ENODEV;
- console_lock();
- info->flags |= FBINFO_MISC_USEREVENT;
- ret = fb_blank(info, arg);
- info->flags &= ~FBINFO_MISC_USEREVENT;
- console_unlock();
- unlock_fb_info(info);
- break;
-
- }
至于framebuffer中的notifier_call_chain的第二、三个参数,详见linux\fb.h,fbmem.c,fbcon.c。有时间也写个关于fb相关的文章。通过上面所述,notifier_chain机制只能在内核个子系统间使用,因此,这里使用3个模块:test_notifier_chain_0、test_notifier_chain_1、test_notifier_chain_2;当 test_notifier_chain_2通过module_init初始化模块时发出事件TESTCHAIN_2_INIT;然后 test_notifier_chain_1作出相应的处理:打印 test_notifier_chain_2正在初始化。
-
-
- #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);
-
-
- static int call_test_notifiers(unsigned long val, void *v)
- {
- return raw_notifier_call_chain(&test_chain, val, v);
- }
- EXPORT_SYMBOL(call_test_notifiers);
-
-
- 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 v2");
- MODULE_AUTHOR("fishOnFly");
-
- module_init(test_chain_0_init);
- module_exit(test_chain_0_exit);
-
-
- #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
-
-
- 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;
- }
-
- 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);<span style="white-space:pre"> </span>
- return 0;
- }
-
- static void __exit test_chain_1_exit(void)
- {
- printk(KERN_DEBUG "Goodbye to test_clain_l\n");
- }
-
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("fishOnFly");
-
- module_init(test_chain_1_init);
- module_exit(test_chain_1_exit);
-
-
-
- #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 v2");
- MODULE_AUTHOR("fishOnFly");
-
- module_init(test_chain_2_init);
- module_exit(test_chain_2_exit);
-
- # Makefile
-
- # Comment/uncomment the following line to disable/enable debugging
- # DEBUG = y
-
-
- # Add your debugging flag (or not) to CFLAGS
- ifeq ($(DEBUG),y)
- DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
- else
- DEBFLAGS = -O2
- endif
-
-
- ifneq ($(KERNELRELEASE),)
- # call from kernel build system
-
- obj-m := test_chain_0.o test_chain_1.o test_chain_2.o
-
- else
-
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
-
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
-
- endif
-
-
-
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
-
- depend .depend dep:
- $(CC) $(CFLAGS) -M *.c > .depend
-
-
- ifeq (.depend,$(wildcard .depend))
- include .depend
- endif
-
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_0.ko
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_1.ko
- [wang2@iwooing: notifier_chian]$ sudo insmod./test_chain_2.ko
-
-
- [wang2@iwooing: notifier_chian]$ dmesg
-
- [ 5950.112649] I'm in test_chain_0
- [ 5956.766610] I'm in test_chain_1
- [ 5962.570003] I'm in test_chain_2
- [ 5962.570008] I got the chain event: test_chain_2 is on the way of init
-
- [ 6464.042975] Goodbye to test_chain_2
- [ 6466.368030] Goodbye to test_clain_l
- [ 6468.371479] Goodbye to test_chain_0