ukblkdev源码阅读

描述了unikraft的块设备及其功能

数据结构

各结构体之间的关联

ukblkdev结构体布局

uk_blkdev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct uk_blkdev {
    /* Pointer to submit request function */
    uk_blkdev_queue_submit_one_t submit_one;
    /* Pointer to handle_responses function 完成请求 */
    uk_blkdev_queue_finish_reqs_t finish_reqs;
    /* Pointer to API-internal state data. */
    struct uk_blkdev_data *_data;
    /* Capabilities. */
    struct uk_blkdev_cap capabilities;
    /* Functions callbacks by driver. */
    const struct uk_blkdev_ops *dev_ops;
    /* Pointers to queues (API-private) */
    struct uk_blkdev_queue *_queue[CONFIG_LIBUKBLKDEV_MAXNBQUEUES];
    /* Entry for list of block devices TAILQ项,用于加入uk_blkdev_list*/
    UK_TAILQ_ENTRY(struct uk_blkdev) _list;
};
  • submit_one:通过将信息放入virtio buffer,向host提交事件请求。只是一个接口,具体实现由virtio_blk.c文件中的virtio_blkdev_submit_request函数完成
  • finish_reqs:host完成请求,并通过virtqueue_vring将请求出队,返还给uk_blkdev。只是一个接口,具体实现由virtio_blk.c文件中的virtio_blkdev_complete_reqs函数完成
  • _data:目前块设备的具体数据信息
  • capabilities:块设备的容量
  • dev_ops:块设备可实现的操作,只定义接口,具体实现由virtio_blk完成
  • _queue[CONFIG_LIBUKBLKDEV_MAXNBQUEUES]:块设备队列,具体结构在virtio_blk.c文件中
  • _list:UK_TAILQ_ENTRY(环状双链表表项),用于将blkdev链接至uk_blkdev_list

    uk_blkdev_data

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct uk_blkdev_data {
         /* Device id identifier */
        const uint16_t id;
        /* Device state */
        enum uk_blkdev_state state;
        /* Event handler for each queue */
        struct uk_blkdev_event_handler
            queue_handler[CONFIG_LIBUKBLKDEV_MAXNBQUEUES];
        /* Name of device*/
        const char *drv_name;
        /* Allocator */
        struct uk_alloc *a;
    };
  • id:块设备的id号,id号根据系统创建顺序进行分配,从0开始。
  • state:设备状态信息,枚举类型
  • queue_handler[CONFIG_LIBUKBLKDEV_MAXNBQUEUES]:块设备的任务管理器

    uk_blkdev_event_handler

    用来实现用户的线程任务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    struct uk_blkdev_event_handler {
        /* Callback */
        uk_blkdev_queue_event_t callback;
        /* Parameter for callback */
        void *cookie;

    #if CONFIG_LIBUKBLKDEV_DISPATCHERTHREADS
        /* Semaphore to trigger events. */
        struct uk_semaphore events;
        /* Reference to blk device. */
        struct uk_blkdev    *dev;
        /* Queue id which caused event. */
        uint16_t            queue_id;
        /* Dispatcher thread. */
        struct uk_thread    *dispatcher;
        /* Reference to thread name. */
        char    *dispatcher_name;
        /* Scheduler for dispatcher. */
        struct uk_sched     *dispatcher_s;
    #endif
    };

    uk_blkdev_cap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct uk_blkdev_cap {
        /* Number of sectors */
        __sector sectors;
        /* Sector size */
        size_t ssize;
        /* Access mode (O_RDONLY, O_RDWR, O_WRONLY) */
        int mode;
        /* Max nb of supported sectors for an op */
        __sector max_sectors_per_req;
        /* Alignment (number of bytes) for data used in future requests */
        uint16_t ioalign;
    };
  • sectors:每个块设备扇区的数量
  • ssize:每个扇区的大小
  • mode:扇区的使用模式
  • max_sectors_per_req:每次请求可以分配的最大数量的扇区
  • ioalign:请求的字节对齐方式

    uk_blkdev_ops

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    struct uk_blkdev_ops {
        // virtio_blk_device的max_vqueue_pairs传入dev_info->max_queues
        uk_blkdev_get_info_t                get_info;
        // 为virtio_blk_device创建uk_blkdev_queue空间,并conf配置
        uk_blkdev_configure_t               dev_configure;
        // 根据virtio_blkdev_queue->qs[queue_id]对应的uk_blkdev_queue,获取nb_min、nb_max、nb_is_power_of_two存入qinfo
        uk_blkdev_queue_get_info_t          queue_get_info;
        // 在vbdev->qs[queue_id]建立uk_blkdev_queue的成员,并返回queue
        uk_blkdev_queue_configure_t         queue_configure;
        // virtio_blkdev配置状态更新完成
        uk_blkdev_start_t               dev_start;
        // 停止virtio_blkdev
        uk_blkdev_stop_t                dev_stop;
        // 对uk_blkdev_queue进行中断使能
        uk_blkdev_queue_intr_enable_t           queue_intr_enable;
        // 禁止uk_blkdev_queue过程进行中断
        uk_blkdev_queue_intr_disable_t          queue_intr_disable;
        // 释放uk_blkdev_queue的空间
        uk_blkdev_queue_unconfigure_t           queue_unconfigure;
        // 释放virtio_blk_device的uk_blkdev_queue空间
        uk_blkdev_unconfigure_t             dev_unconfigure;
    };
    具体实现在vitrio_blk.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static const struct uk_blkdev_ops virtio_blkdev_ops = {
            .get_info = virtio_blkdev_get_info,
            .dev_configure = virtio_blkdev_configure,
            .queue_get_info = virtio_blkdev_queue_info_get,
            .queue_configure = virtio_blkdev_queue_setup,
            .queue_intr_enable = virtio_blkdev_queue_intr_enable,
            .dev_start = virtio_blkdev_start,
            .dev_stop = virtio_blkdev_stop,
            .queue_intr_disable = virtio_blkdev_queue_intr_disable,
            .queue_unconfigure = virtio_blkdev_queue_release,
            .dev_unconfigure = virtio_blkdev_unconfigure,
    };

    操作

    对virtio层的抽象操作

    以下函数均是基于uk_blkdev_ops的成员操作实现的外层抽象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 得到块设备支持的队列的最大数量
    int uk_blkdev_get_info(struct uk_blkdev *dev, struct uk_blkdev_info *dev_info)
    // 为块设备所在的virtio块设备创建块设备队列空间,并conf配置
    int uk_blkdev_configure(struct uk_blkdev *dev, const struct uk_blkdev_conf *conf)
    // 获取块设备的第queue_id个队列描述符的最大数量、最小数量、2的幂后的数量存入qinfo
    int uk_blkdev_queue_get_info(struct uk_blkdev *dev, uint16_t queue_id,
            struct uk_blkdev_queue_info *q_info)
    // 创建事件管理+构建块设备的第queue_id个块设备队列
    int uk_blkdev_queue_configure(struct uk_blkdev *dev, uint16_t queue_id,
            uint16_t nb_desc,
            const struct uk_blkdev_queue_conf *queue_conf)
    // virtio块设备状态更新完成,可以开始任务
    int uk_blkdev_start(struct uk_blkdev *dev)
    // 停用dev块设备,不能再向host发送任何请求
    int uk_blkdev_stop(struct uk_blkdev *dev)
    // 释放块设备中的一个队列
    int uk_blkdev_queue_unconfigure(struct uk_blkdev *dev, uint16_t queue_id)
    // 释放virtio块设备的块队列队头成员空间
    int uk_blkdev_unconfigure(struct uk_blkdev *dev)
    以下是基于块设备的submit_one、finish_reqs操作实现的外层抽象
    1
    2
    3
    4
    5
    6
    // 通过将信息放入virtio buffer,向host提交事件请求
    int uk_blkdev_queue_submit_one(struct uk_blkdev *dev, uint16_t queue_id,
            struct uk_blkreq *req)
    // host完成事件请求
    int uk_blkdev_queue_finish_reqs(struct uk_blkdev *dev,
            uint16_t queue_id)

    事件管理结构的创建及销毁

    创建一个event handler,并传给块设备
    如果有线程的话,在event_handler中顺便创建线程
    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
    static int _create_event_handler(uk_blkdev_queue_event_t callback,
            void *cookie,
    #if CONFIG_LIBUKBLKDEV_DISPATCHERTHREADS
            struct uk_blkdev *dev, uint16_t queue_id,
            struct uk_sched *s,
    #endif
            struct uk_blkdev_event_handler *event_handler)
    {
        UK_ASSERT(event_handler);
        UK_ASSERT(callback || (!callback && !cookie));

        event_handler->callback = callback;
        event_handler->cookie = cookie;

    #if CONFIG_LIBUKBLKDEV_DISPATCHERTHREADS
        /* If we do not have a callback, we do not need a thread */
        if (!callback)
            return 0;

        event_handler->dev = dev;
        event_handler->queue_id = queue_id;
        uk_semaphore_init(&event_handler->events, 0);
        event_handler->dispatcher_s = s;

        /* Create a name for the dispatcher thread.
         * In case of errors, we just continue without a name
         */
        if (asprintf(&event_handler->dispatcher_name,
                    "blkdev%"PRIu16"-q%"PRIu16"]",
                    dev->_data->id, queue_id) < 0) {
            event_handler->dispatcher_name = NULL;
        }
        /* Create thread */
        event_handler->dispatcher = uk_sched_thread_create(
                event_handler->dispatcher_s,
                event_handler->dispatcher_name, NULL,
                _dispatcher, (void *)event_handler);
        // 线程没有创建成功,则释放空间,并报错  
        if (event_handler->dispatcher == NULL) {
            if (event_handler->dispatcher_name) {
                free(event_handler->dispatcher);
                event_handler->dispatcher = NULL;
            }
            return -ENOMEM;
        }
    #endif
        return 0;
    }
    销毁事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static void _destroy_event_handler(struct uk_blkdev_event_handler *h
            __maybe_unused)
    {
        UK_ASSERT(h);

    #if CONFIG_LIBUKBLKDEV_DISPATCHERTHREADS
        if (h->dispatcher) {
            uk_semaphore_up(&h->events);  // 信号量增加
            UK_ASSERT(h->dispatcher_s);
            uk_thread_kill(h->dispatcher);
            uk_thread_wait(h->dispatcher);
            h->dispatcher = NULL;
        }

        // 释放线程命名空间
        if (h->dispatcher_name) {
            free(h->dispatcher_name);
            h->dispatcher_name = NULL;
        }
    #endif
    }

注册块设备

在virtio驱动创建块设备时使用
1.分配dev->_data空间
2.将块设备插入到块设备链表尾部
3.系统块设备数量+1

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
int uk_blkdev_drv_register(struct uk_blkdev *dev, struct uk_alloc *a,
        const char *drv_name)
{
    UK_ASSERT(dev);

    /* Data must be unallocated. */
    UK_ASSERT(PTRISERR(dev->_data));
    /* Assert mandatory configuration. */
    UK_ASSERT(dev->dev_ops);
    UK_ASSERT(dev->dev_ops->dev_configure);
    UK_ASSERT(dev->dev_ops->dev_start);
    UK_ASSERT(dev->dev_ops->queue_configure);
    UK_ASSERT(dev->dev_ops->get_info);
    UK_ASSERT(dev->dev_ops->queue_get_info);
    UK_ASSERT(dev->submit_one);
    UK_ASSERT(dev->finish_reqs);
    UK_ASSERT((dev->dev_ops->queue_intr_enable &&
                dev->dev_ops->queue_intr_disable)
            || (!dev->dev_ops->queue_intr_enable
                && !dev->dev_ops->queue_intr_disable));

    dev->_data = _alloc_data(a, blkdev_count,  drv_name);
    if (!dev->_data)
        return -ENOMEM;

    UK_TAILQ_INSERT_TAIL(&uk_blkdev_list, dev, _list);
    uk_pr_info("Registered blkdev%"PRIu16": %p (%s)\n",
            blkdev_count, dev, drv_name);
    dev->_data->state = UK_BLKDEV_UNCONFIGURED;

    return blkdev_count++;
}

注销块设备

1.释放dev->_data空间
2.将块设备从块设备链表中移除
3.系统块设备数量-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void uk_blkdev_drv_unregister(struct uk_blkdev *dev)
{
    uint16_t id;

    UK_ASSERT(dev != NULL);
    UK_ASSERT(dev->_data);
    UK_ASSERT(dev->_data->state == UK_BLKDEV_UNCONFIGURED);

    id = dev->_data->id;

    uk_free(dev->_data->a, dev->_data);
    UK_TAILQ_REMOVE(&uk_blkdev_list, dev, _list);
    blkdev_count--;

    uk_pr_info("Unregistered blkdev%"PRIu16": %p\n",
            id, dev);
}