内核中所有的网卡设备,包括所有的真实网卡,以及绝大多数的虚拟网卡,要想工作,前提是实例化并向内核注册一个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);