Linux 网络内核代码中使用了通知链(Notification Chains)来使相关的子系统对感兴趣的事件作出反应。下面从头介绍一下通知链的使用。
  数据结构定义
  通知链使用的数据结构如下:
  struct notifier_block {
  int (*notifier_call)(struct notifier_block *, unsigned long, void *);
  struct notifier_block *next;
  int priority;
  };
  其 中notifier_call 是通知链要执行的函数指针,后面会介绍它的参数和返回值;next 用来连接下一个notifier_block;priority是这个通知的优先级,同一条链上的notifier_block是按优先级排列的,一般都 初始化为0,这样按照注册到通知链上的顺序执行。
  内核代码中一般把通知链命名成xxx_chain,xxx_notifier_chain 这种形式的变量名
  注册一个notifier_block
  在通知链上增加一个notifier_block是用notifier_chain_register() 函数完成的
  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;
  }
  注销一个notifier_block
  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;
  }
  调用通知链上的函数
  当需要通知特定的事件发生时,通过调用notifier_call_chain()函数来完成。注意,通知链上的回调函数是在调用notifier_call_chain()的上下文中执行的。
static int __kprobes notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference(*nl);
while (nb && nr_to_call) {
next_nb = rcu_dereference(nb->next);
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
  参数nl是通知链的名称,val表示事件类型,例如NETDEV_REGISTER,v用来指向通知链上的函数执行时需要用到的参数,一般不同的通知链,参 数类型也不一样,例如当通知一个网卡被注册时,v指向net_device结构,nr_to_call 表示准备多通知几个,-1表示整条链上的都调用,nr_calls 非空的话,返回通知了几个。
  每个被执行的notifier_block回调函数一般返回下面几个可能的值:
  NOTIFY_DONE:表示对相关的事件类型不关心
  NOTIFY_OK:顺利执行
  NOTIFY_BAD:执行有错
  NOTIFY_STOP:停止执行后面的回调函数
  NOTIFY_STOP_MASK:停止执行的掩码
  关于这几个值的定义,请参考include/linux/notifier.h。
  notifier_call_chain() 函数把后一个被调的回调函数的返回值作为它的返回值。
  内核网络代码中对通知链的使用
  明白了通知链的原理后,我们看一下内核网络中使用的一些通知链
  inetaddr_chain ipv4地址变动时的通知链
  netdev_chain 网络设备状态变动时通知链
  网络代码中对通知链的调用一般都有一个包装函数,例如对netdev_chain的注册是由register_netdevice_notifier() 函数来完成
int register_netdevice_notifier(struct notifier_block *nb)
{
struct net_device *dev;
struct net_device *last;
struct net *net;
int err;
rtnl_lock();
err = raw_notifier_chain_register(&netdev_chain, nb);
if (err)
goto unlock;
if (dev_boot_phase)
goto unlock;
for_each_net(net) {
for_each_netdev(net, dev) {
err = nb->notifier_call(nb, NETDEV_REGISTER, dev);
err = notifier_to_errno(err);
if (err)
goto rollback;
if (!(dev->flags & IFF_UP))
continue;
nb->notifier_call(nb, NETDEV_UP, dev);
}
}
unlock:
rtnl_unlock();
return err;
rollback:
last = dev;
for_each_net(net) {
for_each_netdev(net, dev) {
if (dev == last)
break;
if (dev->flags & IFF_UP) {
nb->notifier_call(nb, NETDEV_GOING_DOWN, dev);
nb->notifier_call(nb, NETDEV_DOWN, dev);
}
nb->notifier_call(nb, NETDEV_UNREGISTER, dev);
}
}
raw_notifier_chain_unregister(&netdev_chain, nb);
goto unlock;
}