操作系统学习-3.Linux文件系统学习3-io的plug过程

2023-12-17 21:20

本文主要是介绍操作系统学习-3.Linux文件系统学习3-io的plug过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

io的plug过程

  • io的plug过程:启动篇
  • io的plug过程:request请求
  • io的plug过程:blk_init_queue
    • 1、这个request_count 个数的统计
    • 2、blk_flush_plug_list进行泄洪我们还要继续看
  • io的plug过程:blk_flush_plug_list的情况
  • io的plug过程:queuelist的问题

io的plug过程:启动篇

include/linux/blkdev.h
struct blk_plug {struct list_head list; /* requests */struct list_head mq_list; /* blk-mq requests */struct list_head cb_list; /* md requires an unplug callback */
};

这个是plug过程的主要结构体

block/blk-core.c
void blk_start_plug(struct blk_plug *plug)
{struct task_struct *tsk = current;if (tsk->plug)return;INIT_LIST_HEAD(&plug->list);INIT_LIST_HEAD(&plug->mq_list);INIT_LIST_HEAD(&plug->cb_list);tsk->plug = plug;
}

这个是主要的函数

接下来我们将分析plug的过程

io的plug过程:request请求

其实还是从上次的请求开始继续分析的,

图片
图片

从应用的角度来说,请求一个bio 最终会调到这里。make_request_fn

而从驱动的角度来说,我们说有两种:

blk_init_queue

blk_queue_make_request

这个就形成一种闭环。

这两个接口都是设置应用上次的请求的接口函数的,

我从blk_init_queue 去分析,发现最后还是blk_queue_make_request,只是中间加了一层自己的blk_queue_bio。

io的plug过程:blk_init_queue

struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);
}struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{struct request_queue *uninit_q, *q;uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);if (!uninit_q)return NULL;q = blk_init_allocated_queue(uninit_q, rfn, lock);if (!q)blk_cleanup_queue(uninit_q);return q;
}struct request_queue *
blk_init_allocated_queue(struct request_queue *q, request_fn_proc *rfn,spinlock_t *lock)
{if (!q)return NULL;q->fq = blk_alloc_flush_queue(q, NUMA_NO_NODE, 0);if (!q->fq)return NULL;if (blk_init_rl(&q->root_rl, q, GFP_KERNEL))goto fail;q->request_fn    = rfn;q->prep_rq_fn    = NULL;q->unprep_rq_fn    = NULL;q->queue_flags    |= QUEUE_FLAG_DEFAULT;if (lock)q->queue_lock    = lock;blk_queue_make_request(q, blk_queue_bio);q->sg_reserved_size = INT_MAX;mutex_lock(&q->sysfs_lock);if (elevator_init(q, NULL)) {mutex_unlock(&q->sysfs_lock);goto fail;}mutex_unlock(&q->sysfs_lock);return q;fail:blk_free_flush_queue(q->fq);return NULL;
}

这里就是可以看出两种模式的区分了,很明显了,至于blk_queue_make_request里面当然是一个队里loop类个实现,这里就不求甚解了。

这里如果是自己blk_queue_make_request就是没有任何调度和蓄洪可言了。就直接的到驱动了。

如果用blk_queue_bio转一下就会有蓄洪,和电梯。下面我看下blk_queue_bio

static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio)
{const bool sync = !!(bio->bi_rw & REQ_SYNC);struct blk_plug *plug;int el_ret, rw_flags, where = ELEVATOR_INSERT_SORT;struct request *req;unsigned int request_count = 0;blk_queue_bounce(q, &bio);blk_queue_split(q, &bio, q->bio_split);if (bio_integrity_enabled(bio) && bio_integrity_prep(bio)) {bio->bi_error = -EIO;bio_endio(bio);return BLK_QC_T_NONE;}if (bio->bi_rw & (REQ_FLUSH | REQ_FUA)) {spin_lock_irq(q->queue_lock);where = ELEVATOR_INSERT_FLUSH;goto get_rq;}
//nomerge 是如果没有合并的要求if (!blk_queue_nomerges(q)) {if (blk_attempt_plug_merge(q, bio, &request_count, NULL))return BLK_QC_T_NONE;} elserequest_count = blk_plug_queued_count(q);//统计这个队列里面的request个数spin_lock_irq(q->queue_lock);el_ret = elv_merge(q, &req, bio);//这函数是梳理电梯的,请求,该合并的合并该链接在一块的连接在一块if (el_ret == ELEVATOR_BACK_MERGE) {if (bio_attempt_back_merge(q, req, bio)) {elv_bio_merged(q, req, bio);if (!attempt_back_merge(q, req))elv_merged_request(q, req, el_ret);goto out_unlock;}} else if (el_ret == ELEVATOR_FRONT_MERGE) {if (bio_attempt_front_merge(q, req, bio)) {elv_bio_merged(q, req, bio);if (!attempt_front_merge(q, req))elv_merged_request(q, req, el_ret);goto out_unlock;}}get_rq:rw_flags = bio_data_dir(bio);if (sync)rw_flags |= REQ_SYNC;req = get_request(q, rw_flags, bio, GFP_NOIO);if (IS_ERR(req)) {bio->bi_error = PTR_ERR(req);bio_endio(bio);goto out_unlock;}init_request_from_bio(req, bio);if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags))req->cpu = raw_smp_processor_id();plug = current->plug;if (plug) {if (!request_count)trace_block_plug(q);else {if (request_count >= BLK_MAX_REQUEST_COUNT) {//如果这个队列的个数太多了,就通过下面的blk_flush_plug_list进行泄洪blk_flush_plug_list(plug, false);trace_block_plug(q);}}list_add_tail(&req->queuelist, &plug->list);//如果这个队列还不是很多,就继续加入到尾部。blk_account_io_start(req, true);} else {spin_lock_irq(q->queue_lock);add_acct_request(q, req, where);__blk_run_queue(q);
out_unlock:spin_unlock_irq(q->queue_lock);}return BLK_QC_T_NONE;
}

这里其实还不是很清楚 蓄洪和泄洪,只是看到了一些框架。

1、这个request_count 个数的统计

unsigned int blk_plug_queued_count(struct request_queue *q)
{
...plug = current->plug;
...if (q->mq_ops)plug_list = &plug->mq_list;elseplug_list = &plug->list;list_for_each_entry(rq, plug_list, queuelist) {if (rq->q == q)ret++;}
out:return ret;
}

就是在统计这个list的个数,那么这个list是没有实际意义的,只是统计个数,为什么不用int去做。

2、blk_flush_plug_list进行泄洪我们还要继续看

void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule)
{struct request_queue *q;unsigned long flags;struct request *rq;LIST_HEAD(list);unsigned int depth;flush_plug_callbacks(plug, from_schedule);if (!list_empty(&plug->mq_list))blk_mq_flush_plug_list(plug, from_schedule);if (list_empty(&plug->list))return;list_splice_init(&plug->list, &list);list_sort(NULL, &list, plug_rq_cmp);q = NULL;depth = 0;local_irq_save(flags);while (!list_empty(&list)) {rq = list_entry_rq(list.next);list_del_init(&rq->queuelist);BUG_ON(!rq->q);if (rq->q != q) {if (q)queue_unplugged(q, depth, from_schedule);q = rq->q;depth = 0;spin_lock(q->queue_lock);}if (unlikely(blk_queue_dying(q))) {__blk_end_request_all(rq, -ENODEV);continue;}if (rq->cmd_flags & (REQ_FLUSH | REQ_FUA))__elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH);else__elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE);depth++;}if (q)queue_unplugged(q, depth, from_schedule);local_irq_restore(flags);
}

就是将list的各个元素拿出来加入到电梯队列里面去,电梯怎么取呢?
在这里插入图片描述

这个是电梯的接口了,我们下次再关注这些电梯的算法接口。

现在问题是这些**__elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH)**; rq是从list里面取出来的,是哪里加进去的呢?

我们在上面好像没有看到。

io的plug过程:blk_flush_plug_list的情况

在上面中我们看到

if (plug) {if (!request_count)trace_block_plug(q);else {if (request_count >= BLK_MAX_REQUEST_COUNT) {//如果这个队列的个数太多了,就通过下面的blk_flush_plug_list进行泄洪blk_flush_plug_list(plug, false);trace_block_plug(q);}}

这个是在blk_queue_bio 中,也就是在提交请求的情况下,会调用blk_flush_plug_list 来unplug操作。
今天看看另外一种unplug操作,
调度时进行unplug(异步方式)
当发生内核调度时,当前进程sleep前,先将当前task的plug列表中的请求flush到派发队列中,并进行unplug。
主要代码流程如下:

schedule->sched_submit_work ->blk_schedule_flush_plug()->blk_flush_plug_list(plug, true) ->注意:这里传入的from_schedule参数为true,表示将触发异步unplug,即唤醒kblockd工作队列来进行unplug操作。后续的kblockd的唤醒周期在块设备驱动中设置,比如scsi中设置为3ms。

queue_unplugged-> blk_run_queue_async

queue_unplugged():void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule)
{struct request_queue *q;unsigned long flags;struct request *rq;LIST_HEAD(list);unsigned int depth;flush_plug_callbacks(plug, from_schedule);if (!list_empty(&plug->mq_list))blk_mq_flush_plug_list(plug, from_schedule);if (list_empty(&plug->list))return;list_splice_init(&plug->list, &list);list_sort(NULL, &list, plug_rq_cmp);q = NULL;depth = 0;local_irq_save(flags);while (!list_empty(&list)) {rq = list_entry_rq(list.next);list 里面存放的就是要执行的rq,每一个遍历出来。这个list_del_init(&rq->queuelist);BUG_ON(!rq->q);if (rq->q != q) {/** This drops the queue lock*/if (q)queue_unplugged(q, depth, from_schedule);q = rq->q;depth = 0;spin_lock(q->queue_lock);}/** Short-circuit if @q is dead*/if (unlikely(blk_queue_dying(q))) {__blk_end_request_all(rq, -ENODEV);continue;}/** rq is already accounted, so use raw insert*/if (rq->cmd_flags & (REQ_FLUSH | REQ_FUA))__elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH);else__elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE);depth++;}static void queue_unplugged(struct request_queue *q, unsigned int depth,bool from_schedule)__releases(q->queue_lock)
{if (from_schedule)blk_run_queue_async(q);else__blk_run_queue(q);spin_unlock(q->queue_lock);
}

根据from_schedule 进行同步操作或者异步

void blk_run_queue_async(struct request_queue *q)
{if (likely(!blk_queue_stopped(q) && !blk_queue_dead(q)))mod_delayed_work(kblockd_workqueue, &q->delay_work, 0);
}

这里面就调入一个异步时钟,到见了它还是会调用到__blk_run_queue,进行request的操作的。

这里面异步时钟的逻辑,它里面有一套,大概可以比喻成handle 的消息处理机制,这一套我们先不讨论。

我们先看下这个异步时钟是在哪里设置的,

blk_init_queue–》blk_init_queue_node–》blk_alloc_queue_node

就是在初始化的时候做的,

blk_init_queue–》blk_init_queue_node–》blk_alloc_queue_node

就是在初始化的时候做的,

struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)
{struct request_queue *q;int err;
.....setup_timer(&q->backing_dev_info.laptop_mode_wb_timer,laptop_mode_timer_fn, (unsigned long) q);setup_timer(&q->timeout, blk_rq_timed_out_timer, (unsigned long) q);INIT_DELAYED_WORK(&q->delay_work, blk_delay_work);
}

中间的时钟消息逻辑我们先不看,我们知道,到时间以后,它会执行blk_delay_work

static void blk_delay_work(struct work_struct *work)
{struct request_queue *q;q = container_of(work, struct request_queue, delay_work.work);spin_lock_irq(q->queue_lock);__blk_run_queue(q);spin_unlock_irq(q->queue_lock);
}

OK,最终还是到 __blk_run_queue,在进行下一步分析前我们先处理下逻辑。

就是一个list里面,有很多个bio请求,这些bio请求转换成驱动能认识的就是rq,什么时候对这个rq进行遍历,

一个就是实际请求的时候,同步策略,另外一个就是schedule的时候异步。

达到的条件是要达到MAX,我就开始遍历。每个rq。

rq的—>q,是由驱动设置进来的,q有可能有很多个驱动,如磁盘,usb或者其他block驱动

所以这里可以细化的,就是可以做一个list分类,每个驱动达到MAX,再来清理

那么我们作为学习,假设就是一个q来说,最终由 __blk_run_queue,送入到那个地方进行运行,而驱动就使用elv_next_request

拿取,rq。这里似乎还有点差距。我们在来看看__blk_run_queue,应该就没什么缝隙了。

void __blk_run_queue(struct request_queue *q)
{if (unlikely(blk_queue_stopped(q)))return;__blk_run_queue_uncond(q);
}inline void __blk_run_queue_uncond(struct request_queue *q)
{if (unlikely(blk_queue_dead(q)))return;q->request_fn_active++;q->request_fn(q);q->request_fn_active--;
}

OK,最终调用的是request_fn,就是在
在这里插入图片描述
这边设置进来的东西,上面是我们的demo代码。
在这里插入图片描述
ok,调用到了驱动的代码,没有问题了,最终的这个elv_next_request,进行那q里面,电梯排好的请求。

这就意味着,电梯算法处理的是rq,它是怎么进入的
在这里插入图片描述
进入也是没有问题的,

初始化也应该没有问题,这样的话,我大概就是可以去看,elv的代码了。

io的plug过程:queuelist的问题

在前面的梳理中一直对queuelist有一个来源的疑问,今天结合noop的电梯调度算法进行一次梳理。

也是看了noop后,才回来思考的。

blk_qc_t generic_make_request(struct bio *bio)
{struct bio_list bio_list_on_stack;blk_qc_t ret = BLK_QC_T_NONE;if (!generic_make_request_checks(bio))goto out;if (current->bio_list) {bio_list_add(current->bio_list, bio);goto out;}bio_list_init(&bio_list_on_stack);current->bio_list = &bio_list_on_stack;do {struct request_queue *q = bdev_get_queue(bio->bi_bdev);if (likely(blk_queue_enter(q, __GFP_DIRECT_RECLAIM) == 0)) {ret = q->make_request_fn(q, bio);blk_queue_exit(q);bio = bio_list_pop(current->bio_list);} else {struct bio *bio_next = bio_list_pop(current->bio_list);bio_io_error(bio);bio = bio_next;}} while (bio);current->bio_list = NULL; /* deactivate */out:return ret;
}

这里还是有些疑问的,关于bio_list的,这样的写法还是第一次看到,搞不清楚它的逻辑。

我们就假设,这个do_while就是对这个bio_list进行清空遍历操作。

然后就进行make_request_fn,

我们说了驱动层的两种,在这里如果是直接的那种,相当于直接调用了,

如果是第二种则会进入电梯处理函数。

static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio)
{const bool sync = !!(bio->bi_rw & REQ_SYNC);struct blk_plug *plug;int el_ret, rw_flags, where = ELEVATOR_INSERT_SORT;struct request *req;unsigned int request_count = 0;blk_queue_bounce(q, &bio);blk_queue_split(q, &bio, q->bio_split);if (bio_integrity_enabled(bio) && bio_integrity_prep(bio)) {bio->bi_error = -EIO;bio_endio(bio);return BLK_QC_T_NONE;}if (bio->bi_rw & (REQ_FLUSH | REQ_FUA)) {spin_lock_irq(q->queue_lock);where = ELEVATOR_INSERT_FLUSH;goto get_rq;}if (!blk_queue_nomerges(q)) {if (blk_attempt_plug_merge(q, bio, &request_count, NULL))return BLK_QC_T_NONE;} elserequest_count = blk_plug_queued_count(q);spin_lock_irq(q->queue_lock);el_ret = elv_merge(q, &req, bio);if (el_ret == ELEVATOR_BACK_MERGE) {if (bio_attempt_back_merge(q, req, bio)) {elv_bio_merged(q, req, bio);if (!attempt_back_merge(q, req))elv_merged_request(q, req, el_ret);goto out_unlock;}} else if (el_ret == ELEVATOR_FRONT_MERGE) {if (bio_attempt_front_merge(q, req, bio)) {elv_bio_merged(q, req, bio);if (!attempt_front_merge(q, req))elv_merged_request(q, req, el_ret);goto out_unlock;}}get_rq:rw_flags = bio_data_dir(bio);if (sync)rw_flags |= REQ_SYNC;req = get_request(q, rw_flags, bio, GFP_NOIO);if (IS_ERR(req)) {bio->bi_error = PTR_ERR(req);bio_endio(bio);goto out_unlock;}init_request_from_bio(req, bio);if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags))req->cpu = raw_smp_processor_id();plug = current->plug;if (plug) {if (!request_count)trace_block_plug(q);else {if (request_count >= BLK_MAX_REQUEST_COUNT) {blk_flush_plug_list(plug, false);trace_block_plug(q);}}list_add_tail(&req->queuelist, &plug->list);blk_account_io_start(req, true);} else {spin_lock_irq(q->queue_lock);add_acct_request(q, req, where);__blk_run_queue(q);
out_unlock:spin_unlock_irq(q->queue_lock);}return BLK_QC_T_NONE;
}

这里如果count > MAX了就进行 flush操作。但是这里如果没有达到要求来说,就是将queuelist 放到 plug->list
之前一直在纠结这个queuelist的元素是怎么哪里加入进去的,
其实是自己进入了一个误区。

queuelist其实就是连接各个rq的一个链表而已,内核list经常是这样子的,自己却忘记了。

在这里插入图片描述
只是这么一个结构。

好吧,我们现在假设已经达到了MAX,就要进行清除操作。

void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule)
{struct request_queue *q;unsigned long flags;struct request *rq;LIST_HEAD(list);unsigned int depth;flush_plug_callbacks(plug, from_schedule);if (!list_empty(&plug->mq_list))blk_mq_flush_plug_list(plug, from_schedule);if (list_empty(&plug->list))return;list_splice_init(&plug->list, &list);list_sort(NULL, &list, plug_rq_cmp);q = NULL;depth = 0;local_irq_save(flags);while (!list_empty(&list)) {rq = list_entry_rq(list.next);list_del_init(&rq->queuelist);BUG_ON(!rq->q);if (rq->q != q) {if (q)queue_unplugged(q, depth, from_schedule);q = rq->q;depth = 0;spin_lock(q->queue_lock);}if (unlikely(blk_queue_dying(q))) {__blk_end_request_all(rq, -ENODEV);continue;}if (rq->cmd_flags & (REQ_FLUSH | REQ_FUA))__elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH);else__elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE);depth++;}
}

plug->list 拿到while中进行循环,直到是empty。

然后拿出每一个rq,然后进行list_del_init(&rq->queuelist);

就是如图。
在这里插入图片描述
就是从链表中剥夺它的位置,当然有可能是头结点或者其他情况。

然后queue_unplugged 是告诉驱动你可以调用elv_next_request/blk_peek_request 到电梯里去拿数据了

而后面的**__elv_add_request** 讲数据加入电梯。

现在我们用noop的算法过程来连接上驱动和电梯,和蓄洪plug。

先继续从__elv_add_request往下看

void __elv_add_request(struct request_queue *q, struct request *rq, int where)
{trace_block_rq_insert(q, rq);blk_pm_add_request(q, rq);rq->q = q;if (rq->cmd_flags & REQ_SOFTBARRIER) {if (rq->cmd_type == REQ_TYPE_FS) {q->end_sector = rq_end_sector(rq);q->boundary_rq = rq;}} else if (!(rq->cmd_flags & REQ_ELVPRIV) &&(where == ELEVATOR_INSERT_SORT ||where == ELEVATOR_INSERT_SORT_MERGE))where = ELEVATOR_INSERT_BACK;switch (where) {case ELEVATOR_INSERT_REQUEUE:case ELEVATOR_INSERT_FRONT:rq->cmd_flags |= REQ_SOFTBARRIER;list_add(&rq->queuelist, &q->queue_head);break;case ELEVATOR_INSERT_BACK:rq->cmd_flags |= REQ_SOFTBARRIER;elv_drain_elevator(q);list_add_tail(&rq->queuelist, &q->queue_head);__blk_run_queue(q);break;case ELEVATOR_INSERT_SORT_MERGE:if (elv_attempt_insert_merge(q, rq))break;case ELEVATOR_INSERT_SORT:BUG_ON(rq->cmd_type != REQ_TYPE_FS);rq->cmd_flags |= REQ_SORTED;q->nr_sorted++;if (rq_mergeable(rq)) {elv_rqhash_add(q, rq);if (!q->last_merge)q->last_merge = rq;}q->elevator->type->ops.elevator_add_req_fn(q, rq);break;case ELEVATOR_INSERT_FLUSH:rq->cmd_flags |= REQ_SOFTBARRIER;blk_insert_flush(rq);break;default:printk(KERN_ERR "%s: bad insertion point %d\n",__func__, where);BUG();}
}

这边有好多个case。其实对于noop来说,意义都不大,noop来说它最终就是将数据放入到q->queue_head队列就好了。

这个队列是驱动初始化的申请的。

我们看下最终的ops

static struct elevator_type elevator_noop = {.ops = {.elevator_merge_req_fn    = noop_merged_requests,.elevator_dispatch_fn    = noop_dispatch,.elevator_add_req_fn    = noop_add_request,.elevator_former_req_fn    = noop_former_request,.elevator_latter_req_fn    = noop_latter_request,.elevator_init_fn    = noop_init_queue,.elevator_exit_fn    = noop_exit_queue,},.elevator_name = "noop",.elevator_owner = THIS_MODULE,
};static void noop_add_request(struct request_queue *q, struct request *rq)
{struct noop_data *nd = q->elevator->elevator_data;list_add_tail(&rq->queuelist, &nd->queue);
}

还是一样的。最终就是放在nd->queue。这个nd和上面的q是不是一个东西呢?应该是吧,等我,看下电梯章节的elevator_init

我们现在来看下拿数据的过程elv_next_request/blk_peek_request

之前的版本一直都是用elv_next_request,后面的好像比较常规用blk_peek_request但是最终还是调用elv_next_request

static inline struct request *__elv_next_request(struct request_queue *q)
{struct request *rq;struct blk_flush_queue *fq = blk_get_flush_queue(q, NULL);while (1) {if (!list_empty(&q->queue_head)) {rq = list_entry_rq(q->queue_head.next);return rq;}if (fq->flush_pending_idx != fq->flush_running_idx &&!queue_flush_queueable(q)) {fq->flush_queue_delayed = 1;return NULL;}if (unlikely(blk_queue_bypass(q)) ||!q->elevator->type->ops.elevator_dispatch_fn(q, 0))return NULL;}
}

这while(1),有点奇怪啊,会不会造成驱动的死等待,有可能它在前面加了线程吧。

后面就是从q->queue中拿rq了。

static int noop_dispatch(struct request_queue *q, int force)
{struct noop_data *nd = q->elevator->elevator_data;struct request *rq;rq = list_first_entry_or_null(&nd->queue, struct request, queuelist);if (rq) {list_del_init(&rq->queuelist);elv_dispatch_sort(q, rq);return 1;}return 0;
}

而这dispatch,了解下,从nd中拿出rq,放到elv_dispatch_sort。

void elv_dispatch_sort(struct request_queue *q, struct request *rq)
{sector_t boundary;struct list_head *entry;int stop_flags;if (q->last_merge == rq)q->last_merge = NULL;elv_rqhash_del(q, rq);q->nr_sorted--;boundary = q->end_sector;stop_flags = REQ_SOFTBARRIER | REQ_STARTED;list_for_each_prev(entry, &q->queue_head) {struct request *pos = list_entry_rq(entry);
..............}list_add(&rq->queuelist, entry);
}

这里就很清楚了,将上面nd拿出来的rq,放到q里面。

哦,那上面的疑问就清楚了,前面的nd和q不是同一个,意思说前面的case,有可能强制插入到头部的情况,就是抢占,感觉挺科学的。

这篇关于操作系统学习-3.Linux文件系统学习3-io的plug过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/505892

相关文章

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在