内核的很多子系统之间有很强的相互依赖性,因此,其中一个子系统侦测到的或者产生的事件,其他的子系统可能很感兴趣。为了实现这种交互需求,Linux使用了所谓的通知链。通知链上的是一个个的notifiter_block结构:
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
priority代表的是该函数的优先级,较高的优先级的函数会先被执行,但是实际中几乎都不会理会该值。因此,执行的次序仅依赖于注册的顺序。回调函数notifier_call是在调用notifier_call_chain的进程上下文中执行,然而,回调函数也可以实现把通知信息安排在某处的队列,然后唤醒查看此通知信息的进程。
在内存中不同的通知被放在不同的链上,在需要将通知加入的时候只要调用相应的注册函数就可以了,比如register_inetaddr_notifier函数:
int register_inetaddr_notifier(struct notifier_block *nb){
return blocking_notifier_chain_register(&inetaddr_chain, nb);
}
一般的这些函数都是包裹函数,通过blocking_notifier_chain_register函数将notifier_block添加到不同的链表上:
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n){
int ret;
// 如果是在开机的时候还不具备down_write的条件
if (unlikely(system_state < SYSTEM_RUNNING))
return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
}
down_write的问题应该是很多其他函数的情况下是不会出现的,不过在这里需要分开考虑。下面是将通知插入到对应链的尾部:
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机制
rcu_assign_pointer(*nl, n);
return 0;
}
RCU机制就是为了让读写并行,下面将节点插入:
#define rcu_assign_pointer(p, v) \
({ \
if (!__builtin_constant_p(v) || \
((v) != NULL)) \
smp_wmb(); \
(p) = (v); \
})
其中__builtin_constant_p是GCC的内联函数,用于判断v是不是编译时常数。而smp_wmb则保证了前面的代码执行完了才执行后面的代码。优化屏障的代码如下:
#define barrier() __asm__ __volatile__("": : :"memory")
__volatile__表示阻止编译器对该值进行优化,确保变量使用了用户定义的精确地址,而不是装有同一信息的一些别名。memory表示指令修改了内存单元。对不同的体系结构应该使用不同的内存屏障:
内存屏障的宏定义 | 功能说明 |
mb() | 多(单)处理器的内存屏障 |
rmb() | 多(单)处理器的读内存屏障 |
wmb() | 多(单)处理器的写内存屏障 |
smp_mb() | 多处理器的内存屏障 |
smp_rmb() | 多处理器的读内存屏障 |
smp_wmb() | 多处理器的写内存屏障 |
对网络层代码表征特别重要事件的通知链有如下两个:
inetaddr_chain | 本地接口上的IPv4地址的插入、删除、变更的通知信息 |
netdev_chain | 有关网络设备注册状态的通知信息 |
static struct notifier_block fib_inetaddr_notifier = {
.notifier_call =fib_inetaddr_event,
};
static struct notifier_block fib_netdev_notifier = {
.notifier_call =fib_netdev_event,
};
void __init ip_fib_init(void){
// ...
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier);
// ...
}
---------------------------------
个人理解,欢迎拍砖。