Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2303978
  • 博文数量: 318
  • 博客积分: 8752
  • 博客等级: 中将
  • 技术积分: 4944
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-23 07:56
文章分类

全部博文(318)

文章存档

2019年(1)

2017年(2)

2016年(12)

2015年(2)

2014年(1)

2013年(17)

2012年(22)

2011年(9)

2010年(37)

2009年(33)

2008年(44)

2007年(43)

2006年(95)

分类: LINUX

2009-12-01 17:05:08

1. 前言

notify是Linux内核中一种常用的事件回调处理机制,提供了基于优先级的回调链表处理功能。

以下内核代码版本为2.6.19.2。

2. 数据结构
/* include/linux/notifier.h */
// 基本的通知块结构
struct notifier_block {
// 回调函数
 int (*notifier_call)(struct notifier_block *, unsigned long, void *);
// 链表中的下一个结构, 这个一个单向链表
 struct notifier_block *next;
// 该块的优先级, 在链表中各个块是按此优先级值进行排序的, 值大的在链表前, 表明
// 相应回调函数执行的顺序
 int priority;
};

除了基本的通知块结构, 还定义了一些扩展的通知块结构:
// 原子通知头结构, 增加了一个锁来保证操作的原子性
struct atomic_notifier_head {
 spinlock_t lock;
 struct notifier_block *head;
};

// 阻塞通知头结构, 增加了一个读写信号灯
struct blocking_notifier_head {
 struct rw_semaphore rwsem;
 struct notifier_block *head;
};

// 原始通知头结构, 就是一个通知块指针
struct raw_notifier_head {
 struct notifier_block *head;
};

// srcu: Sleepable Read-Copy Update mechanism
// srcu通知头结构, 增加了锁和srcu结构
struct srcu_notifier_head {
 struct mutex mutex;
 struct srcu_struct srcu;
 struct notifier_block *head;
};

以下是一些宏来初始化各种类型的通知头结构, 一般在程序中使用:
#define ATOMIC_INIT_NOTIFIER_HEAD(name) do { \
  spin_lock_init(&(name)->lock); \
  (name)->head = NULL;  \
 } while (0)
#define BLOCKING_INIT_NOTIFIER_HEAD(name) do { \
  init_rwsem(&(name)->rwsem); \
  (name)->head = NULL;  \
 } while (0)
#define RAW_INIT_NOTIFIER_HEAD(name) do { \
  (name)->head = NULL;  \
 } while (0)
 
以下这些宏也是用来初始化各种类型的通知头结构, 但是在参数定义时使用:
#define ATOMIC_NOTIFIER_INIT(name) {    \
  .lock = __SPIN_LOCK_UNLOCKED(name.lock), \
  .head = NULL }
#define BLOCKING_NOTIFIER_INIT(name) {    \
  .rwsem = __RWSEM_INITIALIZER((name).rwsem), \
  .head = NULL }
#define RAW_NOTIFIER_INIT(name) {    \
  .head = NULL }
注意, 没有定义scru通知头结构的初始化, 因为scru是不能静态初始化的.
 
以下这些宏用来直接定义通知头结构:
#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)
 
3. 基本通知块操作函数

关于通知块的基本操作函数都在kernel/sys.c中定义

3.1 登记

该函数将一个通知块结构挂接到指定的通知链表
/*
 * Notifier chain core routines.  The exported routines below
 * are layered on top of these, with appropriate locking added.
 */
// nl是链表头块的地址, n是要添加到该链表的通知块
static int notifier_chain_register(struct notifier_block **nl,
  struct notifier_block *n)
{
// 使用的是dummy header算法, 即使刚开始时链表为空也不用显示判断区分
 while ((*nl) != NULL) {
// 判断优先权值, 优先权值越大位置越靠前
  if (n->priority > (*nl)->priority)
   break;
  nl = &((*nl)->next);
 }
// 将节点n链接到链表nl中的合适位置
 n->next = *nl;
// 使用rcu处理函数保证SMP下的安全性, 相当于加上锁再赋值
 rcu_assign_pointer(*nl, n);
 return 0;
}

3.2 撤销
该函数将一个通知块结构从通知链表中拆除:
// nl是链表头块的地址, n是要删除的通知块
static int notifier_chain_unregister(struct notifier_block **nl,
  struct notifier_block *n)
{
 while ((*nl) != NULL) {
// 地址匹配, 进行拆除操作
  if ((*nl) == n) {
// *nl=n->next的安全赋值操作,相当于将节点从链表断开
   rcu_assign_pointer(*nl, n->next);
   return 0;
  }
  nl = &((*nl)->next);
 }
 return -ENOENT;
}

3.3 回调函数处理

该函数执行链表中各节点的回调函数:

// nl通常是通知块链表头的地址, val和v是传给回调函数的参数
static int __kprobes notifier_call_chain(struct notifier_block **nl,
  unsigned long val, void *v)
{
 int ret = NOTIFY_DONE;
 struct notifier_block *nb, *next_nb;
// 安全地获取通知块指针
 nb = rcu_dereference(*nl);
// 链表循环
 while (nb) {
// 找下一个块
  next_nb = rcu_dereference(nb->next);
// 调用回调函数
  ret = nb->notifier_call(nb, val, v);
// 如果返回停止标志, 不执行后续结构
  if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
   break;
// 循环到下一节点
  nb = next_nb;
 }
 return ret;
}

4. 扩展的通知块操作

扩展的通知块操作功能和基本通知块类似, 但使用了扩展的结构中的参数保证操作的安全

4.1 原子通知块

4.1.1 登记
/*
 * Atomic notifier chain routines.  Registration and unregistration
 * use a spinlock, and call_chain is synchronized by RCU (no locks).
 */
/**
 * atomic_notifier_chain_register - Add notifier to an atomic notifier chain
 * @nh: Pointer to head of the atomic notifier chain
 * @n: New entry in notifier chain
 *
 * Adds a notifier to an atomic notifier chain.
 *
 * Currently always returns zero.
 */
// 只是在基本通知登记操作前后加锁解锁进行保护
int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
  struct notifier_block *n)
{
 unsigned long flags;
 int ret;
// 加锁
 spin_lock_irqsave(&nh->lock, flags);
 ret = notifier_chain_register(&nh->head, n);
// 解锁
 spin_unlock_irqrestore(&nh->lock, flags);
 return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);

4.1.2 撤销
/**
 * atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
 * @nh: Pointer to head of the atomic notifier chain
 * @n: Entry to remove from notifier chain
 *
 * Removes a notifier from an atomic notifier chain.
 *
 * Returns zero on success or %-ENOENT on failure.
 */
// 只是在基本通知块撤销操作前后加锁解锁进行保护
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
  struct notifier_block *n)
{
 unsigned long flags;
 int ret;
// 加锁
 spin_lock_irqsave(&nh->lock, flags);
 ret = notifier_chain_unregister(&nh->head, n);
// 解锁
 spin_unlock_irqrestore(&nh->lock, flags);
// 同步rcu, 等待一个grace period
 synchronize_rcu();
 return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);

4.1.3 原子回调

这个函数是在原子操作上下文中调用, 是不能阻塞的
 
int __kprobes atomic_notifier_call_chain(struct atomic_notifier_head *nh,
  unsigned long val, void *v)
{
 int ret;
// 禁止了抢占
 rcu_read_lock();
// 使用基本通知块回调
 ret = notifier_call_chain(&nh->head, val, v);
// 允许抢占
 rcu_read_unlock();
 return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_call_chain);

4.2 可阻塞通知块

4.2.1 登记
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
  struct notifier_block *n)
{
 int ret;
 /*
  * This code gets used during boot-up, when task switching is
  * not yet working and interrupts must remain disabled.  At
  * such times we must not call down_write().
  */
// 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本登记函数
// 不用处理信号灯, 因为此时是不能阻塞
 if (unlikely(system_state == SYSTEM_BOOTING))
  return notifier_chain_register(&nh->head, n);
// 使用信号灯进行同步, 可能阻塞
 down_write(&nh->rwsem);
// 基本登记函数
 ret = notifier_chain_register(&nh->head, n);
 up_write(&nh->rwsem);
 return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);

4.2.2 撤销
该函数是在进程处理过程中调用,可阻塞:
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
  struct notifier_block *n)
{
 int ret;
 /*
  * This code gets used during boot-up, when task switching is
  * not yet working and interrupts must remain disabled.  At
  * such times we must not call down_write().
  */
// 这是内核启动时就进行调用了, 虽然可能性很小, 直接执行基本撤销函数
// 不用处理信号灯, 因为此时是不能阻塞
 if (unlikely(system_state == SYSTEM_BOOTING))
  return notifier_chain_unregister(&nh->head, n);
// 使用信号灯进行同步, 可能阻塞
 down_write(&nh->rwsem);
// 基本撤销函数
 ret = notifier_chain_unregister(&nh->head, n);
 up_write(&nh->rwsem);
 return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_unregister);

4.2.3 回调

在进行上下文中调用, 可以阻塞:
 
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
  unsigned long val, void *v)
{
 int ret;
// 信号灯同步
 down_read(&nh->rwsem);
// 进行基本回调处理
 ret = notifier_call_chain(&nh->head, val, v);
 up_read(&nh->rwsem);
 return ret;
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);

4.3 原始通知块操作

和基本原始块操作完全相同:

int raw_notifier_chain_register(struct raw_notifier_head *nh,
  struct notifier_block *n)
{
 return notifier_chain_register(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
  struct notifier_block *n)
{
 return notifier_chain_unregister(&nh->head, n);
}
EXPORT_SYMBOL_GPL(raw_notifier_chain_unregister);

int raw_notifier_call_chain(struct raw_notifier_head *nh,
  unsigned long val, void *v)
{
 return notifier_call_chain(&nh->head, val, v);
}
EXPORT_SYMBOL_GPL(raw_notifier_call_chain);

4.4 SRCU通知块操作

4.4.1 登记
必须在进程的上下文中调用, 和blocking通知类似
int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
  struct notifier_block *n)
{
 int ret;
 /*
  * This code gets used during boot-up, when task switching is
  * not yet working and interrupts must remain disabled.  At
  * such times we must not call mutex_lock().
  */
 if (unlikely(system_state == SYSTEM_BOOTING))
  return notifier_chain_register(&nh->head, n);
 mutex_lock(&nh->mutex);
 ret = notifier_chain_register(&nh->head, n);
 mutex_unlock(&nh->mutex);
 return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_register);

4.4.2 撤销

必须在进程的上下文中调用, 和blocking通知类似
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,
  struct notifier_block *n)
{
 int ret;
 /*
  * This code gets used during boot-up, when task switching is
  * not yet working and interrupts must remain disabled.  At
  * such times we must not call mutex_lock().
  */
 if (unlikely(system_state == SYSTEM_BOOTING))
  return notifier_chain_unregister(&nh->head, n);
 mutex_lock(&nh->mutex);
 ret = notifier_chain_unregister(&nh->head, n);
 mutex_unlock(&nh->mutex);
 synchronize_srcu(&nh->srcu);
 return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_chain_unregister);

4.4.3 回调

在进程的上下文中调用, 可以阻塞:
int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
  unsigned long val, void *v)
{
 int ret;
 int idx;
// 使用srcu读锁来加锁
 idx = srcu_read_lock(&nh->srcu);
 ret = notifier_call_chain(&nh->head, val, v);
 srcu_read_unlock(&nh->srcu, idx);
 return ret;
}
EXPORT_SYMBOL_GPL(srcu_notifier_call_chain);
4.4.4 初始化
因为SRCU通知不能通过宏来初始化, 必须要专门定义一个初始化函数来初始化srcu的通知块参数:
void srcu_init_notifier_head(struct srcu_notifier_head *nh)
{
// 初始化锁
 mutex_init(&nh->mutex);
// 初始化scru结构
 if (init_srcu_struct(&nh->srcu) < 0)
  BUG();
 nh->head = NULL;
}
EXPORT_SYMBOL_GPL(srcu_init_notifier_head);

5. 应用

下面以连接跟踪中的事件处理来说明, 就是通过通知块来实现的:
初始化定义一个静态的原子通知头参数:
/* net/ipv4/netfilter/ip_conntrack_core.c */
ATOMIC_NOTIFIER_HEAD(ip_conntrack_chain);

连接跟踪的事件处理函数, 实际就是通知回调函数:
/* include/linux/netfilter_ipv4/ip_conntrack.h */
// 连接事件处理
static inline void ip_conntrack_event(enum ip_conntrack_events event,
          struct ip_conntrack *ct)
{
// 判断连接是否合法
 if (is_confirmed(ct) && !is_dying(ct))
// 调用原子通知回调函数, 执行登记的回调函数
  atomic_notifier_call_chain(&ip_conntrack_chain, event, ct);
}

连接跟踪相关事件的登记和撤销:
/* include/linux/netfilter_ipv4/ip_conntrack.h */
// 就是标准的原子通知登记和撤销函数
static inline int ip_conntrack_register_notifier(struct notifier_block *nb)
{
 return atomic_notifier_chain_register(&ip_conntrack_chain, nb);
}
static inline int ip_conntrack_unregister_notifier(struct notifier_block *nb)
{
 return atomic_notifier_chain_unregister(&ip_conntrack_chain, nb);
}
在net/ipv4/netfilter/ip_conntrack_netlink.c中定义了netlink通知回调函数:
static struct notifier_block ctnl_notifier = {
 .notifier_call = ctnetlink_conntrack_event,
};
......
 ret = ip_conntrack_register_notifier(&ctnl_notifier);
......
这样, 在任何地方ip_conntrack_event()函数时就会调用到该netlink通知回调函数.

6. 结论

notify通知块处理是内核中的一种回调处理机制, 一般不是直接调用原始的通知处理函数, 而是根据
要完成的功能, 如事件回调, 重启回调等重新定义新的处理函数, 然后在必要的地方调用相应的回调
包装函数就可以实现回调。
阅读(1192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~