当前位置: 首页 > news >正文

block层:5. 请求分配

请求相关

源码基于5.10

1. 分配请求

static struct request *__blk_mq_alloc_request(struct blk_mq_alloc_data *data)
{// 请求队列struct request_queue *q = data->q;// 电梯struct elevator_queue *e = q->elevator;u64 alloc_time_ns = 0;unsigned int tag;// 判断有无QUEUE_FLAG_RQ_ALLOC_TIME标志// 有的话记录分配的时间if (blk_queue_rq_alloc_time(q))alloc_time_ns = ktime_get_ns();// 不等待if (data->cmd_flags & REQ_NOWAIT)data->flags |= BLK_MQ_REQ_NOWAIT;// 有电梯if (e) {// 非刷新请求 && 调度器有limit_depth && 使用保留tag, 则调用调度器的限制队列深度的方法if (!op_is_flush(data->cmd_flags) &&e->type->ops.limit_depth &&!(data->flags & BLK_MQ_REQ_RESERVED))e->type->ops.limit_depth(data->cmd_flags, data);}retry:// 获取ctxdata->ctx = blk_mq_get_ctx(q);// 根据不同的请求返回hctxdata->hctx = blk_mq_map_queue(q, data->cmd_flags, data->ctx);// 没有调度器,则给hctx->state标记 BLK_MQ_S_TAG_ACTIVEif (!e)blk_mq_tag_busy(data->hctx);// 获取一个tagtag = blk_mq_get_tag(data);// 没tag了if (tag == BLK_MQ_NO_TAG) {// 不等待,直接返回NULLif (data->flags & BLK_MQ_REQ_NOWAIT)return NULL;// 睡3秒再重试msleep(3);goto retry;}// 初始化tag对应的请求return blk_mq_rq_ctx_init(data, tag, alloc_time_ns);
}static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data,unsigned int tag, u64 alloc_time_ns)
{// 获取data里的tagsstruct blk_mq_tags *tags = blk_mq_tags_from_data(data);// 取tag的静态请求struct request *rq = tags->static_rqs[tag];// 根据是否有电梯在不同的字段记录tag// todo: 有电梯时为啥要在internal_tag里记录if (data->q->elevator) {rq->tag = BLK_MQ_NO_TAG;rq->internal_tag = tag;} else {rq->tag = tag;rq->internal_tag = BLK_MQ_NO_TAG;}// 请求队列rq->q = data->q;// 软件队列rq->mq_ctx = data->ctx;// 硬件队列rq->mq_hctx = data->hctx;rq->rq_flags = 0;// 命令标志rq->cmd_flags = data->cmd_flags;// 运行时电源管理请求?if (data->flags & BLK_MQ_REQ_PM)rq->rq_flags |= RQF_PM;// 有io统计需要if (blk_queue_io_stat(data->q))rq->rq_flags |= RQF_IO_STAT;// 一些初始化INIT_LIST_HEAD(&rq->queuelist);INIT_HLIST_NODE(&rq->hash);RB_CLEAR_NODE(&rq->rb_node);rq->rq_disk = NULL;rq->part = NULL;
#ifdef CONFIG_BLK_RQ_ALLOC_TIME// 开始分配请求时的时间戳rq->alloc_time_ns = alloc_time_ns;
#endif// 需要记录请求开始时时间戳if (blk_mq_need_time_stamp(rq))rq->start_time_ns = ktime_get_ns();elserq->start_time_ns = 0;rq->io_start_time_ns = 0;rq->stats_sectors = 0;rq->nr_phys_segments = 0;
#if defined(CONFIG_BLK_DEV_INTEGRITY)rq->nr_integrity_segments = 0;
#endif// 加密blk_crypto_rq_set_defaults(rq);// deadline设置为0WRITE_ONCE(rq->deadline, 0);rq->timeout = 0;rq->end_io = NULL;rq->end_io_data = NULL;// ctx里请求派发的计数data->ctx->rq_dispatched[op_is_sync(data->cmd_flags)]++;// 引用置1refcount_set(&rq->ref, 1);// 如果不是刷新请求if (!op_is_flush(data->cmd_flags)) {struct elevator_queue *e = data->q->elevator;rq->elv.icq = NULL;// 如果电梯有prepare_request,则调用之if (e && e->type->ops.prepare_request) {// 如果有icq缓存,则创建icqif (e->type->icq_cache)blk_mq_sched_assign_ioc(rq);// 调度器的准备请求方法e->type->ops.prepare_request(rq);// rq有调度器的私有数据rq->rq_flags |= RQF_ELVPRIV;}}// 已入队的请求数加1data->hctx->queued++;return rq;
}static inline bool blk_mq_need_time_stamp(struct request *rq)
{// 有统计需要 || 有调度器return (rq->rq_flags & (RQF_IO_STAT | RQF_STATS)) || rq->q->elevator;
}

request_to_qc_t

static inline blk_qc_t request_to_qc_t(struct blk_mq_hw_ctx *hctx,struct request *rq)
{// queue_num是队列编号// BLK_QC_T_SHIFT是16// 无调度器时rq->tag != -1,用低16位存tag,高16位存队列数量if (rq->tag != -1)return rq->tag | (hctx->queue_num << BLK_QC_T_SHIFT);// BLK_QC_T_INTERNAL = (1U << 31),// 有调度器时tag保存在internal_tag里,和上面类似,低16位存tag,高16位存队列数量,// 在第32位保存internal的标志return rq->internal_tag | (hctx->queue_num << BLK_QC_T_SHIFT) |BLK_QC_T_INTERNAL;
}

把bio转换成请求

static void blk_mq_bio_to_request(struct request *rq, struct bio *bio,unsigned int nr_segs)
{int err;// 如果是预读的话,加上快速失败标志if (bio->bi_opf & REQ_RAHEAD)rq->cmd_flags |= REQ_FAILFAST_MASK;// 请求扇区的起点设置为bio的扇区起点rq->__sector = bio->bi_iter.bi_sector;// todo: what?rq->write_hint = bio->bi_write_hint;// 把bio里的一些读写信息写到请求里blk_rq_bio_prep(rq, bio, nr_segs);// todo: 加密相关后面再看err = blk_crypto_rq_bio_prep(rq, bio, GFP_NOIO);WARN_ON_ONCE(err);// 统计相关blk_account_io_start(rq);
}static inline void blk_rq_bio_prep(struct request *rq, struct bio *bio,unsigned int nr_segs)
{// 段数rq->nr_phys_segments = nr_segs;// 请求的数据长度rq->__data_len = bio->bi_iter.bi_size;// 请求的头、尾bio都指向这个biorq->bio = rq->biotail = bio;// 设置io优先级?rq->ioprio = bio_prio(bio);// 设置请求的磁盘if (bio->bi_disk)rq->rq_disk = bio->bi_disk;
}void blk_account_io_start(struct request *rq)
{// 没有统计io的需要直接返回if (!blk_do_io_stat(rq))return;// 把扇区转换成对应的分区rq->part = disk_map_sector_rcu(rq->rq_disk, blk_rq_pos(rq));part_stat_lock();update_io_ticks(rq->part, jiffies, false);part_stat_unlock();
}static inline bool blk_do_io_stat(struct request *rq)
{return rq->rq_disk && (rq->rq_flags & RQF_IO_STAT);
}static void update_io_ticks(struct hd_struct *part, unsigned long now, bool end)
{unsigned long stamp;
again:// 分区时间戳stamp = READ_ONCE(part->stamp);// 更新分区时间戳if (unlikely(stamp != now)) {if (likely(cmpxchg(&part->stamp, stamp, now) == stamp))__part_stat_add(part, io_ticks, end ? now - stamp : 1);}// 更新0号分区时间戳if (part->partno) {part = &part_to_disk(part)->part0;goto again;}
}

插入请求

插入请求是给调度器或者ctx里插入

插入刷新请求

void blk_insert_flush(struct request *rq)
{struct request_queue *q = rq->q;// 队列标志unsigned long fflags = q->queue_flags;// 刷新策略unsigned int policy = blk_flush_policy(fflags, rq);// 获取hctx对应的flush_queuestruct blk_flush_queue *fq = blk_get_flush_queue(q, rq->mq_ctx);// 清除REQ_PREFLUSH和REQ_FUA标志,因为policy里已经记录了rq->cmd_flags &= ~REQ_PREFLUSH;if (!(fflags & (1UL << QUEUE_FLAG_FUA)))rq->cmd_flags &= ~REQ_FUA;// 刷新请求是同步的rq->cmd_flags |= REQ_SYNC;// 没有策略的直接结束请求if (!policy) {blk_mq_end_request(rq, 0);return;}// 没有bio或者只能有一个BUG_ON(rq->bio != rq->biotail);// 如果有数据,没有刷新需求。这个请求就可以直接处理不用经过刷新机制。if ((policy & REQ_FSEQ_DATA) &&!(policy & (REQ_FSEQ_PREFLUSH | REQ_FSEQ_POSTFLUSH))) {// 绕过插入blk_mq_request_bypass_insert(rq, false, false);return;}/** @rq should go through flush machinery.  Mark it part of flush* sequence and submit for further processing.*/// 初始化请求的刷新对象memset(&rq->flush, 0, sizeof(rq->flush));INIT_LIST_HEAD(&rq->flush.list);// 顺序刷新?rq->rq_flags |= RQF_FLUSH_SEQ;// 保存endiorq->flush.saved_end_io = rq->end_io;// 设置flush的回调rq->end_io = mq_flush_data_end_io;spin_lock_irq(&fq->mq_flush_lock);// 刷出请求, REQ_FSEQ_ACTIONS = REQ_FSEQ_PREFLUSH | REQ_FSEQ_DATA | REQ_FSEQ_POSTFLUSHblk_flush_complete_seq(rq, fq, REQ_FSEQ_ACTIONS & ~policy, 0);spin_unlock_irq(&fq->mq_flush_lock);
}static unsigned int blk_flush_policy(unsigned long fflags, struct request *rq)
{unsigned int policy = 0;// 请求里有数据,这个获取的是数据长度对应的扇区数if (blk_rq_sectors(rq))policy |= REQ_FSEQ_DATA;// 如果有writeback cachingif (fflags & (1UL << QUEUE_FLAG_WC)) {// 预刷新?if (rq->cmd_flags & REQ_PREFLUSH)policy |= REQ_FSEQ_PREFLUSH;// flag没有fua, 但是cmd有fuaif (!(fflags & (1UL << QUEUE_FLAG_FUA)) &&(rq->cmd_flags & REQ_FUA))policy |= REQ_FSEQ_POSTFLUSH;}return policy;
}
static void blk_flush_complete_seq(struct request *rq,struct blk_flush_queue *fq,unsigned int seq, blk_status_t error)
{struct request_queue *q = rq->q;struct list_head *pending = &fq->flush_queue[fq->flush_pending_idx];unsigned int cmd_flags;BUG_ON(rq->flush.seq & seq);// 设置seq?rq->flush.seq |= seq;// 命令标志cmd_flags = rq->cmd_flags;if (likely(!error))// 计算rq的顺序seq = blk_flush_cur_seq(rq);elseseq = REQ_FSEQ_DONE;switch (seq) {case REQ_FSEQ_PREFLUSH:case REQ_FSEQ_POSTFLUSH:// 预刷出或已经刷出// 如果pending是空的,则记录pending时间if (list_empty(pending))fq->flush_pending_since = jiffies;// 把rq移到pending末尾list_move_tail(&rq->flush.list, pending);break;case REQ_FSEQ_DATA:// 有数据刷出// 把rq移到 flush_data_in_flight 队列list_move_tail(&rq->flush.list, &fq->flush_data_in_flight);// 添加到运行队列里blk_flush_queue_rq(rq, true);break;case REQ_FSEQ_DONE:// 请求已经执行完了BUG_ON(!list_empty(&rq->queuelist));// 从flush里删除list_del_init(&rq->flush.list);// 先还原成正常请求之前的数据blk_flush_restore_request(rq);// 结束请求blk_mq_end_request(rq, error);break;default:BUG();}// 刷出请求blk_kick_flush(q, fq, cmd_flags);
}static unsigned int blk_flush_cur_seq(struct request *rq)
{// ffz是找第一个0的位置return 1 << ffz(rq->flush.seq);
}static void blk_flush_queue_rq(struct request *rq, bool add_front)
{// 添加到队列里blk_mq_add_to_requeue_list(rq, add_front, true);
}void blk_mq_add_to_requeue_list(struct request *rq, bool at_head,bool kick_requeue_list)
{struct request_queue *q = rq->q;unsigned long flags;// 不能有这个标志BUG_ON(rq->rq_flags & RQF_SOFTBARRIER);spin_lock_irqsave(&q->requeue_lock, flags);if (at_head) {// 添加在头部,设置这个标志,这个标志是:io调度器可能无法传递rq->rq_flags |= RQF_SOFTBARRIER;// 加到队列前面list_add(&rq->queuelist, &q->requeue_list);} else {// 加到队列末尾list_add_tail(&rq->queuelist, &q->requeue_list);}spin_unlock_irqrestore(&q->requeue_lock, flags);// 如果需要唤醒队列,则唤醒之if (kick_requeue_list)blk_mq_kick_requeue_list(q);
}void blk_mq_kick_requeue_list(struct request_queue *q)
{// 运行kblockd_workqueue。第1个参数是指定cpu,最后一个参数是延迟时间// 延迟为0表示立即开始kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &q->requeue_work, 0);
}static void blk_flush_restore_request(struct request *rq)
{// 刷新完bio是NULL,我们应该还原它rq->bio = rq->biotail;// 去除刷出请求rq->rq_flags &= ~RQF_FLUSH_SEQ;// 还原之前的回调函数rq->end_io = rq->flush.saved_end_io;
}static void blk_kick_flush(struct request_queue *q, struct blk_flush_queue *fq,unsigned int flags)
{// 待刷出请求队列?struct list_head *pending = &fq->flush_queue[fq->flush_pending_idx];// 第1个请求struct request *first_rq =list_first_entry(pending, struct request, flush.list);struct request *flush_rq = fq->flush_rq;// 同时只能有一个运行或者待刷出队列是空的if (fq->flush_pending_idx != fq->flush_running_idx || list_empty(pending))return;// 有正在运行的,并且没有超过5秒if (!list_empty(&fq->flush_data_in_flight) &&time_before(jiffies,// FLUSH_PENDING_TIMEOUT是5秒fq->flush_pending_since + FLUSH_PENDING_TIMEOUT))return;// 切换pending_idxfq->flush_pending_idx ^= 1;// 初始化flush_rq请求blk_rq_init(q, flush_rq);// 使用第1个请求的上下文flush_rq->mq_ctx = first_rq->mq_ctx;flush_rq->mq_hctx = first_rq->mq_hctx;if (!q->elevator) {// 没有调度器flush_rq->tag = first_rq->tag;/** 这个标记防止多次统计*/flush_rq->rq_flags |= RQF_MQ_INFLIGHT;} else// 有调度器flush_rq->internal_tag = first_rq->internal_tag;// REQ_OP_FLUSH:刷出易失缓存flush_rq->cmd_flags = REQ_OP_FLUSH | REQ_PREFLUSH;// REQ_DRV:需要驱动? REQ_FAILFAST_MASK: 快速结束flush_rq->cmd_flags |= (flags & REQ_DRV) | (flags & REQ_FAILFAST_MASK);// 顺序刷出flush_rq->rq_flags |= RQF_FLUSH_SEQ;// 磁盘flush_rq->rq_disk = first_rq->rq_disk;// 结束回调flush_rq->end_io = flush_end_io;smp_wmb();// 设引用为1refcount_set(&flush_rq->ref, 1);// 刷出,把请求添加列队列里blk_flush_queue_rq(flush_rq, false);
}void blk_rq_init(struct request_queue *q, struct request *rq)
{memset(rq, 0, sizeof(*rq));INIT_LIST_HEAD(&rq->queuelist);// 设置队列rq->q = q;rq->__sector = (sector_t) -1;INIT_HLIST_NODE(&rq->hash);RB_CLEAR_NODE(&rq->rb_node);// 没有tagrq->tag = BLK_MQ_NO_TAG;rq->internal_tag = BLK_MQ_NO_TAG;// 开始时间rq->start_time_ns = ktime_get_ns();rq->part = NULL;// 加密blk_crypto_rq_set_defaults(rq);
}

批量插入

这个函数主要在flush_plug时调用,它会把plug里的请求,按hctx排序来按hctx刷出.

void blk_mq_sched_insert_requests(struct blk_mq_hw_ctx *hctx,struct blk_mq_ctx *ctx,struct list_head *list, bool run_queue_async)
{struct elevator_queue *e;struct request_queue *q = hctx->queue;// 增加引用计数,怕在使用期间队列被释放了percpu_ref_get(&q->q_usage_counter);// 获取调度器e = hctx->queue->elevator;if (e && e->type->ops.insert_requests)// 有电梯 && 有插入请求函数,则调用之e->type->ops.insert_requests(hctx, list, false);else {// 没有调度器或者调度器没有insert函数的话就调用通用函数// 队列不忙 && 没有电梯 && 不是异步,直接发布// 这样能给软队列节省一个入队或出队请求if (!hctx->dispatch_busy && !e && !run_queue_async) {// 直接发布请求blk_mq_try_issue_list_directly(hctx, list);// 链表空了,则退出if (list_empty(list))goto out;}// 把list里剩余的元素插入队列blk_mq_insert_requests(hctx, ctx, list);}// 运行队列blk_mq_run_hw_queue(hctx, run_queue_async);out:// 减少引用percpu_ref_put(&q->q_usage_counter);
}void blk_mq_insert_requests(struct blk_mq_hw_ctx *hctx, struct blk_mq_ctx *ctx,struct list_head *list){struct request *rq;enum hctx_type type = hctx->type;// 这个打印插入的tracelist_for_each_entry(rq, list, queuelist) {// 原注释:抢占不会刷新列表头,所以ctx->cpu可能已经下线?BUG_ON(rq->mq_ctx != ctx);trace_block_rq_insert(hctx->queue, rq);}spin_lock(&ctx->lock);// 把list添加到软队列的末尾list_splice_tail_init(list, &ctx->rq_lists[type]);// 标记软队列有准备提交的请求blk_mq_hctx_mark_pending(hctx, ctx);spin_unlock(&ctx->lock);
}static void blk_mq_hctx_mark_pending(struct blk_mq_hw_ctx *hctx,struct blk_mq_ctx *ctx)
{const int bit = ctx->index_hw[hctx->type];// 标记软位图,这个标记之后就表示有准备提交的请求if (!sbitmap_test_bit(&hctx->ctx_map, bit))sbitmap_set_bit(&hctx->ctx_map, bit);
}

批量插入的主要流程很简单:

  1. 把list里的请求全部加到软队列里
  2. 标记软队列对应hctx的位图,表示这个软队列有请求需要提交
  3. 运行队列

调度器插入请求

void blk_mq_sched_insert_request(struct request *rq, bool at_head,bool run_queue, bool async)
{struct request_queue *q = rq->q;struct elevator_queue *e = q->elevator;struct blk_mq_ctx *ctx = rq->mq_ctx;struct blk_mq_hw_ctx *hctx = rq->mq_hctx;// 有调度器时,rq->tag应该为空,tag在internal_tag里保存WARN_ON(e && (rq->tag != BLK_MQ_NO_TAG));// 判断是否要绕过插入直接发布, bypass的条件是flush或passthrough请求if (blk_mq_sched_bypass_insert(hctx, !!e, rq)) {// 如果是刷新请求则放到队头at_head = (rq->rq_flags & RQF_FLUSH_SEQ) ? true : at_head;// 直接插入到hctx的派发队列里,最后一个参数表示是否运行队列blk_mq_request_bypass_insert(rq, at_head, false);goto run;}// 如果没有直接发布就加到软队列里if (e && e->type->ops.insert_requests) {// 有调度器 && 有insert_requests方法,则调用之LIST_HEAD(list);list_add(&rq->queuelist, &list);e->type->ops.insert_requests(hctx, &list, at_head);} else {// 否则调用通用方法插入请求spin_lock(&ctx->lock);// 这个会把请求插到软队列里__blk_mq_insert_request(hctx, rq, at_head);spin_unlock(&ctx->lock);}run:// 若需运行,则执行请求if (run_queue)blk_mq_run_hw_queue(hctx, async);
}

直接发布

static bool blk_mq_sched_bypass_insert(struct blk_mq_hw_ctx *hctx,bool has_sched,struct request *rq)
{// RQF_FLUSH_SEQ是顺序刷出// blk_rq_is_passthrough判断是否是REQ_OP_SCSI_IN/OUT,REQ_OP_DRV_IN/OUT这4种之一的请求// 这2种情况绕过插入,直接发出请求if ((rq->rq_flags & RQF_FLUSH_SEQ) || blk_rq_is_passthrough(rq))return true;// 有调度器,则需要排序if (has_sched)rq->rq_flags |= RQF_SORTED;return false;
}void blk_mq_request_bypass_insert(struct request *rq, bool at_head,bool run_queue)
{struct blk_mq_hw_ctx *hctx = rq->mq_hctx;spin_lock(&hctx->lock);// 添加到硬件队列的派发队列,// 加到dispatch队列后,在运行队列时,会优先处理if (at_head)list_add(&rq->queuelist, &hctx->dispatch);elselist_add_tail(&rq->queuelist, &hctx->dispatch);spin_unlock(&hctx->lock);// 如需运行硬件队列,则运行之if (run_queue)blk_mq_run_hw_queue(hctx, false);
}

插入软队列

void __blk_mq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,bool at_head)
{struct blk_mq_ctx *ctx = rq->mq_ctx;lockdep_assert_held(&ctx->lock);// 把请求插到队列里__blk_mq_insert_req_list(hctx, rq, at_head);// 标记hctx有待处理的请求blk_mq_hctx_mark_pending(hctx, ctx);
}static inline void __blk_mq_insert_req_list(struct blk_mq_hw_ctx *hctx,struct request *rq,bool at_head)
{struct blk_mq_ctx *ctx = rq->mq_ctx;enum hctx_type type = hctx->type;lockdep_assert_held(&ctx->lock);trace_block_rq_insert(hctx->queue, rq);// 根据at_head的值,加到软队列头或者队列尾if (at_head)list_add(&rq->queuelist, &ctx->rq_lists[type]);elselist_add_tail(&rq->queuelist, &ctx->rq_lists[type]);
}
http://www.lryc.cn/news/141469.html

相关文章:

  • L1-038 新世界(Python实现) 测试点全过
  • 【hello git】初识Git
  • Vueelementui动态渲染Radio,Checkbox,笔记
  • SpringDataRedis 使用
  • Redis全局命令与数据结构
  • LibreOffice新一代的办公软件for Mac/Windows免费版
  • Python|OpenCV-读取视频,显示视频并保存视频(3)
  • 上传WSL项目到gitlab
  • 从0开始做yolov5模型剪枝
  • 飞天使-k8s基础组件分析-安全
  • Mysql安装使用
  • 聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化
  • uniapp 使用 mui-player 插件播放 m3u8/flv 视频流
  • 大数据课程K4——Spark的DAGRDD依赖关系
  • disable 禁用元素后无法触发点击事件
  • uni-app开启gzip配置
  • 房屋结构健康监测,科技助力让建筑更安全
  • Android 面试之Glide做了哪些优化?
  • 【韩顺平 零基础30天学会Java】数组、排序和查找(2days)
  • VUE笔记(一)初识vue
  • 3D点云处理:学习总结(更新整理中)
  • Day45|leetcode 70. 爬楼梯、322. 零钱兑换、279.完全平方数
  • arm:day9
  • 【大模型AIGC系列课程 1-2】创建并部署自己的ChatGPT机器人
  • 启动metastore服务报错
  • c 语言 算法 技巧 之 用移位来代替乘除
  • python爬虫实战零基础(3)——某云音乐
  • 渗透测试漏洞原理之---【XSS 跨站脚本攻击】
  • 【浮点数二分】
  • 基于FPGA的FIR低通滤波器实现(附工程源码),matlab+vivado19.2+simulation