How does QEMU notify Guest the nic link status changed

事情的起因是同事问到,Guest内部virtio网卡的链路状态是如何改变的,于是从virtio网卡入手,梳理了一下QEMU的网卡链路状态的相关代码。

virtio_net_set_link_status

搜索相关代码,能够找到设置virtio-net链路状态的函数virtio_net_set_link_status,该函数会判断如果链路状态发生了改变,则调用virtio_notify_config通知Guest内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void virtio_net_set_link_status(NetClientState *nc)
{
VirtIONet *n = qemu_get_nic_opaque(nc);
VirtIODevice *vdev = VIRTIO_DEVICE(n);
uint16_t old_status = n->status;

if (nc->link_down)
n->status &= ~VIRTIO_NET_S_LINK_UP;
else
n->status |= VIRTIO_NET_S_LINK_UP;

if (n->status != old_status)
virtio_notify_config(vdev);

virtio_net_set_status(vdev, vdev->status);
}

在初始化virtio-net的NetClientInfo的时候link_status_changed函数设置为virtio_net_set_link_status

1
2
3
4
5
6
7
8
9
static NetClientInfo net_virtio_info = {
.type = NET_CLIENT_DRIVER_NIC,
.size = sizeof(NICState),
.can_receive = virtio_net_can_receive,
.receive = virtio_net_receive,
.link_status_changed = virtio_net_set_link_status,
.query_rx_filter = virtio_net_query_rxfilter,
.announce = virtio_net_announce,
};

link_status_changed

从函数命名上可以看出,该函数在链路状态发生改变后被调用。

该函数有2个调用处:qmp_set_linkqemu_del_net_client

qmp开头的函数很明显,都是QMP(QEMU Machine Protocol)相关的命令对应的实现函数,一般场景下都是用户通过HMP(Human Monitor Interface)接口去手动设置的。

所以,一种显然的调用方式为:通过QMP,主动设置链路状态。

除此之外,在后端为vhost-user的场景下,QEMU代码有特殊处理:

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
static void net_vhost_user_event(void *opaque, QEMUChrEvent event)
{
......
switch (event) {
case CHR_EVENT_OPENED:
......
qmp_set_link(name, true, &err);
......
break;
case CHR_EVENT_CLOSED:
/* a close event may happen during a read/write, but vhost
* code assumes the vhost_dev remains setup, so delay the
* stop & clear to idle.
* FIXME: better handle failure in vhost code, remove bh
*/
if (s->watch) {
......
aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
}
break;
case CHR_EVENT_BREAK:
case CHR_EVENT_MUX_IN:
case CHR_EVENT_MUX_OUT:
/* Ignore */
break;
}

if (err) {
error_report_err(err);
}
}

即后端为vhost-user时,当对应的socket连接事件发生,主动从代码中调用qmp_set_link,设置网卡的链路状态为UP;当对应socket断开事件发生,异步调用一次chr_closed_bh,该函数中进行后端断开对应的清理操作,包括调用qmp_set_link将网卡的链路状态设置为DOWN。

qemu_del_net_client

这个函数主要是清理NetClientState的相关联资源,需要注意的是,这里调用link_status_changed并不是设置当前被清理的NetClientState对应设备的状态,而是设置peer的状态(QEMU网络虚拟化中对虚拟网络设备抽象为device和backend,如virtio-net/vhost-net、e1000/tap都是device/backend的对应关系,两者互为peer,详细可参考Wiki):

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
void qemu_del_net_client(NetClientState *nc)
{
......
/* If there is a peer NIC, delete and cleanup client, but do not free. */
if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_NIC) {
NICState *nic = qemu_get_nic(nc->peer);
if (nic->peer_deleted) {
return;
}
nic->peer_deleted = true;

for (i = 0; i < queues; i++) {
ncs[i]->peer->link_down = true;
}

if (nc->peer->info->link_status_changed) {
nc->peer->info->link_status_changed(nc->peer);
}

for (i = 0; i < queues; i++) {
qemu_cleanup_net_client(ncs[i]);
}

return;
}
......
}

一般而言,当backend对应的NetClientState被删除时,会调用qemu_del_net_client,通知peer网卡链路状态发生了改变。

该函数的调用处,除了各类型网卡的初始化异常处理逻辑,和关机流程,就只有qmp_netdev_del(又是一处QMP的接口调用😅),也就说明,只有通过QMP删除netdev时,会走到这个逻辑。

Conclusion

上述代码分析中,除了入手点从virtio-net,其余都是对link_status_changed的调用分析,即该流程对QEMU的其他类型网卡同样适用。

正常运行过程中会引起Guest内部链路状态改变的场景如下:

  • virtio-net网卡的backend为vhost-user时,对应的unix socket发生断开、连接事件;
  • 通过qmp,主动设置对应网卡的链路状态;
  • 通过qmp,删除backend,通知peer网卡链路状态发生改变。

qemu_del_net_client本身还有其他调用处,如关机流程、backend初始化失败的异常处理逻辑,但不属于正常运行过程中的场景,场景也比较简单,所以就不作分析了。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021-2023 Martzki
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信