任务在就绪队列的等待时间--run_delay分析

发布时间 2023-06-23 17:55:06作者: 温暖的电波

1 什么是run_delay

    在linux中一个任务被创建、被唤醒后并非立刻运行,而是需要先放置到一个叫做”就绪队列”的合适位置上等待CPU调度运行;此外,一个任务运行过程中由于时间片到期或者高优先级任务抢占或者主动放弃CPU等情况发生时,内核会将当前运行的任务暂放到就绪队列上选择其他任务到CPU运行。

    上面不论是第一种首次进入就绪队列等待还是暂时进入就绪队列等待的情况,他们在就绪队列上等待CPU调度的时间,就是run_delay,我把它称作为调度延迟。

2 run_delay原理

    run_delay的统计方式有两种,一种是以任务为单位进行统计,即各个任务的调度延迟情况;另外一种是以CPU为单位进行统计,即各个CPU的调度延迟情况。而run_delay的原理十分简单,主要有两个要素:

2.1 记录入队时间戳

    当一个任务进入就绪队列时,记录它进入就绪队列的时间戳 

2.2 离队计算延迟

    当任务从就绪队列出队时,计算他在该就绪队列上的停留时间,并分别统计到当前cpu和任务对应的数据结构中。这就涉及到一些数据结构,如下所示:

  • 任务粒度调度延迟的数据结构
    task_struct->sched_info.last_queued        /* 任务进入就绪队列时刻的时间戳 */
    task_struct->sched_info.run_delay            /* 任务累积的调度延迟 */
    task_struct->sched_info.last_arrival            /* 任务得到CPU资源运行时刻的时间戳,其实是用于计算CPU粒度调度延迟 */
  • CPU粒度调度延迟的数据结构
rq->rq_sched_info.run_delay

3 run_delay 在内核的实现细节

    对调度不感兴趣的同学可以跳过这一节。前面有提及内核通过计算任务进入就绪队列/离开就绪队列的。

3.1 入队情况

  • 新任务创建和被唤醒任务加入就绪队列

    这种情况下一般会调用activate_task(rq, p, flags)来将目标任务p加入到就绪队列,这个过程会调用sched_info_queued(rq, p)来记录任务入队时间戳p->sched_info.last_queued。

  • 在CPU上运行的任务被迫放弃CPU回到就绪队列

    这种情况是任务在CPU运行,且还处于R状态;由于时间片到期、抢占等原因调用了__schedule(preempt)主调度函数,这个过程会调用pick_next_task()函数选择下一个要运行的任务,然后自己重新回到就绪队列中排队,等待下一次调度;而这个重回就绪队列的任务的入队时间戳是在sched_info_depart()子函数sched_info_queued(rq, p)记录p->sched_info.last_queued,记录前还会再次检查目标任务运行状态是p->state是否是TASK_RUNNING。

  • 任务发生CPU迁移的情况

    由于各个CPU的clock可能会有差异,所以在这种情况会先统计当前就绪队列等待的时间,然后加入到新就绪队列后会取新CPU的时间戳作为任务加入就绪队列的起始时间戳task_struct->sched_info.last_queued

    前面这3种情况是需要记录任务在就绪队列等待时间是需要记录的。

  • 优先级改变、cpuset改变等情况

    这种情况下在就绪队列中的任务一般会先离开就绪队列(这种情况所在的CPU并未改变,出队、再入队的一部分原因为了进行数据统计),然后很快再加入到就绪队列;在离开就绪队列、进入就绪队列时一般会分别使用DEQUEUE_SAVE与ENQUEUE_RESTORE两种标志来提示内核不进行run_delay统计;即使有些场景中在dequeue出队的时候使用了DEQUEUE_SAVE,但是在入队的时候没有成对使用ENQUEUE_RESTORE,但是在sched_info_queued(rq, p)中会判断task_struct->sched_info.last_queued是否已经有值来避免重复设置task_struct->sched_info.last_queued。

3.2 出队情况

  • 任务得到调度获得CPU资源

    这种情况和上面第2)种情况类似,也是发生在__schedule(preempt)函数中,只不过这一次是在里面调用了sched_info_arrive()函数计算并统计任务在就绪队列等待的时间。

  • 任务的迁移

    如load_balance()、move_queued_task()、numa_balance将任务从一个src就绪队列迁移到dest就绪队列,会调用sched_info_dequeued()来统计任务之前在src就绪队列等待的时间,注意这里sched_info_dequeued()函数统计的时候会通过task_struct->sched_info.last_queued检查任务是否有真正进入就绪队列。

4 run_delay启用的条件与用户态访问接口

4.1 run_delay启用的条件

    在一些业务系统中run_delay指标对于业务性能的监控与反映尤其重要, Linux系统中run_delay的统计需要满足如下2个条件才会进行统计。

  1. CONFIG_SCHED_INFO=y
  2. CONFIG_SCHEDSTATS=y  或者 (CONFIG_TASK_DELAY_ACCT=y且delayacct_on=1)

    其中delayacct_on默认为1,除非系统使用了”nodelayacct”启动参数。

4.2 用户态接口

    Linux系统为每cpu的run_delay延迟提供了一个proc课访问接口:/proc/schedstat,这个接口还提供了很多的其他字段,而run_delay则是在cpu行的倒数第二列,如下图1所示:

图1

 

    Linux系统为任务run_delay延迟提供的用户态访问接口为:/proc/PID/schedstat  这个接口有3个字段分别是:task->se.sum_exec_runtime、task->sched_info.run_delay、task->sched_info.pcount。 run_delay是在第2个字段,如下图2所示:

图2 

    这两个接口中对于run_delay的统计时间单位都是ns,且都是统计的累计值。