这篇笔记分析了设备接口层的初始化代码实现,其中有些数据结构的详细情况会在后续相关的笔记中进一步说明。
开机初始化: net_dev_init()
// 对应系统参数/proc/sys/net/core/dev_weight
int weight_p __read_mostly = 64; /* old backlog weight */
/*
* This is called single threaded during boot, so no need
* to take the rtnl semaphore.
*/
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
// 全局变量dev_boot_phase确保只初始化一次
BUG_ON(!dev_boot_phase);
// 在procfs中创建维测信息节点
if (dev_proc_init())
goto out;
// 在sysfs中注册一个名为"net"的设备类
if (netdev_kobject_init())
goto out;
// 初始化一个哈希表,该哈希表会用于后续的数据包向上(如IP层)分发过程
INIT_LIST_HEAD(&ptype_all);
for (i = 0; i < PTYPE_HASH_SIZE; i++)
INIT_LIST_HEAD(&ptype_base[i]);
// 网络命名空间级别的初始化
if (register_pernet_subsys(&netdev_net_ops))
goto out;
// 每个CPU上分配一个接收队列,该队列将用于后续的非NAPI接收过程
for_each_possible_cpu(i) {
struct softnet_data *queue;
queue = &per_cpu(softnet_data, i);
// 初始化非NAPI模式的数据包接收队列
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue = NULL;
// 初始化NAPI模式的轮询队列
INIT_LIST_HEAD(&queue->poll_list);
// 为了将NAPI和非NAPI两种接收流程归一化,设备接口层使用backlog变量将非NAPI方式
// 改造成了NAPIF方式,这里指定其poll()回调轮询配额
queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
}
// 标识boot阶段完成
dev_boot_phase = 0;
/* The loopback device is special if any other network devices
* is present in a network namespace the loopback device must
* be present. Since we now dynamically allocate and free the
* loopback device ensure this invariant is maintained by
* keeping the loopback device as the first device on the
* list of network devices. Ensuring the loopback devices
* is the first device that appears and the last network device
* that disappears.
*/
// 确保在每个网络命名空间中,lo是第一个注册,最后一个销毁的设备
if (register_pernet_device(&loopback_net_ops))
goto out;
// 注册一个网络命名空间删除回调,当网络命名空间被销毁时,将该网络命名空间
// 中的所有未去注册设备全部更改到默认的initnet命名空间中
if (register_pernet_device(&default_device_ops))
goto out;
// 注册网络收发软中断
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
// 监测CPU热插拔事件
hotcpu_notifier(dev_cpu_callback, 0);
// 路由相关初始化
dst_init();
// 设备接口层多播相关初始化
dev_mcast_init();
rc = 0;
out:
return rc;
}
subsys_initcall(net_dev_init);
namespace级别初始化: netdev_init()
完成namespace中
/* Initialize per network namespace state */
static int __net_init netdev_init(struct net *net)
{
// 初始化网络设备对象通用列表
INIT_LIST_HEAD(&net->dev_base_head);
// 创建以网络设备名字为key的网络设备对象哈希表
net->dev_name_head = netdev_create_hash();
if (net->dev_name_head == NULL)
goto err_name;
// 创建以网络设备索引为key的网络设备对象哈希表
net->dev_index_head = netdev_create_hash();
if (net->dev_index_head == NULL)
goto err_idx;
return 0;
err_idx:
kfree(net->dev_name_head);
err_name:
return -ENOMEM;
}
网络命名空间net
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;
}
net中设计了三个链表来维护系统中所有注册的网络设备对象:
- dev_base_head: 通用的网络设备对象双向链表;
- dev_name_head: 以网络设备名字为key的网络设备对象哈希表;
- dev_index_head: 以网络设备索引为key的网络设备对象哈希表;
procfs文件节点
设备接口层初始化过程中,通过dev_proc_init()在/proc/net目录下创建了三个文件用来暴露一些信息。
- dev文件
从该文件可以查看基于网络设备的数据包统计信息。
- ptype文件
从该文件可以查看当前内核由哪些函数会处理哪些类型的L2层帧。如下第一行表示packet_rcv_spkt()函数处理eth0上的所有类型帧;第二行表示ip_rcv()函数处理所有接口上类型为ETH_P_IP(0x0800)的帧;第三行表示arp_rcv()函数处理所有接口上类型为ETH_P_ARP(0x0806)的帧;第二行表示ipv6_rcv()函数处理所有接口上类型为ETH_P_IPV6(0x86dd)的帧。
- softnet_stat文件
sysfs文件节点
在/sys/class目录下创建net设备类,后续注册的每个网络设备在该目录下都会创建一个软链接,指向对应的devices目录。
每个设备的目录下包含了大量针对该网络设备的控制节点文件。