vhost-net 2 -- 重要数据结构

2022-11-15  本文已影响0人  苏苏林
vhost_virtqueue

struct vhost_virtqueue:用于描述vhost设备对应的virtqueue,这部分内容可以参考之前virtqueue机制分析,本质上是将Qemu中virtqueue处理机制下沉到了Kernel中。


/* The virtqueue structure describes a queue attached to a device. */
struct vhost_virtqueue {
    struct vhost_dev *dev;

    /* The actual ring of buffers. */
    struct mutex mutex;
    unsigned int num;
    struct vring_desc __user *desc;
    struct vring_avail __user *avail;
    struct vring_used __user *used;
    struct file *kick;
    struct file *call;
    struct file *error;
    struct eventfd_ctx *call_ctx;
    struct eventfd_ctx *error_ctx;
    struct eventfd_ctx *log_ctx;

    struct vhost_poll poll;

    /* The routine to call when the Guest pings us, or timeout. */
    vhost_work_fn_t handle_kick;

    /* Last available index we saw. */
    u16 last_avail_idx;

    /* Caches available index value from user. */
    u16 avail_idx;

    /* Last index we used. */
    u16 last_used_idx;

    /* Used flags */
    u16 used_flags;

    /* Last used index value we have signalled on */
    u16 signalled_used;

    /* Last used index value we have signalled on */
    bool signalled_used_valid;

    /* Log writes to used structure. */
    bool log_used;
    u64 log_addr;

    struct iovec iov[UIO_MAXIOV];
    struct iovec *indirect;
    struct vring_used_elem *heads;
    /* We use a kind of RCU to access private pointer.
     * All readers access it from worker, which makes it possible to
     * flush the vhost_work instead of synchronize_rcu. Therefore readers do
     * not need to call rcu_read_lock/rcu_read_unlock: the beginning of
     * vhost_work execution acts instead of rcu_read_lock() and the end of
     * vhost_work execution acts instead of rcu_read_unlock().
     * Writers use virtqueue mutex. */
    void __rcu *private_data;
    /* Log write descriptors */
    void __user *log_base;
    struct vhost_log *log;
};

关键成员:

vhost_virtqueue 中就包含着三部分。
VRing中buffer空间的分配永远由guest负责,guest发数据时,还需要向buffer填写数据,guest收数据时,分配buffer空间后通知Host向buffer中填写数据。所以,vhost_virtqueue中的desc、avail、used都使用__user宏定义,说明指针地址必须在用户地址空间。qemu通过 vring_ioctl 的 VHOST_SET_VRING_ADDR 下发用户态地址设置。前后端使用共享这些内存。

long vhost_vring_ioctl(struct vhost_dev *d, int ioctl, void __user *argp)
{
......
    case VHOST_SET_VRING_ADDR:
        if (copy_from_user(&a, argp, sizeof a)) {
            r = -EFAULT;
            break;
        }
         ......
        vq->log_used = !!(a.flags & (0x1 << VHOST_VRING_F_LOG));
        vq->desc = (void __user *)(unsigned long)a.desc_user_addr;
        vq->avail = (void __user *)(unsigned long)a.avail_user_addr;
        vq->log_addr = a.log_guest_addr;
        vq->used = (void __user *)(unsigned long)a.used_user_addr;
        break;
......

}

kick和call都是是eventfd文件,涉及到virtio前后端通知机制实现的重要部分:ioeventfd和irqfd。

eventfd和irqfd都是qemu分别在vhost和kvm注册。 image.png
static int
ioeventfd_write(struct kvm_io_device *this, gpa_t addr, int len,
        const void *val)
{
    struct _ioeventfd *p = to_ioeventfd(this);

    if (!ioeventfd_in_range(p, addr, len, val))
        return -EOPNOTSUPP;

    eventfd_signal(p->eventfd, 1);
    return 0;
}

/* This actually signals the guest, using eventfd. */
void vhost_signal(struct vhost_dev *dev, struct vhost_virtqueue *vq)
{
    /* Signal the Guest tell them we used something up. */
    if (vq->call_ctx && vhost_notify(dev, vq))
        eventfd_signal(vq->call_ctx, 1);
}
vhost_poll

理解vhost_poll是理解vhost_net的工作机制很重要的一环。其代表vhost poll机制中的一个等待实体。
poll,字面意思轮询,可以理解为,当某一事件发生的时候(tap收到报文),轮询遍历关心此事件的所有对象(tap socket遍历socket等待列表中的所有等待实体 vhost_poll.wait),唤醒此对象的工作线程/进程(调用vhost_poll.wait.func=vhost_poll_wakeup唤醒vhost内核线程),调用次事件的处理函数处理事件(vhost_poll.work.fn)。
vhost_poll 存在于两个位置:

/* Poll a file (eventfd or socket) */
/* Note: there's nothing vhost specific about this structure. */
struct vhost_poll {
    // table 是包含一个函数指针,在驱动的poll函数中被调用
    poll_table                table;
    // wqh是一个等待队列头,没太多用处,用来判断poll是否已经挂载过。
    wait_queue_head_t        *wqh;
    // wait是一个等待实体,其包含一个函数作为唤醒函数,vhost_poll_wakeup
    wait_queue_t              wait;
    // vhost_work是poll机制处理的核心任务,处理网络数据包,其中函数指针指向用户设置的处理函数,这里就是handle_tx_net和handle_rx_net
    struct vhost_work     work;
    unsigned long         mask;
    struct vhost_dev     *dev;
};


vhost_poll 包含:

1、 poll_table table
包含一个回调函数,挂载的vhost_poll_func,外部通过poll_table完成等待队列的挂载,为poll挂载(poll_wait挂等待队列)通用流程所需通用数据结构。

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

/*
 * Do not touch the structure directly, use the access functions
 * poll_does_not_wait() and poll_requested_events() instead.
 */
typedef struct poll_table_struct {
    poll_queue_proc _qproc;
    unsigned long _key;
} poll_table;

vhost_poll_func函数 通过传入的 poll_table 参数获取到vhost_poll,将其代表的等待实体加入到传入的等待队列中,这里的等待队列有tap socket和eventfd的等待队列。vhost_poll_func 被 poll_wait调用,而poll_wait会被各种设备驱动的poll函数调用。
vhost中涉及两处调用:

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);
}

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);
}

static unsigned int tun_chr_poll(struct file *file, poll_table *wait)
{
    struct tun_file *tfile = file->private_data;
    struct tun_struct *tun = __tun_get(tfile);
    struct sock *sk;
    unsigned int mask = 0;

    if (!tun)
        return POLLERR;

    sk = tfile->socket.sk;

    tun_debug(KERN_INFO, tun, "tun_chr_poll\n");

    poll_wait(file, &tfile->wq.wait, wait);
......
}

2、struct vhost_work work;
vhost_work 代表vhost poll 被触发后,要做的工作,其中函数指针fn就是任务的回调函数,在vhost中有 handle_tx_net/handle_rx_net 用来处理网络数据包,handle_tx_kick/handle_rx_kick用来处理kick事件。
vhost_work 在vhost poll 被唤醒后,会被挂载到vhost内核线程的任务列表中等待处理。

struct vhost_work;
typedef void (*vhost_work_fn_t)(struct vhost_work *work);

struct vhost_work {
    struct list_head      node;
    vhost_work_fn_t       fn;
    wait_queue_head_t     done;
    int           flushing;
    unsigned          queue_seq;
    unsigned          done_seq;
};

3、 wait_queue_t wait;
wait_queue_t 表示一个等待实体,包含一个等待节点task_list,用来加入设备等待队列,就是在上面vhost_poll_func 中所做的事情。包含一个回调函数 func,挂载的vhost_poll_wakeup,用来唤醒vhost内核线程处理响应的事件。
vhost_poll_wakeup


typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);

struct __wait_queue {
    unsigned int flags;
#define WQ_FLAG_EXCLUSIVE   0x01
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};


vhost_poll_wakeup 做的事情就是将vhost_poll 中的vhost_work 加入到vhost设备的工作列表,然后唤醒vhost内核线程,其会遍历vhost设备的工作列表调用其上挂载的vhost_work,调用其回调函数处理。


static int vhost_poll_wakeup(wait_queue_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)
{
    unsigned long flags;

    spin_lock_irqsave(&dev->work_lock, flags);
    if (list_empty(&work->node)) {
        list_add_tail(&work->node, &dev->work_list);
        work->queue_seq++;
        wake_up_process(dev->worker);
    }
    spin_unlock_irqrestore(&dev->work_lock, flags);
}


vhost_poll_wakeup 被调用的地方:

static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct tun_struct *tun = netdev_priv(dev);
    int txq = skb->queue_mapping;
    struct tun_file *tfile;
......
    wake_up_interruptible_poll(&tfile->wq.wait, POLLIN |
                   POLLRDNORM | POLLRDBAND);
......
}

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, wake_flags, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}
__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;
}

vhost_net_virtqueue

用于描述Vhost-Net设备对应的virtqueue,封装的struct vhost_virtqueue。

struct vhost_dev

描述通用的vhost设备,可内嵌在基于vhost机制的其他设备结构体中,比如struct vhost_net,struct vhost_scsi等。关键字段如下:1)vqs指针,指向已经分配好的struct vhost_virtqueue,对应数据传输;2)work_list,任务链表,用于放置需要在vhost_worker内核线程上执行的任务;3)worker,用于指向创建的内核线程,执行任务列表中的任务;

struct vhost_net

用于描述Vhost-Net设备。它包含几个关键字段:1)struct vhost_dev,通用的vhost设备,可以类比struct device结构体内嵌在其他特定设备的结构体中;2)struct vhost_net_virtqueue,实际上对struct vhost_virtqueue进行了封装,用于网络包的数据传输;3)struct vhost_poll,用于tap socket或者eventfd文件的poll机制,以便在数据包接收与发送时进行任务调度;

vhost_dev,vhost_virtqueue是vhost机制通用部分的实现。vhost_net,vhost_net_virtqueue则是采用vhost机制的网络功能的实现。还有vhost-blk,对block设备的模拟,以及vhost-scsi,对scsi设备的模拟。

上一篇 下一篇

猜你喜欢

热点阅读