天天看點

linux notifier的了解和應用

notifier主要用于核心間的各個子產品的通信

  1. (通知源)子系統A進行定義初始化和回調函數的調用
  2. (被通知)子系統B進行回調函數的注冊和登出

    當A系統發生某種事件時,就調用通知鍊中的所有回調函數,B系統中注冊的回調函數就會得到執行。一旦執行回調函數,他會從連結清單頭依次執行每一個回調函數,那麼依次執行是依次性執行完,執行過程中任意時刻都可睡眠?這些需求也就産生了4種類型的notifier_chain。

struct notifier_block {		/* chain的基本機關 */
	int (*notifier_call)(struct notifier_block *, unsigned long, void *);
	struct notifier_block __rcu *next;
	int priority;
};
 
struct atomic_notifier_head {/* atmoic context; 執行(rcu_read_lock);回調函數執行不能阻塞;實時性高 */
	spinlock_t lock;
	struct notifier_block __rcu *head;
};
 
struct blocking_notifier_head {	/* process context;執行(rw_semaphore) ;回調函數執行可以阻塞;實時性相對低*/
	struct rw_semaphore rwsem;
	struct notifier_block __rcu *head;
};
 
struct raw_notifier_head {	/* 原始連結清單操作,注冊,執行過程無任何保護,完全有驅動人員控制 */
	struct notifier_block __rcu *head;
};
 
struct srcu_notifier_head {	/* process context;可阻塞通知鍊的變體(Sleepable Read-Copy-Update),回調函數執行可以阻塞 */
	struct mutex mutex;
	struct srcu_struct srcu;
	struct notifier_block __rcu *head;
};
           

notifer_chain的api使用的四大基本步驟,定義初始化,注冊,調用和登出,而這些操作的基本機關是notifier_block,這個基本機關中包含了回調函數的notifier_call,後繼notifier_block指針,優先級priority(預設0,數字越大優先級越大,越靠近連結清單的頭節點,越優先得到執行)。

  1. 初始化
#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)
           
/* srcu_notifier_heads must be initialized and cleaned up dynamically */
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);
	/* 注冊的notifier_block不重複*/
    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);
           

其中注冊和登出都調用了最基本的函數如下:

/*
 *	Notifier chain core routines.  The exported routines below
 *	are layered on top of these, with appropriate locking added.
 */
 
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*類型,使用者自定義任何類型含義,一般為平台結構體指針,使用的時候需要強制轉換。

/*調用了__atomic_notifier_call_chain,且nr_to_call=-1表示回調函數調用個數不限,nr_calls=NULL表示不關心已執行的回調函數個數*/
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);
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)
						/* Bad/Veto action 回調函數執行有錯*/
/*
 * Clean way to return from the notifier and stop further calls.目前順利調用,禁止繼續調用
 */
#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.

/*
 *  linux/drivers/video/fb_notify.c
 *
 *  Copyright (C) 2006 Antonino Daplas <[email protected]>
 *
 *	2001 - Documented with DocBook
 *	- Brad Douglas <[email protected]>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 */
#include <linux/fb.h>
#include <linux/notifier.h>
#include <linux/export.h>
 
/*靜态定義并初始化通知鍊頭*/
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
 
/** 注冊回調函數,加入一個回調函數節點
 *	fb_register_client - register a client notifier
 *	@nb: notifier block to callback on events
 */
int fb_register_client(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_register_client);
 
/** 登出回調函數,删除一個回調函數節點
 *	fb_unregister_client - unregister a client notifier
 *	@nb: notifier block to callback on events
 */
int fb_unregister_client(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_unregister_client);
 
/** 當源即framebuffer發生某種事件,調用該函數執行所有回調函數,通知其他子系統
 * fb_notifier_call_chain - notify clients of fb_events
 *
 */
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);
 
           

假如framenbuffer為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);
								/* 檢測是否是顯示器BLANK改變事件 */
	if (evdata && evdata->data && event == FB_EVENT_BLANK &&
			ft5x06_data && ft5x06_data->client) {
		blank = evdata->data;
		if (*blank == FB_BLANK_UNBLANK)	/*是BLANK事件中的LCD亮屏事件,喚醒觸屏*/
			ft5x06_ts_resume(&ft5x06_data->client->dev);
		else if (*blank == FB_BLANK_POWERDOWN)	/*是BLANK事件中的LCD滅屏事件,讓觸屏休眠 */
			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;
	/*注冊fb回調函數*/
	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)
	/*登出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 = ␣
		/*硬體BLANK操作成功後,調用所有的注冊回調函數,比如通知給觸屏*/
		fb_notifier_call_chain(FB_EVENT_BLANK, &event);
	}
 
 	return ret;
}
 
	/*fbmem中的ioctl*/
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
 
	//.......
		case FBIOBLANK:	//由上層發下來的亮屏還是息屏的IO指令
		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中的notifie_call_chain的第二,第三個參數,詳見linux\fb.h,fbmem.c,fbcon.c。

繼續閱讀