Linux 網路核心程式碼中使用了通知鏈(Notification Chains)來使相關的子系統對感興趣的事件作出反應。下面從頭介紹一下通知鏈的使用。
資料結構定義
通知鏈使用的資料結構如下:
1 2 3 4 5 |
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() 函式完成的
1 2 3 4 5 6 7 8 9 10 11 |
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
1 2 3 4 5 6 7 8 9 10 11 |
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()的上下文中執行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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() 函式來完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
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; } |
這個函式看似複雜,其實就主要作兩件事:
1) 把引數struct notifier_block *nb 註冊到netdev_chain通知鏈上去
2) 系統中所有已經被註冊過或啟用的網路裝置的事件都要被新增的這個通知的回撥函式重新呼叫一遍,這樣讓裝置更新到一個完整的狀態。
一個例子
在 路由子系統初始化時,系統會呼叫ip_fib_init() 函式,ip_fib_init() 中會註冊一個回撥函式到netdev_chain通知鏈,這樣當別的子系統通知netdev_chain上有特定的事件型別發生時,路由子系統的相應回撥 函式就可以作一些反應。
1 2 3 4 5 6 |
void __init ip_fib_init(void) { ... ... register_netdevice_notifier(&fib_netdev_notifier); ... ... } |
看一下fib_netdev_notifier的定義:
1 2 3 |
static struct notifier_block fib_netdev_notifier = { .notifier_call =fib_netdev_event, }; |
fib_netdev_notifier就是一個struct notifier_block,其中.priority預設初始化為0,.next由註冊時設定。
再來大致看一下函式fib_netdev_event()做一些什麼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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); } endfor_ifa(in_dev); #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; } |