源码解析
这里我们就介绍两个源文件:
1 | ./unikraft/plat/drivers/include/virtio/virtio_net.h |
头文件virtio_net.h
这部分其实和unikraft本身关系不大,主要用于了解virtio网络设备相关背景
上来首先介绍了virtio的feature bitmap.
Feature bits
定义了 Guest 和 Host 支持的功能,例如 VIRTIO_NET_F_CSUM bit 表示网络设备是否支持 checksum offload。feature bits 机制提供了未来扩充功能的灵活性,以及兼容旧设备的能力。
这里是涉及到网络环境涉及的超参数宏
- VIRTIO_NET_F_CSUM (0) 设备进行部分校验数据包。“checksum offload”是现代网卡的常见功能。
- VIRTIO_NET_F_GUEST_CSUM (1) 驱动进行部分校验数据包。
- VIRTIO_NET_F_CTRL_GUEST_OFFLOADS (2) 没有代码使用过,offload配置通道,好像和 XDP LRO/CSUM 相关。Control channel offloads reconfiguration support.Dynamic offload configuration。
- VIRTIO_NET_F_MAC (5) 设备有指定的MAC
- VIRTIO_NET_F_GUEST_TSO4 (7) 驱动支持TSOv4
- VIRTIO_NET_F_GUEST_TSO6 (8) 驱动支持 TSOv6
- VIRTIO_NET_F_GUEST_ECN (9) 驱动支持带有ECN 的 TSO
- VIRTIO_NET_F_GUEST_UFO (10) 驱动支持 UFO. VIRTIO_NET_F_HOST_TSO4 (11) 设备支持 TSOv4.
- VIRTIO_NET_F_HOST_TSO6 (12) 设备支持 TSOv6.
- VIRTIO_NET_F_HOST_ECN (13) 设备支持有 ECN 的 TSO
- VIRTIO_NET_F_HOST_UFO (14) 设备支持 UFO
- VIRTIO_NET_F_MRG_RXBUF (15) 驱动支持 Merge Buffer
- VIRTIO_NET_F_STATUS (16) 支持Configuration status 域,config change有关
- VIRTIO_NET_F_CTRL_VQ (17) 支持Control queue
- VIRTIO_NET_F_CTRL_RX (18) 支持 通过Control queue 设置 RX mode,如混杂模式,组播,广播等
- VIRTIO_NET_F_CTRL_VLAN (19) Control queue 设置vlan过滤表
- VIRTIO_NET_F_GUEST_ANNOUNCE(21) 驱动支持发送 gratuitous packets(Gratuitous ARP)
- VIRTIO_NET_F_MQ(22) 设备支持多队列,并且RR方式接收报文
- VIRTIO_NET_F_CTRL_MAC_ADDR(23) 支持通过Control Queue设置MAC地址
里面涉及的概念补充如下:
MTU:最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
ECN:显式拥塞通知Explicit Congestion Notification(ECN)是TCP/IP协议的扩展,在RFC 3168 (2001) 中进行了定义。ECN支持端到端的网络拥塞通知。
virtio_net_config
这个数据结构主要管理configuration布局
1 | struct virtio_net_config { |
后面都是定义一些前后端交互时报文的限制条件,我们在具体实现中进行讨论
2.2 virtio_net.c
这部分主要解析和uknetdev的关系
在uknetdev中曾经定义了网络驱动设备的接收和传输队列,但是没有实现,在virtio_net.c进行了明确定义。
1 | /** |
1 | /** |
- virtio_net_device
1 | struct virtio_net_device { |
- virtio_netdev_recv 这里定义了一个rx_one的函数,提前放上来说
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69static int virtio_netdev_recv(struct uk_netdev *dev __unused,
struct uk_netdev_rx_queue *queue,
struct uk_netbuf **pkt)
{
int status = 0x0;
int rc = 0;
UK_ASSERT(dev && queue);
UK_ASSERT(pkt);
/* Queue interrupts have to be off when calling receive */
UK_ASSERT(!(queue->intr_enabled & VTNET_INTR_EN));
rc = virtio_netdev_rxq_dequeue(queue, pkt);
if (unlikely(rc < 0)) {
uk_pr_err("Failed to dequeue the packet: %d\n", rc);
goto err_exit;
}
// 检验报文接收状态
status |= (*pkt) ? UK_NETDEV_STATUS_SUCCESS : 0x0;
status |= virtio_netdev_rx_fillup(queue, (queue->nb_desc - rc), 1);
/* Enable interrupt only when user had previously enabled it */
if (queue->intr_enabled & VTNET_INTR_USR_EN_MASK) {
/* Need to enable the interrupt on the last packet */
rc = virtqueue_intr_enable(queue->vq);
if (rc == 1 && !(*pkt)) {
/**
* Packet arrive after reading the queue and before
* enabling the interrupt
*/
// 报文到达时间在开启中断之前到达,在执行read操作以后,再读
rc = virtio_netdev_rxq_dequeue(queue, pkt);
if (unlikely(rc < 0)) {
uk_pr_err("Failed to dequeue the packet: %d\n",
rc);
goto err_exit;
}
status |= UK_NETDEV_STATUS_SUCCESS;
/*
* Since we received something, we need to fillup
* and notify
*/
status |= virtio_netdev_rx_fillup(queue,
(queue->nb_desc - rc),
1);
/* Need to enable the interrupt on the last packet */
rc = virtqueue_intr_enable(queue->vq);
status |= (rc == 1) ? UK_NETDEV_STATUS_MORE : 0x0;
} else if (*pkt) {
/* When we originally got a packet and there is more */
status |= (rc == 1) ? UK_NETDEV_STATUS_MORE : 0x0;
}
} else if (*pkt) {
/**
* For polling case, we report always there are further
* packets unless the queue is empty.
*/
status |= UK_NETDEV_STATUS_MORE;
}
return status;
err_exit:
UK_ASSERT(rc < 0);
return rc;
}
virtio_netdev操作
这里函数主要分成两类,一类是virtio_net_*主要是virtio层面设备的管理,信息获取等
1 | static int virtio_net_drv_init(struct uk_alloc *drv_allocator); |
这里就举一个添加设备的例子
1 | static int virtio_net_add_dev(struct virtio_dev *vdev) |
virtio_netdev_*
直接和netdev扯上关系的部分的函数
1 | static int virtio_netdev_configure(struct uk_netdev *n, |
这些函数,最终都要绑定在uk_netdev_ops
netdev_core.h中的定义如下
1 | /** |
在virtio_net.c中的实现为
1 | static const struct uk_netdev_ops virtio_netdev_ops = { |
virtio_netdev_feature_negotiate:网络设备bus相关feature map,抽取部分关键代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45/**
* Read device feature bits, and write the subset of feature bits
* understood by the OS and driver to the device. During this step the
* driver MAY read (but MUST NOT write) the device-specific
* configuration fields to check that it can support the device before
* accepting it.
*/
host_features = virtio_feature_get(vndev->vdev);
/**
* Hardware address
* NOTE: For now, we require a provided hw address. In principle,
* we could generate one if none was given.
* 设置网络设备的mac硬件地址
*/
VIRTIO_FEATURE_SET(drv_features, VIRTIO_NET_F_MAC);
/**
* MTU information (needed)
*/
VIRTIO_FEATURE_SET(drv_features, VIRTIO_NET_F_STATUS);
/**
* Gratuitous ARP
* NOTE: We tell that we will do gratuitous ARPs ourselves.
* ARP信息,ARP就是根据IP地址解析物理地址
*/
VIRTIO_FEATURE_SET(drv_features, VIRTIO_NET_F_GUEST_ANNOUNCE);
/**
* Partial checksumming
* NOTE: This enables sending and receiving of packets marked with
* VIRTIO_NET_HDR_F_DATA_VALID and VIRTIO_NET_HDR_F_NEEDS_CSUM
* 校验和相关特征值设置
*/
VIRTIO_FEATURE_SET(drv_features, VIRTIO_NET_F_CSUM);
VIRTIO_FEATURE_SET(drv_features, VIRTIO_NET_F_GUEST_CSUM);
/**
* Announce our enabled driver features back to the backend device
* 将feature bits映射到具体设备上
*/
vndev->vdev->features = drv_features;
virtio_feature_set(vndev->vdev, vndev->vdev->features);virtio_netdev_configure:主要是对virtio netdev里的配置初始化,主要是接收和发送队列数设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* netdev_core.h, nb = number
* A structure used to configure a network device.
*/
struct uk_netdev_conf {
uint16_t nb_rx_queues;
uint16_t nb_tx_queues;
};
static int virtio_netdev_configure(struct uk_netdev *n,
const struct uk_netdev_conf *conf)
{
int rc = 0;
struct virtio_net_device *vndev;
UK_ASSERT(n);
UK_ASSERT(conf);
vndev = to_virtionetdev(n);
rc = virtio_netdev_rxtx_alloc(vndev, conf); // 给virtio netdev设置接受发送队列数上限
if (rc < 0) {
uk_pr_err("%p: Failed to initialize rx and tx rings: %d\n",
n, rc);
}
/* Initialize the count of the virtio-net device */
vndev->rx_vqueue_cnt = 0;
vndev->tx_vqueue_cnt = 0;
return rc;
}uk_netdev_rx_queue
初始化接收队列
1 |
|
这里涉及到了virtio_netdev_vqueue_setup函数。
这个函数的主要目的是初始化队列virtio_vqueue_setup(vqueue)中的,接收和发送队列都需要调用这个函数。完成的任务包括为队列绑定callback,队列描述符的初始化,rxqs成员变量初始化。
在绑定callback时调用了virtio_netdev_recv_done函数,这个函数主要用于终止队列ring的打断virtqueue_intr_disable,并触发uk_netdev_drv_rx_event,表示完成接收事件后的动作
1 | /** |
uk_netdev_tx_queue和这个读队列的操作基本一致,就不再赘述
- virtio_net_start:这个函数就是禁用接收和发送队列的中断选项
1 | static int virtio_net_start(struct uk_netdev *n) |
- virtio_net_rx_intr_enable, virtio_net_rx_intr_disable:管理接收发送队列的enable与disable
it - virtio_net_info_get:获取virtio_netdev的最大接收传输队列,对齐标志以及feature bit。
- virtio_net_promisc_get:是否promisc模式
- virtio_net_mac_get:获取设备硬件mac地址
- virtio_net_mtu_get:获取网络最大传输单元
- virtio_netdev_rxq_info_get:获取接收队列信息,主要是描述符
1 | static int virtio_netdev_rxq_info_get(struct uk_netdev *dev, |