Linux 網絡通知鏈

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;
}
這個函數看似複雜,其實就主要作兩件事:
1)    把參數struct notifier_block *nb 註冊到netdev_chain通知鏈上去
2)    系統中所有已經被註冊過或激活的網絡設備的事件都要被新增的這個通知的回調函數重新調用一遍,這樣讓設備更新到一個完整的狀態。

一個例子
在路由子系統初始化時,系統會調用ip_fib_init() 函數,ip_fib_init() 中會註冊一個回調函數到netdev_chain通知鏈,這樣當別的子系統通知netdev_chain上有特定的事件類型發生時,路由子系統的相應回調函數就可以作一些反應。
void __init ip_fib_init(void)
{
    ... ...
    register_netdevice_notifier(&fib_netdev_notifier);
    ... ...
}

看一下fib_netdev_notifier的定義:
static struct notifier_block fib_netdev_notifier = {
    .notifier_call =fib_netdev_event,
};
fib_netdev_notifier就是一個struct notifier_block,其中.priority默認初始化爲0,.next由註冊時設定。

再來大致看一下函數fib_netdev_event()做一些什麼
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;
}

 原文地址http://hi.baidu.com/mczyh/blog/item/80f4200098588c087bec2ce8.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章