裝置介面層之net_device結構的管理
核心中所有的網絡卡裝置,包括所有的真實網絡卡,以及絕大多數的虛擬網絡卡,要想工作,前提是例項化並向核心註冊一個struct net_device結構,該結構就是軟體層面對網絡卡的抽象。系統中出於不同的目的會有多個網絡卡共存,核心需要能夠合理的管理這些註冊的網絡卡,這篇筆記就來介紹這方面的內容。
1. 資料結構
系統將所有已註冊的net_device結構從三個維度上分別組織為一個連結串列和兩個雜湊表:連結串列dev_base_head是按照裝置被註冊的順序維護的;dev_name_head雜湊表是以裝置的名字為key維護的;dev_index_head雜湊表是以裝置的索引(全域性唯一)為key維護的。
1.1 網路名稱空間中相關資料成員
可以想到,這三個資料結構在不同網路名稱空間中是單獨維護的。
struct net
{
...
struct list_head dev_base_head;
struct hlist_head *dev_name_head;
struct hlist_head *dev_index_head;
...
}
1.2 net_device中相關資料成員
具體的每個網路裝置也需要有相關的欄位用於將裝置接入全域性的資料結構中。
struct net_device
{
...
struct hlist_node name_hlist;
struct list_head dev_list;
struct hlist_node index_hlist;
...
}
1.3 記憶體佈局
有了這些資料結構成員,就可以來看看核心到底是如何組織這些已經註冊了的net_device結構的。
1.3.1 dev_base_head
dev_base_head連結串列的記憶體佈局如下圖所示:
為了記憶體對齊,struct net_device結構在分配時有可能在其首部會有一段padding,而全域性的dev_base_head指向的是net_device結構的第一個成員的位置。
PS:上圖來自於《深入理解Linux網路技術內幕》,圖片比較舊,所貼程式碼版本中dev_base_head就是圖中的dev_base;dev_list就是圖中的next指標。
1.3.1 dev_name_head
dev_name_head和dev_index_head雜湊表的記憶體佈局如下圖所示:
之所以維護dev_name_head和dev_index_head兩個雜湊表完全是為了查詢方便,應為使用者空間的程式、工具基本上都是以名字操作網路裝置的。
1.4 鎖
作為全域性的資料結構,當然需要鎖來保護了,核心使用讀寫鎖來對全域性的連結串列和雜湊表進行保護。使用讀寫鎖是合理的,因為實際過程中,新增和移除裝置這種寫操作是比較少見的,大多數情況下都是查詢即讀操作,使用讀寫鎖效能是最好的。
DEFINE_RWLOCK(dev_base_lock);
EXPORT_SYMBOL(dev_base_lock);
2. 網路裝置的新增和刪除
可以想象的到,向連結串列中新增網路裝置一定是發生在註冊過程中,即register_netdevice()中,而刪除網路裝置一定是發生在去註冊過程中,即unregister_netdevice()中,在這兩個流程中,分別會呼叫list_netdevice()和unlist_netdevice()來維護連結串列結構,程式碼如下:
/* Device list insertion */
static int list_netdevice(struct net_device *dev)
{
struct net *net = dev_net(dev);
ASSERT_RTNL();
//獲取寫鎖
write_lock_bh(&dev_base_lock);
//將新設別新增到dev_base_head的末尾
list_add_tail(&dev->dev_list, &net->dev_base_head);
//加入到dev_name_list
hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));
//加入到dev_index_list
hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
//操作完成,釋放寫鎖
write_unlock_bh(&dev_base_lock);
return 0;
}
/* Device list removal */
static void unlist_netdevice(struct net_device *dev)
{
ASSERT_RTNL();
//過程是list_netdevice()的逆操作
write_lock_bh(&dev_base_lock);
list_del(&dev->dev_list);
hlist_del(&dev->name_hlist);
hlist_del(&dev->index_hlist);
write_unlock_bh(&dev_base_lock);
}
3. 網路裝置的查詢
根據名字、介面索引查詢網路裝置時,遍歷的是對應的雜湊表,其它根據地址等資訊查詢網路裝置時都是遍歷的dev_base_head連結串列,程式碼如下:
struct net_device *dev_get_by_index(struct net *net, int ifindex)
{
struct net_device *dev;
//持有讀鎖
read_lock(&dev_base_lock);
//查詢dev_index_head表查詢
dev = __dev_get_by_index(net, ifindex);
//找到指定裝置,這裡會增加對該裝置的引用計數
if (dev)
dev_hold(dev);
//釋放讀鎖
read_unlock(&dev_base_lock);
return dev;
}
EXPORT_SYMBOL(dev_get_by_index);
struct net_device *dev_get_by_name(struct net *net, const char *name)
{
struct net_device *dev;
//操作類似dev_get_by_index()
read_lock(&dev_base_lock);
dev = __dev_get_by_name(net, name);
if (dev)
dev_hold(dev);
read_unlock(&dev_base_lock);
return dev;
}
EXPORT_SYMBOL(dev_get_by_name);