Linux 內核廣泛使用通知鏈實現模塊間松耦合的事件通知。網絡子系統通過 netdev_chain、inetaddr_chain 等通知鏈,讓路由、鄰居、防火牆等模塊對網卡狀態變更、IP 地址異動做出反應。本文從數據結構、註冊/註銷、調用流程、實戰案例四個維度深度剖析。
struct notifier_block {
int (*notifier_call)(struct notifier_block *nb, unsigned long val, void *v);
struct notifier_block *next;
int priority; // 優先級,高者先執行,默認 0 按註冊順序
};
notifier_call:回調函數,參數 val 為事件類型(如 NETDEV_REGISTER),v 為事件數據(如 struct net_device*)NOTIFY_DONE(0) 不關心、NOTIFY_OK 處理成功、NOTIFY_BAD 出錯、NOTIFY_STOP 停止傳播// 註冊:按優先級降序插入鏈表頭部
static int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n) {
while (*nl) {
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) {
if (*nl == n) {
rcu_assign_pointer(*nl, n->next);
return 0;
}
nl = &(*nl)->next;
}
return -ENOENT;
}
使用 RCU(Read-Copy-Update)保證無鎖讀取並發安全。
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;
}
nr_to_call=-1 表示遍歷整條鏈| 通知鏈 | 定義位置 | 典型事件 | 典型監聽者 |
|---|---|---|---|
| netdev_chain | net/core/dev.c | NETDEV_REGISTER/UNREGISTER/UP/DOWN/CHANGE/CHANGEMTU | 路由 (fib)、鄰居 (arp/ndisc)、橋接、防火牆、bonding/team |
| inetaddr_chain | net/ipv4/devinet.c | NETDEV_UP/DOWN (IPv4 地址增刪) | IGMP、路由、策略路由 |
// net/ipv4/fib_frontend.c
void __init ip_fib_init(void) {
register_netdevice_notifier(&fib_netdev_notifier);
}
static struct notifier_block fib_netdev_notifier = {
.notifier_call = fib_netdev_event,
};
int register_netdevice_notifier(struct notifier_block *nb) {
rtnl_lock();
int err = raw_notifier_chain_register(&netdev_chain, nb);
if (err) goto unlock;
// 關鍵:對「已存在」的網卡重放事件,讓新註冊者同步當前狀態
if (!dev_boot_phase) {
for_each_netdev(net, dev) {
nb->notifier_call(nb, NETDEV_REGISTER, dev);
if (dev->flags & IFF_UP)
nb->notifier_call(nb, NETDEV_UP, dev);
}
}
unlock:
rtnl_unlock();
return err;
}
static int fib_netdev_event(struct notifier_block *this, unsigned long event, void *ptr) {
struct net_device *dev = ptr;
struct in_device *in_dev = __in_dev_get_rtnl(dev);
if (event == NETDEV_UNREGISTER) {
fib_disable_ip(dev, 2); // 刪除該設備所有路由
return NOTIFY_DONE;
}
if (!in_dev) return NOTIFY_DONE;
switch (event) {
case NETDEV_UP:
for_ifa(in_dev) fib_add_ifaddr(ifa); // 添加接口路由
#ifdef CONFIG_IP_ROUTE_MULTIPATH
fib_sync_up(dev);
#endif
rt_cache_flush(-1);
break;
case NETDEV_DOWN:
fib_disable_ip(dev, 0); // 刪除接口路由
break;
case NETDEV_CHANGEMTU:
case NETDEV_CHANGE:
rt_cache_flush(0); // 刷新路由緩存
break;
}
return NOTIFY_DONE;
}
NOTIFY_STOP 允許關鍵模塊攔截事件(如防火牆丟包)struct notifier_block nb = { .notifier_call = my_handler };register_netdevice_notifier(&nb) 或 register_inetaddr_notifier(&nb)unregister_*_notifier