Chinaunix首页 | 论坛 | 博客
  • 博客访问: 327163
  • 博文数量: 161
  • 博客积分: 245
  • 博客等级: 二等列兵
  • 技术积分: 694
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-08 13:19
文章分类

全部博文(161)

文章存档

2016年(3)

2015年(31)

2014年(11)

2013年(107)

2012年(9)

分类: LINUX

2013-04-16 22:52:23

原文地址:linux系统通知机制 作者:yanghoo

Contents

概 述


    内核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即在某个子系统内发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了notification chain机制
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。

类似SOA中的服务请求与服务提供以及UDDI

Notification chain使用发布-订阅模型(publish-and-subscribe model):
    在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。
订阅者必须知道其他子系统提供了哪些事件通知支持,以选择可以订阅的事件通知;当然,订阅者本身也是一个子系统,因此也具有信息发布功能,因此它也要清楚本系统内哪些事件对其他子系统是有价值的,即有哪些本系统内的事件发生时需要通知订阅者,但是子系统对谁订阅了事件通知以及为什么要订阅一无所知。
从某种意义上来说,notification chain实现了事件信息的共享

struct notifier_block结构


Notification chain由notifier block组成,其结构类型如下(include/linux/notifier.h):
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};

notifier_call回调函数:self参数通常为notifier_block本身;unsigned long型参数表示发生的事件类型,因为一个chain可能支持多个事件,此参数用来对事件进行区分,在include/linux/notifier.h文件中预订了一些事件常量,例如与netdevice相关的就有多个事件;void *用来存放私有信息,其具体信息取决于特定的事件
next指针:用于同一个chain中的notifier_block的链接
priority:表示notifier_call函数的优先级,在事件发生时先调用高优先级的回调函数。实际上在chain中notifier block是根据优先级来进行排队的,高优先级的在前面,这样就可以容易地实现根据优先级来调用回调函数。同优先级的根据加入chain的顺序来排队,最新加入的排在同优先级的最后。通常该字段取缺省值0,这样回调函数就根据加入chain的顺序来调用。该字段的用法可参考函数
 
notifier_chain_register的实现。
实际上notification chain就是一组函数列表。通常notification chain的名字的格式为xxx_chain、xxx_notifier_chain、 xxx_notifier_list,例如reboot_notifier_list。

回调函数notifier_call

int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
前面已经讲过函数参数的含义,注意到该函数的返回值为int类型,这里就来看一下可能的返回值(include/linux/notifier.h文件中定义了这些常值):
NOTIFY_DONE              0x0000                                                        对该事件不感兴趣(根据unsigned long参数)
NOTIFY_OK                    0x0001                                                        成功响应该事件
NOTIFY_STOP_MASK  0x8000                                                        该回调函数返回后停止处理后续notifier block
NOTIFY_BAD                 (NOTIFY_STOP_MASK|0x0002)             出错,回调函数返回后停止处理后续notifier block
NOTIFY_STOP              (NOTIFY_OK|NOTIFY_STOP_MASK)     成功响应事件,回调函数返回后停止处理后续notifier block
注意,NOTIFY_STOP和NOTIFY_BAD的定义都包含了NOTIFY_STOP_MASK。


并发访问控制


在kernel/sys.c文件中定义了对notification chain进行并发访问控制的读写锁notifier_lock。系统中对所有notification chain的并发访问都是由该锁来控制。
子系统通常只在boot或者加载module时注册notifier block,即修改notification chain,而大多数时间仅以只读方式来访问,因此一个锁基本不会影响系统性能。


 基 本 API


下面的基本例程位于kernel/sys.c文件中
要接收某些事件的通知需要先注册到支持这些事件的notification chain中
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
list为notification chain
n为当前子系统提供的notifier_block,其中指明了回调函数
该函数会根据notifier_block的优先级priority将n插入到list中合适位置
如果不想接受已订阅事件的通知,则需要取消订阅注册:
int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
nl为notification chain
n为当前子系统提供的notifier_block
事件发生时,要通知订阅该事件的子系统:
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
n为notification chain
val为事件类型,前面提到过一个chain可能支持多个事件,该参数用来对事件进行区分
v存放特定于事件的信息
该函数会遍历chain,对chain中每个notifier block,以参数val和v调用其notifier_call函数,若notifier_call函数返回值中标志了NOTIFY_STOP_MASK(如NOTIFY_BAD、NOTIFY_STOP),则函数停止处理,返回当前notifier block的返回值;否则返回chain中最后一个notifier block的返回值。
注意:多数子系统都定义了这些基本例程的封装函数,因此很少看到对这些函数的直接调用。例如后面的例子中用到的register_reboot_notifier和unregister_reboot_notifier就是简单的封装函数。
 


 简 单 示 例


    在kernel/sys.c文件中定义了一个全局reboot_notifier_list,该chain用来挂接想在系统shutdown时执行的函数,例如进行某种清理工作。前面提到过register_reboot_notifier和unregister_reboot_notifier是对notifier_chain_register和notifier_chain_unregister简单封装,具体请参考实现,在此不再赘述。下面给出一个简单的使用notification chain的module例子:
#include
#include
#include
#include
static int myreboot(struct notifier_block *self, unsigned long event, void *data)
{
printk(KERN_ALERT "Just a test! Event code: %li! System reboot now...", event);
return NOTIFY_OK;
}
static struct notifier_block myreboot_notifier = {
.notifier_call = myreboot,
};
static int myreboot_init(void)
{
register_reboot_notifier(&myreboot_notifier);
return 0;
}
static void myreboot_exit(void)
{
unregister_reboot_notifier(&myreboot_notifier);
}
module_init(myreboot_init);
module_exit(myreboot_exit);
该模块向reboot_notifier_list注册了一个函数myreboot,该函数在系统reboot时会简单地打印一些信息(在系统shutdown时也可以)。下面图中划红线的部分为函数myreboot的输出。注意到传递给myreboot的event参数值为1,而include/linux/notifier.h文件中定义了与系统关机相关的一组事件常值,而1对应于事件SYS_DOWN。



 Linux Notification 机制的分析 

1. 基本机制 
1)数据结构 
struct notifier_block 

int (*notifier_call)(struct notifier_block *self, unsigned long, void *); 
struct notifier_block *next; 
int priority; /*用于对注册者进行优先级排队,高优先级的处理例程将被优先执行,由注册者自己指定 */ 
}; 
2)基本例程 
extern int notifier_chain_register(struct notifier_block **list, struct notifier_block *n); 
说明:注册到某个notifier_block链;这时的n可以只要初始化(*notifier_call)指针; 
extern int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n); 
说明:从某个notifier_block链中移去n; 
extern int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v); 
说明:轮循执行某个notifier_block链中的所有notifier_block,对其(*notifier_call)传入参数val和*v; 
其中val应该是EVENT NUMBER,而*v是导致这个事件的数据结构,比如某个网络设备UP,则val=NETDEV_UP,v=dev; 
3)返回值 
#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 */ 

4)已定义事件 
/* 
* Declared notifiers so far. I can imagine quite a few more chains 
* over time (eg laptop power reset chains, reboot chain (to clean 
* device units up), device [un]mount chain, module load/unload chain, 
* low memory chain, screenblank chain (for plug in modular screenblankers) 
* VC switch chains (for loadable kernel svgalib VC switch helpers) etc... 
*/ 

/* netdevice notifier chain */ 
#define NETDEV_UP 0x0001 /* For now you can't veto a device up/down */ 
#define NETDEV_DOWN 0x0002 
#define NETDEV_REBOOT 0x0003 /* Tell a protocol stack a network interface 
detected a hardware crash and restarted 
- we can use this eg to kick tcp sessions 
once done */ 
#define NETDEV_CHANGE 0x0004 /* Notify device state change */ 
#define NETDEV_REGISTER 0x0005 
#define NETDEV_UNREGISTER 0x0006 
#define NETDEV_CHANGEMTU 0x0007 
#define NETDEV_CHANGEADDR 0x0008 
#define NETDEV_GOING_DOWN 0x0009 
#define NETDEV_CHANGENAME 0x000A 

#define SYS_DOWN 0x0001 /* Notify of system down */ 
#define SYS_RESTART SYS_DOWN 
#define SYS_HALT 0x0002 /* Notify of system halt */ 
#define SYS_POWER_OFF 0x0003 /* Notify of system power off */ 


2. 举例分析 
以网络设备的通知信息块netdev_chain为例来说明如何使用notification机制。 
在net/core/dev.c中定义了netdev_chain链: 
static struct notifier_block *netdev_chain=NULL; 
提供别的模块的接口,以便它们使用netdev_chain链: 
/* 
* Device change register/unregister. These are not inline or static 
* as we export them to the world. 
*/ 

/** 
* register_netdevice_notifier - register a network notifier block 
* @nb: notifier 

* Register a notifier to be called when network device events occur. 
* The notifier passed is linked into the kernel structures and must 
* not be reused until it has been unregistered. A negative errno code 
* is returned on a failure. 
*/ 

int register_netdevice_notifier(struct notifier_block *nb) 

return notifier_chain_register(&netdev_chain, nb); 


/** 
* unregister_netdevice_notifier - unregister a network notifier block 
* @nb: notifier 

* Unregister a notifier previously registered by 
* register_netdevice_notifier(). The notifier is unlinked into the 
* kernel structures and may then be reused. A negative errno code 
* is returned on a failure. 
*/ 

int unregister_netdevice_notifier(struct notifier_block *nb) 

return notifier_chain_unregister(&netdev_chain,nb); 


以X25为例来说明使用者(--前面所说的订阅者)。 
在af_x25.c中,定义了: 
struct notifier_block x25_dev_notifier = { 
notifier_call: x25_device_event, 
}; 
然后模块初始化时向netdev_chain注册: 
static int __init x25_init(void) 

... ... 
register_netdevice_notifier(&x25_dev_notifier); 
... ... 

比如当NETDEV_UP事件发生时(--前面所说的事件发布者网卡驱动--》调用dev.c),调用到: 
notifier_call_chain(&netdev_chain, NETDEV_UP, dev); 
就会执行到x25_dev_notifier中注册的处理例程:x25_device_event,至于对相应的事件(event number)是不是感兴趣, 
需要处理例程自己来判断。 
static int x25_device_event(struct notifier_block *this, unsigned long event, void *ptr) 

struct net_device *dev = (struct net_device *)ptr; 
struct x25_neigh *neigh; 

if (dev->type == ARPHRD_X25 
#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE) 
|| dev->type == ARPHRD_ETHER 
#endif 
) { 
switch (event) { 
case NETDEV_UP: 
x25_link_device_up(dev); 
break; 
case NETDEV_GOING_DOWN: 
if ((neigh = x25_get_neigh(dev))) 
x25_terminate_link(neigh); 
break; 
case NETDEV_DOWN: 
x25_kill_by_device(dev); 
x25_route_device_down(dev); 
x25_link_device_down(dev); 
break; 



return NOTIFY_DONE; 


所有关于网络设备的事件全部在net/core/dev.c中发生,从而引发notifier_call_chain(&netdev_chain,val, dev)的调用: 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGE, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_UP, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_GOING_DOWN, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGE, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGEMTU, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGEADDR, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGEADDR, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_CHANGENAME, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_UNREGISTER, dev); 
Dev.c (linux\net\core): notifier_call_chain(&netdev_chain, NETDEV_UNREGISTER, dev); 

3. 总结 
从上面的分析可以看出,Linux下的Notification机制不是通过消息的方式实现的,而是一旦外部事件发生,所以对这个事件感兴趣的模块都会立即响应这个事件。但是,这个通知机制的效率不是很高,因为它的粒度不够细,比如A对E1、E2事件感兴趣,B对E2、E3感兴趣,但是E1~E3都是由N链来管理的,这样当发生E1事件时,A、B的处理例程都会被调用一次。如果能够区分对待不同模块感兴趣的事件集,然后只把事件发送到感兴趣的模块,效率会更高一些。另外,对优先级的处理是必须得,但是如何利用这个优先级似乎没有很好的说明和例证。

阅读(2955) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~