void pci_set_irq(PCIDevice *pci_dev, int level) { int intx = pci_intx(pci_dev); pci_irq_handler(pci_dev, intx, level); }
/* 0 <= irq_num <= 3. level must be 0 or 1 */ static void pci_irq_handler(void *opaque, int irq_num, int level) { PCIDevice *pci_dev = opaque; int change; change = level - pci_irq_state(pci_dev, irq_num); if (!change) return; pci_set_irq_state(pci_dev, irq_num, level); pci_update_irq_status(pci_dev); if (pci_irq_disabled(pci_dev)) return; pci_change_irq_level(pci_dev, irq_num, change); }
一. 概述
二. Vhost-blk架構

三. IO的傳送流

void vhost_virtqueue_start(struct vhost_dev *dev,
struct VirtIODevice *vdev,
struct vhost_virtqueue *vq,
unsigned idx)
int vhost_virtqueue_set_addr(struct vhost_dev *dev,
struct vhost_virtqueue *vq,
unsigned idx, bool enable_log)

四. 重要函數
1. Vhost_blk重要函數

2. QEMU中的重要函數
五. 總結



virtio_queue_set_guest_notifier_fd_handler
host是virtio的另一種方案,用於跳過qemu,減少qemu和內核之間上下文切換的開銷,對於網絡IO而言提升尤其明顯。vhost目前有兩種實現方案,內核態和用戶態,本文重點討論內核態的vhost
vhost內核模塊主要處理數據面的事情,控制面上還是交給qemu,
下面來看下vhost的數據流,vhost與kvm模塊之間通過eventfd來實現,guest到host方向的kick event,通過ioeventfd實現,host到guest方向的call event,通過irqfd實現
host到guest方向
-> r = k->set_guest_notifiers(qbus->parent, total_queues * 2, true); //調用virtio_pci_set_guest_notifiers 1、-> virtio_pci_set_guest_notifiers --> kvm_vm_ioctl 2、 -> virtio_pci_set_guest_notifier-> virtio_queue_set_guest_notifier_fd_handler
1、有中斷irqfd通過kvm_vm_ioctl來設置kvm模塊的irqfd
首先host處理used ring,然后判斷如果KVM_IRQFD成功設置,kvm模塊會通過irqfd把中斷注入guest。qemu是通過virtio_pci_set_guest_notifiers -> kvm_virtio_pci_vector_use -> kvm_virtio_pci_irqfd_use -> kvm_irqchip_add_irqfd_notifier -> kvm_irqchip_assign_irqfd最終調用kvm_vm_ioctl來設置kvm模塊的irqfd的,包含write fd和read fd(可選)
static int kvm_virtio_pci_vector_use(VirtIOPCIProxy *proxy, int nvqs) { PCIDevice *dev = &proxy->pci_dev; VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus); VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev); unsigned int vector; int ret, queue_no; MSIMessage msg; for (queue_no = 0; queue_no < nvqs; queue_no++) { if (!virtio_queue_get_num(vdev, queue_no)) { break; } vector = virtio_queue_vector(vdev, queue_no); if (vector >= msix_nr_vectors_allocated(dev)) { continue; } msg = msix_get_message(dev, vector); ret = kvm_virtio_pci_vq_vector_use(proxy, queue_no, vector, msg); if (ret < 0) { goto undo; } /* If guest supports masking, set up irqfd now. * Otherwise, delay until unmasked in the frontend. */ if (k->guest_notifier_mask) { ret = kvm_virtio_pci_irqfd_use(proxy, queue_no, vector); if (ret < 0) { kvm_virtio_pci_vq_vector_release(proxy, vector); goto undo; } } } return 0; undo: while (--queue_no >= 0) { vector = virtio_queue_vector(vdev, queue_no); if (vector >= msix_nr_vectors_allocated(dev)) { continue; } if (k->guest_notifier_mask) { kvm_virtio_pci_irqfd_release(proxy, queue_no, vector); } kvm_virtio_pci_vq_vector_release(proxy, vector); } return ret; }
2、如果沒有設置irqfd,則guest notifier fd會通知到等待fd的qemu進程,進入注冊函數virtio_queue_guest_notifier_read,調用virtio_irq,最終調用到virtio_pci_notify
static void virtio_queue_guest_notifier_read(EventNotifier *n) { VirtQueue *vq = container_of(n, VirtQueue, guest_notifier); if (event_notifier_test_and_clear(n)) { virtio_irq(vq); } } void virtio_irq(VirtQueue *vq) { trace_virtio_irq(vq); vq->vdev->isr |= 0x01; virtio_notify_vector(vq->vdev, vq->vector); } static void virtio_notify_vector(VirtIODevice *vdev, uint16_t vector) { BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); if (k->notify) { k->notify(qbus->parent, vector); } } static void virtio_pci_notify(DeviceState *d, uint16_t vector) { VirtIOPCIProxy *proxy = to_virtio_pci_proxy_fast(d); if (msix_enabled(&proxy->pci_dev)) msix_notify(&proxy->pci_dev, vector); else { VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus); pci_set_irq(&proxy->pci_dev, vdev->isr & 1); } }
1 技術簡介
1.1 virtio-net 簡介
virtio-net 在 guest 前端驅動 kick 后端驅動時,采用 I/O 指令方式退出到 host KVM。kvm 通過 eventfd_signal
喚醒阻塞的 qemu 線程。qemu 通過 vring 處理報文。qemu 把報文從用戶態傳送給 tap 口。
1.2 vhost-net 簡介
與 virtio-net 不同的是,eventfd_signal
喚醒的是內核 vhost_worker
進程。vhost_worker
從 vring 提取報文數據,然后發送給 tap。與 virtio-net 相比,vhost-net 處理數據在內核態,在發送到 tap 口的時候少了一次數據的拷貝。
1.3 ovs 轉發涉及的模塊概要
VM->VM 流程:
2 virtio-net.ko 前端驅動部分
2.1 guest->host 數據發送
當前端 virtio-net 有想發送的報文數據時將會 kick 后端,右面是前端 kick 后端的流程。前端調用 xmit_skb
發送數據,virtqueue_add_outbuf
是把 sk_buff
里的內容(frag[]
數組)逐一的填入 scatterlist
數組中。這里可以理解成填寫分散聚合描述符表。
但前端和后端數據傳遞是通過 struct vring_desc
傳遞的,所以 virtqueue_add()
再把 struct scatterlist
里的數據填寫到 struct vring_desc
里。
struct vring_desc
這個數據結構的使用,后面我們再詳細說。
最后通過 vq->notify(&vq->vq) (vp_notify())
kick 后端,后續流程到了 kvm.ko 部分的第 4 小節。
2.2 guest->host 代碼流程
2.3 host->guest 數據發送
guest 通過 NAPI 接口的 virtnet_poll
接收數據,通過 virtqueue_get_buf_ctx
從 Vring 中獲取報文數據。再通過 receive_buf
把報文數據保存到 skb 中。
這樣目的端就成功接收了來自源端的報文。
2.4 host->guest 代碼流程
3 kvm.ko 部分
3.1 eventfd 注冊
由上圖可見 eventfd 的注冊是在 qemu 中發起的。qemu 調用 kvm 提供的系統調用。
3.2 eventfd 通知流程
eventfd 一半的用法是用戶態通知用戶態,或者內核態通知用戶態。例如 virtio-net 的實現是 guest 在 kick host 時采用 eventfd 通知的 qemu,然后 qemu 在用戶態做報文處理。但 vhost-net 是在內核態進行報文處理,guest 在 kick host 時采用 eventfd 通知的是內核線程 vhost_worker
。所以這里的用法就跟常規的 eventfd 的用法不太一樣。
下面介紹 eventfd 通知的使用。
eventfd 核心數據結構:
struct eventfd_ctx {
struct kref kref;
wait_queue_head_t wqh;
__u64 count;
unsigned int flags;
};
eventfd 的數據結構其實就是包含了一個等待隊列頭。當調用 eventfd_signal
函數時就是喚醒 wgh 上等待隊列。
__u64 eventfd_signal(struct eventfd_ctx *ctx, __u64 n)
{
unsigned long flags;
spin_lock_irqsave(&ctx->wqh.lock, flags);
if (ULLONG_MAX - ctx->count < n)
n = ULLONG_MAX - ctx->count;
ctx->count += n;
if (waitqueue_active(&ctx->wqh))
wake_up_locked_poll(&ctx->wqh, POLLIN);
spin_unlock_irqrestore(&ctx->wqh.lock, flags);
return n;
}
#define wake_up_locked_poll(x, m) \
__wake_up_locked_key((x), TASK_NORMAL, (void *) (m))
void __wake_up_locked_key(struct wait_queue_head *wq_head, unsigned int mode, void *key)
{
__wake_up_common(wq_head, mode, 1, 0, key, NULL);
}
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
int nr_exclusive, int wake_flags, void *key,
wait_queue_entry_t *bookmark)
{
...
list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {
unsigned flags = curr->flags;
int ret;
if (flags & WQ_FLAG_BOOKMARK)
continue;
ret = curr->func(curr, mode, wake_flags, key); /* 調用vhost_poll_wakeup */
if (ret < 0)
break;
if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) &&
(&next->entry != &wq_head->head)) {
bookmark->flags = WQ_FLAG_BOOKMARK;
list_add_tail(&bookmark->entry, &next->entry);
break;
}
}
return nr_exclusive;
}
static int vhost_poll_wakeup(wait_queue_entry_t *wait, unsigned mode, int sync,
void *key)
{
struct vhost_poll *poll = container_of(wait, struct vhost_poll, wait);
if (!((unsigned long)key & poll->mask))
return 0;
vhost_poll_queue(poll);
return 0;
}
void vhost_poll_queue(struct vhost_poll *poll)
{
vhost_work_queue(poll->dev, &poll->work);
}
void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work)
{
if (!dev->worker)
return;
if (!test_and_set_bit(VHOST_WORK_QUEUED, &work->flags)) {
/* We can only add the work to the list after we're
* sure it was not in the list.
* test_and_set_bit() implies a memory barrier.
*/
llist_add(&work->node, &dev->work_list); /* 添加到 dev->work_list)*/
wake_up_process(dev->worker); /* 喚醒vhost_worker線程 */
}
}
這里有一個疑問,就是 vhost_worker
什么時候加入到 eventfd 的 wgh 字段的,__wake_up_common
函數里 curr->func
又是什么時候被設置成 vhost_poll_wakeup
函數的呢?請看下一節。
3.3 eventfd 與 vhost_worker 綁定
vhost.ko 創建了一個字符設備,vhost_net_open
在打開這個設備文件的時候會調用 vhost_net_open
函數。這里為 vhost_dev
設備進行初始化。
static int vhost_net_open(struct inode *inode, struct file *f)
{
...
dev = &n->dev;
vqs[VHOST_NET_VQ_TX] = &n->vqs[VHOST_NET_VQ_TX].vq;
vqs[VHOST_NET_VQ_RX] = &n->vqs[VHOST_NET_VQ_RX].vq;
n->vqs[VHOST_NET_VQ_TX].vq.handle_kick = handle_tx_kick;
n->vqs[VHOST_NET_VQ_RX].vq.handle_kick = handle_rx_kick;
...
vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev);
vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev);
f->private_data = n;
return 0;
}
void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
unsigned long mask, struct vhost_dev *dev)
{
init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup); /* 給curr->fn賦值 vhost_poll_wakeup */
init_poll_funcptr(&poll->table, vhost_poll_func); /* 給poll_table->_qproc賦值vhost_poll_func */
poll->mask = mask;
poll->dev = dev;
poll->wqh = NULL;
vhost_work_init(&poll->work, fn); /* 給 work->fn 賦值為handle_tx_net和handle_rx_net */
}
qemu 使用 ioctl
系統調用 VHOST_SET_VRING_KICK
時會把 eventfd 的 struct file
指針付給 pollstart 和 pollstop,同時調用 vhost_poll_start()
。
long vhost_vring_ioctl(struct vhost_dev *d, int ioctl, void __user *argp)
{
...
case VHOST_SET_VRING_KICK:
if (copy_from_user(&f, argp, sizeof f)) {
r = -EFAULT;
break;
}
eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd);
if (IS_ERR(eventfp)) {
r = PTR_ERR(eventfp);
break;
}
if (eventfp != vq->kick) {
pollstop = (filep = vq->kick) != NULL;
pollstart = (vq->kick = eventfp) != NULL;
} else
filep = eventfp;
break;
...
if (pollstart && vq->handle_kick)
r = vhost_poll_start(&vq->poll, vq->kick);
...
}
int vhost_poll_start(struct vhost_poll *poll, struct file *file)
{
unsigned long mask;
int ret = 0;
if (poll->wqh)
return 0;
mask = file->f_op->poll(file, &poll->table); /* 執行eventfd_poll */
if (mask)
vhost_poll_wakeup(&poll->wait, 0, 0, (void *)mask);
if (mask & POLLERR) {
vhost_poll_stop(poll);
ret = -EINVAL;
}
return ret;
}
static unsigned int eventfd_poll(struct file *file, poll_table *wait)
{
struct eventfd_ctx *ctx = file->private_data;
unsigned int events = 0;
u64 count;
poll_wait(file, &ctx->wqh, wait);
。。。
}
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p); /* 調用vhost_poll_func */
}
static void vhost_poll_func(struct file *file, wait_queue_head_t *wqh,
poll_table *pt)
{
struct vhost_poll *poll;
poll = container_of(pt, struct vhost_poll, table);
poll->wqh = wqh;
add_wait_queue(wqh, &poll->wait);
}
關鍵數據結構關系如下圖:
3.4 guest->host 的通知流程(喚醒 vhost_worker 線程)
Kick host 的原理是通過 io 指令實現的。前端執行 io 指令,就會發生 vm exit。KVM 捕捉到 vm exit 會去查詢退出原因,由於是 io 指令,所以執行對應的 handle_io
處理。handle_io()
從 exit_qualification
中得到 io 操作地址。kvm_fast_pio_out()
會根據 io 操作的地址找到對應的處理函數。第 1 小節 eventfd 注冊的流程可知,kvm_fast_pio_out()
最終會調用 eventfd 對應的回調函數 ioeventfd_write()
。再根據第 3 小節可知 eventfd 最終會喚醒 vhost_worker
內核進程。
流程進入 vhost.ko 的第3小節。
3.5 host 給 guest 注入中斷
到目前位置,發送給 guest 的報文已經准備好了。通過注入中斷通知 guest 接收報文。這里要為虛機的 virtio-net 設備模擬一個 MSI 中斷,並且准備了中斷向量號。調用 vmx_deliver_posted_interrupt
給目的 VCPU 線程所在的物理核注入終端。
流程將跳轉到 virtio-net.ko 前端驅動的第3小節。
3.6 host 給 guest 注入中斷代碼流程 ------------------------非host方式通過virtio_notify_irqfd
4 vhost.ko 部分
前面有提到 vhost_worker
線程被喚醒后將執行 vhost_poll_init()
函數這冊的 handle_tx_net
和 handle_rx_net
函數。
4.1 vhost_worker 線程創建
long vhost_dev_set_owner(struct vhost_dev *dev)
{
...
/* No owner, become one */
dev->mm = get_task_mm(current);
worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
if (IS_ERR(worker)) {
err = PTR_ERR(worker);
goto err_worker;
}
dev->worker = worker;
wake_up_process(worker); /* avoid contributing to loadavg */
err = vhost_attach_cgroups(dev);
if (err)
goto err_cgroup;
err = vhost_dev_alloc_iovecs(dev);
if (err)
goto err_cgroup;
...
}
讓 vhost-dev 的 worker 指向剛創建出的 worker 線程。
4.2 vhost_worker 實現
static int vhost_worker(void *data)
{
struct vhost_dev *dev = data;
struct vhost_work *work, *work_next;
struct llist_node *node;
mm_segment_t oldfs = get_fs();
set_fs(USER_DS);
use_mm(dev->mm);
for (;;) {
/* mb paired w/ kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
if (kthread_should_stop()) {
__set_current_state(TASK_RUNNING);
break;
}
node = llist_del_all(&dev->work_list); /*vhost_work_queue 添加 */
if (!node)
schedule();
node = llist_reverse_order(node);
/* make sure flag is seen after deletion */
smp_wmb();
llist_for_each_entry_safe(work, work_next, node, node) {
clear_bit(VHOST_WORK_QUEUED, &work->flags);
__set_current_state(TASK_RUNNING);
work->fn(work); /* 由vhost_poll_init賦值 handle_tx_net和handle_rx_net*/
if (need_resched())
schedule();
}
}
unuse_mm(dev->mm);
set_fs(oldfs);
return 0;
}
從代碼可以看到在循環的開始部分是摘除 dev->work_list
鏈表中的頭表項。這里如果鏈表為空則返回 NULL,如果鏈表不為空則返回頭結點。如果鏈表為空則調用 schedule()
函數 vhost_worker
進程進入阻塞狀態,等待被喚醒。
當 vhost_worker
被喚醒后將執行 fn 函數,對於 vhost-net 將被賦值為 handle_tx_net
和 handle_rx_net
。
4.3 從 guest->host 方向的發送報文函數 handle_tx_net
handle_tx_net
的代碼邏輯比較短,里面直接調用了 tun.ko 的接口函數發送報文。流程走到了 tun.ko 章節的第1小節。
static void handle_tx_net(struct vhost_work *work)
{
struct vhost_net *net = container_of(work, struct vhost_net,
poll[VHOST_NET_VQ_TX].work);
handle_tx(net);
}
static void handle_tx(struct vhost_net *net)
{
...
for (;;) {
...
/* TODO: Check specific error and bomb out unless ENOBUFS? */
err = sock->ops->sendmsg(sock, &msg, len); /* tup.c中定義 tup_sendmsg ()*/
if (unlikely(err < 0)) {
...
}
out:
mutex_unlock(&vq->mutex);
}
4.4 guest->host 代碼流程
4.5 從 host->guest 方向的接收
vhost-worker 進程調用 handle_rx_net
。vhost_add_used_and_signal_n
負責從 vring 中接收報文,vhost_signal
函數通知 guest 報文的到來。目前都是通過注入中斷的方式通知 guest。 流程將跳轉到 kvm.ko 的第5小節。
4.6 host->guest 代碼流程
host->guest 方向:
5 tun.ko 部分
5.1 報文發送處理流程
tun 模塊首先通過調用 __napi_schedue()
接口去掛起 NET_RX_SOFTIRQ
軟中斷的,並且調度的是 sd->backlog
這個 struct napi
。然后在 tun_rx_batched()
函數在使能中斷下半部時會調用 do_softirq()
,從而執行剛剛掛起的 NET_RX_SOFTIRQ
對應的 net_rx_action
軟中斷響應函數 net_rx_aciton
。net_rx_action
會執行 sd->backlog
對應的 napi 接口函數。process_backlog
是內核的 netdev 在初始化時在每 CPU 變量中填入的 struct napi_struct
結構體。最后從 process_backlog
執行到 openvswitch 注冊的 hook 函數 netdev_frame_hook
(openvswitch.ko 第 2小節)。
流程將跳轉到 openvswitch.ko 第3小節。
5.2 process_backlog 的注冊
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
…
for_each_possible_cpu(i) { /* 遍歷各個CPU的每CPU變量 */
struct work_struct *flush = per_cpu_ptr(&flush_works, i);
struct softnet_data *sd = &per_cpu(softnet_data, i); /* sd是個每CPU變量 */
INIT_WORK(flush, flush_backlog);
skb_queue_head_init(&sd->input_pkt_queue);
skb_queue_head_init(&sd->process_queue);
INIT_LIST_HEAD(&sd->poll_list);
sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
sd->csd.func = rps_trigger_softirq;
sd->csd.info = sd;
sd->cpu = i;
#endif
sd->backlog.poll = process_backlog; /* 定義napi_struct的poll函數 */
sd->backlog.weight = weight_p;
}
…
if (register_pernet_device(&loopback_net_ops))
goto out;
if (register_pernet_device(&default_device_ops))
goto out;
open_softirq(NET_TX_SOFTIRQ, net_tx_action); /* 設置軟中斷NET_TX_SOFTIRQ的響應函數 */
open_softirq(NET_RX_SOFTIRQ, net_rx_action); /*設置軟中斷NET_RX_SOFTIRQ的響應函數 */
rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
NULL, dev_cpu_dead);
WARN_ON(rc < 0);
rc = 0;
out:
return rc;
}
6 openvswitch 部分
openvswitch.ko 作為 openvswitch 的一個內核模塊內核態報文的接收和轉發。通過給 tun 設備掛接 hook 函數,來處理 tun 接收和發送的報文。在創建虛機時給虛機分配的 vnet 口會暴露給 host,我們一般通過 xml 文件指定到橋入那個 ovs 網橋。在橋入的時候,用戶態代碼通過 netlink 與 openvswitch.ko 進行通信。把 vnet 口橋入 ovs 網橋時會給 vnet 這個設備掛 netdev_frame_hook
鈎子函數。
6.1 netlink 注冊
當 ovs 添加一個 vport 時會通過 netlink 發送到 openvswitch.ko,openvswitch 注冊的 netlink 處理函數負責處理相關命令。
static struct genl_ops dp_vport_genl_ops[] = {
{ .cmd = OVS_VPORT_CMD_NEW,
.flags = GENL_UNS_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = vport_policy,
.doit = ovs_vport_cmd_new /* OVS_VPORT_CMD_NEW消息的 */
},
{ .cmd = OVS_VPORT_CMD_DEL,
.flags = GENL_UNS_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = vport_policy,
.doit = ovs_vport_cmd_del
},
{ .cmd = OVS_VPORT_CMD_GET,
.flags = 0, /* OK for unprivileged users. */
.policy = vport_policy,
.doit = ovs_vport_cmd_get,
.dumpit = ovs_vport_cmd_dump
},
{ .cmd = OVS_VPORT_CMD_SET,
.flags = GENL_UNS_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = vport_policy,
.doit = ovs_vport_cmd_set,
},
};
struct genl_family dp_vport_genl_family __ro_after_init = {
.hdrsize = sizeof(struct ovs_header),
.name = OVS_VPORT_FAMILY,
.version = OVS_VPORT_VERSION,
.maxattr = OVS_VPORT_ATTR_MAX,
.netnsok = true,
.parallel_ops = true,
.ops = dp_vport_genl_ops,
.n_ops = ARRAY_SIZE(dp_vport_genl_ops),
.mcgrps = &ovs_dp_vport_multicast_group,
.n_mcgrps = 1,
.module = THIS_MODULE,
};
static struct genl_family *dp_genl_families[] = {
&dp_datapath_genl_family,
&dp_vport_genl_family,
&dp_flow_genl_family,
&dp_packet_genl_family,
&dp_meter_genl_family,
};
static int __init dp_register_genl(void)
{
int err;
int i;
for (i = 0; i < ARRAY_SIZE(dp_genl_families); i++) {
err = genl_register_family(dp_genl_families[i]); 注冊netlink處理函數
if (err)
goto error;
}
return 0;
error:
dp_unregister_genl(i);
return err;
}
6.2 netdev_frame_hook 函數的注冊
6.3 ovs 對報文的轉發流程
OVS 首先通過 key 值找到對應的流表,然后轉發到對應的端口。這篇文章的重點是講解 vhost 的流程,OVS 具體流程並不是我們的講解的重點。所以這方面有什么疑問請大家自行搜索一下 OVS 的資料。
這段代碼的大體目的就是找到目的虛機所在的端口,也就是目的虛機所在的 vnet 端口。
流程跳轉到內核部分第1小節。
7 內核部分
7.1 發送報文喚醒目的端的 vhost-worker 進程
內核的發送函數 __dev_queue_xmit
將會找到 vnet 設備對應的等待隊列,並喚醒等待隊列里對應的進程。這里將喚醒的進程就是 vhost_worker
進程了。
流程跳轉到 vhost.ko 的第5小節。
7.2 代碼流程