系统中出于不同的目的可能会有多个网络设备,内核需要能够合理的管理这些已经注册的net_device对象。这篇笔记就来介绍这方面的内容。
数据结构
系统将所有已注册的网络设备从三个维度上组织为一个链表和两个哈希表:
- 链表dev_base_head是按照设备被注册的顺序维护的,所以后面称该表为“设备表”;
- 哈希表dev_name_head是以设备的名字为键值维护的,所以后面称该表为“名字表”;
- 哈希表dev_index_head哈希表是以设备的索引(即if_index)维护的,所以后面称该表为“索引表”。
表头定义
上面的三个表的表头是在网络命名空间中定义的,如下:
struct net
{
...
struct list_head dev_base_head;
struct hlist_head *dev_name_head;
struct hlist_head *dev_index_head;
...
}
表项定义
每个网络设备也需要有相关的字段才能将网络设备对象接入全局的表中:
struct net_device
{
...
struct hlist_node name_hlist;
struct list_head dev_list;
struct hlist_node index_hlist;
...
}
组织结构
清除了数据结构定义,下面来看看内核利用这些数据结构对已注册网络设备对象的组织:
设备对象表
dev_base_head链表的内存布局如下所示:
为了内存对齐,struct net_device结构在分配时有可能在其首部会有一段padding,而全局的dev_base_head指向的是net_device结构的第一个成员的位置。
PS:上图来自于《深入理解Linux网络技术内幕》,图片比较旧,所贴代码版本中dev_base_head就是图中的dev_base;dev_list就是图中的next指针。
名字表与索引表
dev_name_head和dev_index_head哈希表的内存布局如下图所示:
之所以维护dev_name_head和dev_index_head两个哈希表完全是为了查找方便,因为用户空间基本上都是以网络设备名字为标识操作网络设备的。
锁
作为全局的数据结构,当然需要锁来保护了,内核使用读写锁来对全局的链表和哈希表进行保护。因为现实情况中,添加和移除网络设备这种写操作频率是比较低的,大多数情况下都是查询即读操作,所以使用读写锁更加高效。
DEFINE_RWLOCK(dev_base_lock);
EXPORT_SYMBOL(dev_base_lock);
添加和移除net_device
在网络设备的注册流程中,会将注册的网络设备加到这三个设备表中;同样的,在网络设备的注销过程中,会将注销的网络设备从这三个设备表中移除。添加和移除分别通过调用list_netdevice()和unlist_netdevice()来完成,代码如下:
static int list_netdevice(struct net_device *dev)
{
struct net *net = dev_net(dev);
ASSERT_RTNL();
write_lock_bh(&dev_base_lock);
// 添加到设备对象表的末尾
list_add_tail(&dev->dev_list, &net->dev_base_head);
// 添加到设备名字表中
hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));
// 添加到设备索引表中
hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
write_unlock_bh(&dev_base_lock);
return 0;
}
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);
}
查询net_device
根据名字、接口索引查询网络设备时,遍历的是对应的哈希表即可;根据其它过滤条件(比如IP地址)查找网络设备时遍历的都是设备对象表,代码如下:
struct net_device *dev_get_by_index(struct net *net, int ifindex)
{
struct net_device *dev;
read_lock(&dev_base_lock);
// 查找设备索引表
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;
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);