nullb driver分析2-nullb初始化流程分析

目录

0.前言

1. null_init

|- -null_init_tag_set

|- -blk_mq_init_queue

2.总结


0.前言

内核版本:4.19

文档目的: 主要以null_dev为例来研究多队列的工作机制, 为了能够凸显多队列的工作流程,只对block层做重点分析说明,对其它代码只做注释性说明。

本文主要通过分析null blk块设备的初始化流程,来理解多队列的初始化,流程。

1. null_init

static int __init null_init(void)
    |--struct nullb *nullb;
    |  struct nullb_device *dev;
    |--config_group_init(&nullb_subsys.su_group)
    |--configfs_register_subsystem(&nullb_subsys)
    |--register_blkdev(0, "nullb")
    \--for (i = 0; i < nr_devices; i++)
        dev = null_alloc_dev();
        null_add_dev(dev);

null_init为null dev创建gendisk并注册进系统,创建软硬件队列及其映射关系,期间也会为null设备创建派发队列并初始化

1. config_group_init 初始化cofigfs group以供使用


2. configfs_register_subsystem: 将config group注册进configfs

3. null_alloc_dev 分配nullb_device,并初始化nullb_device的成员


4. register_blkdev完成了nullb块设备的注册,实际是添加到全局major_names数组中


5. null_add_dev为null设备创建gendisk并注册进系统

static int null_add_dev(struct nullb_device *dev) 
    |--struct nullb *nullb;
    |--null_validate_conf(dev)
    |--nullb = kzalloc_node(sizeof(*nullb), GFP_KERNEL, dev->home_node);
    |  nullb->dev = dev;
    |  dev->nullb = nullb
    |
    |--setup_queues(nullb)
    |--if (dev->queue_mode == NULL_Q_MQ)
    |      null_init_tag_set(nullb, nullb->tag_set)
    |      nullb->q = blk_mq_init_queue_data(nullb->tag_set, nullb);
    |  else if (dev->queue_mode == NULL_Q_BIO)
    |      nullb->q = blk_alloc_queue(dev->home_node);
    |      rv = init_driver_queues(nullb);
    |--nullb->q->queuedata = nullb;
    |--blk_queue_logical_block_size(nullb->q, dev->blocksize)
    |--blk_queue_physical_block_size(nullb->q, dev->blocksize)
    |--null_config_discard(nullb);
    \--null_gendisk_register(nullb)
        |--struct gendisk *disk;
        |--disk = nullb->disk = alloc_disk_node(1, nullb->dev->home_node);
        |--set_capacity(disk, size);
        |--if (queue_is_mq(nullb->q))
        |      disk->fops              = &null_rq_ops;
        |  else
        |      disk->fops              = &null_bio_ops;
        \--add_disk(disk)


null_add_dev创建tag set并初始化,创建软硬件队列及其映射关系,为null设备创建request_queue并初始化,为null设备创建gendisk并注册进系统    


1. setup_queues主要为nullb_queue分配空间,队列个数由nullb->dev->submit_queues来定义
  同时也设置了队列深度

2,null_init_tag_set分配了tag set,并对其进行初始化,为mq_map分配空间,用于保存软硬队列的映射关系建立软硬件队列的映射关系,

set->mq_map数组下标为cpu编号,数组元素为cpu编号所保存的硬队列号。根据队列深度为每个硬件队列分配bitmap,创建request。

 

3.blk_mq_init_queue_data创建request_queue并对其进行初始化,这里只考虑了多队列的情况

4. null_gendisk_register注册nullb磁盘设备。首先alloc_disk_node分配gendisk,add_disk 将gendisk注册进系统

|- -null_init_tag_set

null_init_tag_set
    \--blk_mq_alloc_tag_set(set)
        |--set->tags =kcalloc_node(nr_cpu_ids, sizeof(struct blk_mq_tags *)
        |--set->mq_map = kcalloc_node(nr_cpu_ids, sizeof(*set->mq_map)
        |--blk_mq_update_queue_map(set)
        |    \--blk_mq_map_queues(set)
        \--blk_mq_alloc_rq_maps(set)

null_init_tag_set分配了tag set,并对其进行初始化,为mq_map分配空间,用于保存软硬队列的映射关系; 建立软硬件队列的映射关系,set->mq_map数组下标为cpu编号,数组元素为cpu编号所对应的硬队列号。根据队列深度为每个硬件队列分配bitmap,并创建request


# blk_mq_alloc_tag_set:分配tag set,此tag set将与一个或多个request_queue进行关联,在分配过程过程中可能由于内存不足而失败,这样就要通过调整queue depth来进行调整后,重新分配,并更新queue depth
 ## kcalloc_node分配nr_cpu_ids个struct blk_mq_tags *并将地址赋值给set->tags
 ## kcalloc_node分配nr_cpu_ids个大小的set->mq_map数组,用于保存软硬件队列的映射关系
 ## blk_mq_update_queue_map 主要通过调用blk_mq_map_queues来建立的软硬件队列的映射关系
 ## blk_mq_alloc_rq_maps:对每个硬件队列,根据队列深度来分配tag bitmap和request

blk_mq_alloc_rq_maps(set)
    \--__blk_mq_alloc_rq_maps
        \--for (i = 0; i < set->nr_hw_queues; i++)
            \--__blk_mq_alloc_rq_map(set, i) 
                 |--blk_mq_alloc_rq_map(set, hctx_idx,queue_depth, reserved_tags)
                 |    |--blk_mq_init_tags(nr_tags, reserved_tags, node)
                 |    |    \--blk_mq_init_bitmap_tags(tags, node, alloc_policy)
                 |    |--tags->rqs = kcalloc_node(nr_tags, sizeof(struct request *)
                 |    \--tags->static_rqs = kcalloc_node(nr_tags, sizeof(struct request *)
                 \--blk_mq_alloc_rqs(set, set->tags[hctx_idx], hctx_idx,queue_depth)

blk_mq_alloc_rq_maps:对每个硬件队列,根据队列深度来分配tag bitmap和request


# __blk_mq_alloc_rq_maps:通过for循环对每个硬件队列,根据队列深度来分配tag bitmap和request
 ##__blk_mq_alloc_rq_map: 根据队列深度分配bitmap和request
  ### blk_mq_alloc_rq_map:分配blk_mq_tags结构体, 并初始化, 根据队列深度分配sbitmap
   #### blk_mq_init_tags: 分配blk_mq_tags结构体, 并初始化tags->nr_tags和tags->nr_reserved_tags,同时根据队列深度分配sbitmap
    ##### blk_mq_init_bitmap_tags:根据队列深度set->queue_depth来分配bitmap,每个bit代表一个tag标记,用于标示硬件队列中的request
  ### blk_mq_alloc_rqs:根据队列深度分配request, 分配的request最终保存到tags->static_rqs[i]。注意此处分配request时,同时也分配了driver payload的空间用于存放cmd


|- -blk_mq_init_queue

blk_mq_init_queue
    |--blk_alloc_queue_node(GFP_KERNEL, set->numa_node, NULL)
    \--blk_mq_init_allocated_queue(set, uninit_q)

blk_mq_init_queue多队列初始化,创建并初始化request_queue, 创建并初始化软硬队列,建立软硬件队列的映射关系


# blk_alloc_queue_node:分配一个reque_queue。其中lock参数为NULL,对于传统的单队列,为了保证对队列进行串行操作,因此需要加锁,对于多队列由于每个cpu core都有自己的软队列,因此不需要加锁。
# blk_mq_init_allocated_queue:初始化创建的request_queue

blk_alloc_queue_node
    |--q = kmem_cache_alloc_node(blk_requestq_cache)
    |--bioset_init(&q->bio_split, BIO_POOL_SIZE, 0, BIOSET_NEED_BVECS)
    |--bdi_alloc_node(gfp_mask, node_id)
    |--blk_alloc_queue_stats()
    |--timer_setup(&q->backing_dev_info->laptop_mode_wb_timer,laptop_mode_timer_fn)
    |--timer_setup(&q->timeout, blk_rq_timed_out_timer, 0)
    |-- INIT_WORK(&q->timeout_work, NULL);
    |--INIT_DELAYED_WORK(&q->delay_work, blk_delay_work)
    \--if (!q->mq_ops)
       q->queue_lock = lock ? : &q->__queue_lock

 

blk_alloc_queue_node主要分配了一个request_queue, 并对其进行初始化


# kmem_cache_alloc_node从blk_requestq_cache的slab对象分配request_queue
# bioset_init:初始化request_queue的bio内存池描述符
# bdi_alloc_node:分配bdi
# struct blk_queue_stats:用于统计
#timer_setup:初始化q->backing_dev_info->laptop_mode_wb_timer,laptop_mode_timer_fn(TODO)
# timer_setup 设置timeout定时器,blk_rq_timed_out_timer(TODO)
# INIT_WORK:初始化q->timeout_work,此处work回调被置位NULL
#INIT_DELAYED_WORK:初始化q->delay_work,blk_delay_work(TODO)
# q->queue_lock = lock ? : &q->__queue_lock:根据是否是多队列,来决定访问队列时是否使用lock

 

blk_mq_init_allocated_queue
    |--blk_stat_alloc_callback
    |--q->queue_ctx = alloc_percpu(struct blk_mq_ctx)
    |--q->queue_hw_ctx = kcalloc_node(nr_cpu_ids, sizeof(*(q->queue_hw_ctx))
    |--q->mq_map = set->mq_map
    |--blk_mq_realloc_hw_ctxs(set, q)
    |--INIT_WORK(&q->timeout_work, blk_mq_timeout_work)
    |--INIT_DELAYED_WORK(&q->requeue_work, blk_mq_requeue_work)
    |--blk_queue_make_request(q, blk_mq_make_request)
    |--if (q->mq_ops->poll)
    |         q->poll_fn = blk_mq_poll
    |--if (set->ops->complete)
    |    blk_queue_softirq_done(q, set->ops->complete)
    |--blk_mq_init_cpu_queues(q, set->nr_hw_queues)
    |    \--for_each_possible_cpu(i)
    |            blk_mq_map_queue(q, i)
    |--blk_mq_add_queue_tag_set(set, q)
    |--blk_mq_map_swqueue(q)
    |--if (!(set->flags & BLK_MQ_F_NO_SCHED))
     \      elevator_init_mq(q)

 

blk_mq_init_allocated_queue:初始化创建的request_queue


# blk_stat_alloc_callback:分配并初始化统计回调函数描述符,统计回调函数针对request进行定时统计(TODO)
# alloc_percpu:分配软队列,软队列数目与cpu core的数目一致
# blk_mq_sysfs_init(q):初始化 q->mq_kobj和软队列的kobjects
# kcalloc_node:分配nr_cpu_ids个struct blk_mq_hw_ctx *类型的指针
# 之前通过null_init_tag_set分配的set->mq_map赋值给q->mq_map,它保存了软硬件队列的映射关系
# blk_mq_realloc_hw_ctxs:分配硬件队列,硬件队列个数为set->nr_hw_queues
# INIT_WORK(&q->timeout_work, blk_mq_timeout_work)(TODO)
# INIT_DELAYED_WORK(&q->requeue_work, blk_mq_requeue_work)(TODO)
# blk_queue_make_request(q, blk_mq_make_request):设置q->make_request_fn的回调为blk_mq_make_request。其中blk_queue_congestion_threshold设置拥塞状态开启和关闭的阀值。
# q->poll_fn = blk_mq_poll(TODO)
# blk_queue_softirq_done:设置软中断完成函数回调为set->ops->complete
# blk_mq_init_cpu_queues初始化软件队列,其中通过blk_mq_map_queue获取软件队列映射的硬件队列
# blk_mq_map_swqueue建立软硬队列的映射关系,与blk_mq_map_queues不同,blk_mq_map_queues是通过map数组通过索引和数组元素记录软硬件队列的映射关系,其中数组索引为软队列编号,数组元素为硬队列编号,且map数组保存在request_queue.mq_map中;blk_mq_map_swqueue是基于blk_mq_map_queues创建的映射关系,进一步将软队列描述符指针保存在硬队列描述符,将软队列映射到硬队列的映射号index_hw保存在软队列描述符,它也与软队列索引号相同。如双核,一个硬队列的情况下blk_mq_map_swqueue打印如下:
blk_mq_map_swqueue: hctx(bfa29000), ctx(80e7a9c0), q->nr_queues(2),hctx->nr_ctx(1),
ctx->index_hw(0),hctx->ctxs[0](80e7a9c0)
blk_mq_map_swqueue: hctx(bfa29000), ctx(80e829c0), q->nr_queues(2),hctx->nr_ctx(2),
ctx->index_hw(1),hctx->ctxs[1](80e829c0)
如上就显示了双核下有两个软队列(地址为80e7a9c0和80e829c0),都映射到同一个硬队列(地址为bfa29000)

#elevator_init_mq:如果支持调度器,就要调度器初始化

elevator_init_mq(q)
|--e = elevator_get(q, "mq-deadline", false)
\--blk_mq_init_sched(q, e)
    |--queue_for_each_hw_ctx(q, hctx, i)
    |       blk_mq_sched_alloc_tags(q, hctx, i)
    |       |--hctx->sched_tags = blk_mq_alloc_rq_map(set, hctx_idx, q->nr_requests...)
    |       \--blk_mq_alloc_rqs(set, hctx->sched_tags, hctx_idx, q->nr_requests)
    |--e->ops.mq.init_sched(q, e)
    |--blk_mq_debugfs_register_sched(q)
    \--queue_for_each_hw_ctx(q, hctx, i)
           if (e->ops.mq.init_hctx)
               e->ops.mq.init_hctx(hctx, i)
           blk_mq_debugfs_register_sched_hctx(q, hctx)

elevator_init_mq调度器初始化,默认采用mq-deadline调度器, 根据硬队列个数分配了tags保存在了hctx->sched_tags,根据硬队列深度分配req, 保存在hctx->sched_tags->static_rqs[i];对dd调度器进行初始化

# elevator_get: 默认是采用mq-deadline调度器;
# blk_mq_init_sched: 初始化调度器
## blk_mq_sched_alloc_tags: 对每个硬队列分配tags
### blk_mq_alloc_rq_map: 对每个硬件队列,根据队列深度来分配tag bitmap, 保存到hctx->sched_tags,注意与blk_mq_alloc_rq_maps时保存在hctx->tags有所区别
### blk_mq_alloc_rqs:根据队列深度分配request, 分配的request最终保存到tags->static_rqs[i]。注意此处分配request时,同时也分配了driver payload的空间用于存放cmd
### e->ops.mq.init_sched:为dd_init_queue,分配elevator_queue,deadline_data,并对deadline_data数据描述符进行初始化
### blk_mq_debugfs_register_sched:在sys/kernel/debug/下创建调试节点,对于null block为:/sys/kernel/debug/block/nullb0/sched
### e->ops.mq.init_hctx: deadline未实现
### blk_mq_debugfs_register_sched_hctx:在sys/kernel/debug/下创建调试节点,对于null block为
/sys/kernel/debug/block/nullb0/hctx0

2.总结

从前面的流程分析可以看出,null blk作为一个块设备,在初始化的时候最重要的是分配gendisk数据结构,并注册进系统;同时作为一个块设备必须分配创建派发队列request_queue, 同时作为多队列需要根据cpu core的数目来创建相应数目的软队列,并与实际个数的硬队列建立映射关系,如果在使能调度器的情况下,需要对调度器进行初始化,默认采用mq-deadline调度器

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值