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

linux之调度管理(5)-实时调度器

一、概述

在Linux内核中,实时进程总是比普通进程的优先级要高,实时进程的调度是由Real Time Scheduler(RT调度器)来管理,而普通进程由CFS调度器来管理。

实时进程支持的调度策略为:SCHED_FIFOSCHED_RR

  • SCHED_FIFO:没有时间片,先进先出,在被调度器选择后,可以运行任意长时间。
  • SCHED_RR:没有时间片,其值在进程运行时会减少,就像普通进程一样。在所有的时间段都到期后,则该值重置为初始值,而进程则置于队列末尾。这确保了在有几个优先级相同的SCHED_RR进程情况下,它们总是依次执行

使用场景:

  1. SCHED_FIFO应用场景,比如时钟域隔离,不同宽度数据接口(如嵌入式单片机8位和16位DSP数据传送,在单片机和DSP连接达到数据匹配目的)。
  2. SCHED_RR,需要定时切换场景或轮流实现操作场景

二、数据结构

  • struct rq:运行队列,每个CPU都对应一个;
  • struct rt_rq:实时运行队列,用于管理实时任务的调度实体;
  • struct sched_rt_entity:实时调度实体,用于参与调度,功能与struct sched_entity类似;
  • struct task_group:组调度结构体;
  • struct rt_bandwidth:带宽控制结构体;

先上张图,捋捋这些结构之间的关系:

  • 从图中的结构组织关系看,与CFS调度器基本一致,区别在与CFS调度器是通过红黑树来组织调度实体,而RT调度器使用的是优先级队列来组织实时调度实体;
  • rt_rq运行队列,维护了100个优先级的队列(链表),优先级0-99,从高到底;
  • 调度器管理的对象是调度实体,任务task_struct和任务组task_group都是通过内嵌调度实体的数据结构,来最终参与调度管理的;
  • task_group任务组调度,自身为每个CPU维护rt_rq,用于存放自己的子任务或者子任务组,子任务组又能往下级联,因此可以构造成树;

下面是关键结构体:

struct sched_rt_entity {struct list_head		run_list;   //用于加入到优先级队列中unsigned long			timeout;    //设置的时间超时unsigned long			watchdog_stamp;     //用于记录jiffies值unsigned int			time_slice;     //时间片,100ms,unsigned short			on_rq;unsigned short			on_list;struct sched_rt_entity		*back;  //临时用于从上往下连接RT调度实体时使用
#ifdef CONFIG_RT_GROUP_SCHEDstruct sched_rt_entity		*parent;    //指向父RT调度实体/* rq on which this entity is (to be) queued: */struct rt_rq			*rt_rq;     //RT调度实体所属的实时运行队列,被调度/* rq "owned" by this entity/group: */struct rt_rq			*my_q;  //RT调度实体所拥有的实时运行队列,用于管理子任务或子组任务
#endif
} __randomize_layout;

//kernel/kernel/sched/sched.h/* Real-Time classes' related field in a runqueue: */
struct rt_rq {struct rt_prio_array active;    //优先级队列,100个优先级的链表,并定义了位图,用于快速查询unsigned int rt_nr_running; //在RT运行队列中所有活动的任务数unsigned int rr_nr_running;
#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHEDstruct {int curr; /* highest queued rt task prio */     //当前RT任务的最高优先级
#ifdef CONFIG_SMPint next; /* next highest */        //下一个要运行的RT任务的优先级,如果两个任务都有最高优先级,则curr == next
#endif} highest_prio;
#endif
#ifdef CONFIG_SMPunsigned long rt_nr_migratory;      //任务没有绑定在某个CPU上时,这个值会增减,用于任务迁移unsigned long rt_nr_total;      //用于overload检查int overloaded;     //RT运行队列过载,则将任务推送到其他CPUstruct plist_head pushable_tasks;   //优先级列表,用于推送过载任务
#endif /* CONFIG_SMP */int rt_queued;  //表示RT运行队列已经加入rq队列int rt_throttled;   //用于限流操作u64 rt_time;    //累加的运行时,超出了本地rt_runtime时,则进行限制u64 rt_runtime;     //分配给本地池的运行时/* Nests inside the rq lock: */raw_spinlock_t rt_runtime_lock;#ifdef CONFIG_RT_GROUP_SCHEDunsigned long rt_nr_boosted;    //用于优先级翻转问题解决struct rq *rq;      //指向运行队列struct task_group *tg;  //指向任务组
#endif
};
struct rt_bandwidth {/* nests inside the rq lock: */raw_spinlock_t		rt_runtime_lock;ktime_t			rt_period;      //时间周期u64			rt_runtime;     //一个时间周期内的运行时间,超过则限流,默认值为95%struct hrtimer		rt_period_timer;    //时间周期定时器unsigned int		rt_period_active;
};
struct rt_prio_array {DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */struct list_head queue[MAX_RT_PRIO];
};

定义了位图,代表100(0-99) 个优先级队列。如上面图所示,就是按照优先级把进程放入对应的优先级队列中。

三、进程的创建

内核中实时调度器的定义如下:

const struct sched_class rt_sched_class__section("__rt_sched_class") = {.enqueue_task           = enqueue_task_rt,//入队.dequeue_task           = dequeue_task_rt,//出队.yield_task             = yield_task_rt,//放弃主动权.check_preempt_curr     = check_preempt_curr_rt,.pick_next_task         = pick_next_task_rt,//调度器中选择哪个任务要被调度.put_prev_task          = put_prev_task_rt,/当一个任务将要被调度出执行.set_next_task          = set_next_task_rt,#ifdef CONFIG_SMP.balance                = balance_rt,.select_task_rq         = select_task_rq_rt,.set_cpus_allowed       = set_cpus_allowed_common,.rq_online              = rq_online_rt,.rq_offline             = rq_offline_rt,.task_woken             = task_woken_rt,.switched_from          = switched_from_rt,.find_lock_rq           = find_lock_lowest_rq,
#endif.task_tick              = task_tick_rt,.get_rr_interval        = get_rr_interval_rt,.prio_changed           = prio_changed_rt,.switched_to            = switched_to_rt,.update_curr            = update_curr_rt,#ifdef CONFIG_UCLAMP_TASK.uclamp_enabled         = 1,
#endif
};

 上面rt_sched_class 的定义时,与fair_sched_class 有一个很大的区别是:没有实现 task_fork回调函数。所有在创建进程时,与CFS调度器有个区别,代码如下:

void sched_post_fork(struct task_struct *p, struct kernel_clone_args *kargs)
{unsigned long flags;rseq_migrate(p);/** We're setting the CPU for the first time, we don't migrate,* so use __set_task_cpu().*/__set_task_cpu(p, smp_processor_id());if (p->sched_class->task_fork)p->sched_class->task_fork(p); //执行调度器的回调函数raw_spin_unlock_irqrestore(&p->pi_lock, flags);

 实时调度器是没有task_fork 函数的。

copy_process()
  sched_fork()
    __sched_fork()
  sched_post_fork()
    ****
  wake_up_new_task()
    activate_task()
      enqueue_task
        rt_sched_class->enqueue_task-->enqueue_task_rt()

下面看一下 调度实体入队:

/** Adding/removing a task to/from a priority array:*/
static void
enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{struct sched_rt_entity *rt_se = &p->rt;if (flags & ENQUEUE_WAKEUP) // 入队flagsrt_se->timeout = 0;enqueue_rt_entity(rt_se, flags);
//进程如果运行在多个CPU上,将任务添加到pushable 链表上if (!task_current(rq, p) && p->nr_cpus_allowed > 1)enqueue_pushable_task(rq, p);
}

enqueue_rt_entity:

static void enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{struct rq *rq = rq_of_rt_se(rt_se);dequeue_rt_stack(rt_se, flags);for_each_sched_rt_entity(rt_se)__enqueue_rt_entity(rt_se, flags);enqueue_top_rt_rq(&rq->rt);
}

这里其实是由有一个问题: 为什么 实时调度体入队时,需要先把rt_se已经到顶部rt_se 出队?

enqueue_task_rtdequeue_task_rt都会调用dequeue_rt_stack接口,当请求的rt_se对应的是任务组时,会从顶部到请求的rt_se将调度实体出列;

当RT任务进行出队入队时,通过enqueue_task_rt/dequeue_task_rt两个接口来完成,调用流程如下:

  • enqueue_task_rtdequeue_task_rt都会调用dequeue_rt_stack接口,当请求的rt_se对应的是任务组时,会从顶部到请求的rt_se将调度实体出列;
  • 任务添加到rt运行队列时,如果存在多个任务可以分配给多个CPU,设置overload,用于任务的迁移;

 这上面有一个RT调度器的核心函数:update_curr_rt ,运行时的统计数据更新。后面具体分析这个函数。

四、进程创建和入队之后,就是如何被调度

4.1 主调度器调度或者主动执行调度函数

__schedule()
  ->pick_next_task()

        ->put_prev_task_balance(选择下一个实时进程前,进行平衡处理,和SMP有关系)

                ->balance_rt

                        ->need_pull_rt_task() ->pull_rt_task()

        ->pick_next_task_rt() (选择下一个实时进程)
  ->context_switch()
    ->switch_mm()
      ->cpu_*_switch_mm()
    ->switch_to()
      ->__switch_to

pick_next_task_rt函数是调度器用于选择下一个执行任务。流程如下:

  • CFS调度器不同,RT调度器会在多个CPU组成的domain中,对任务进行pull/push处理,也就是说,如果当前CPU的运行队列中任务优先级都不高,那么会考虑去其他CPU运行队列中找一个更高优先级的任务来执行,以确保按照优先级处理,此外当前CPU也会把任务推送到其他更低优先级的CPU运行队列上。
  • _pick_next_task_rt的处理逻辑比较简单,如果实时调度实体是task,则直接查找优先级队列的位图中,找到优先级最高的任务,而如果实时调度实体是task_group,则还需要继续往下进行遍历查找;

上面的流程图,稍微和内核5.10.* 的版本有点不同,下面看一下pick_next_task函数:

** Pick up the highest-prio task:*/
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{const struct sched_class *class;struct task_struct *p;/** Optimization: we know that if all tasks are in the fair class we can* call that function directly, but only if the @prev task wasn't of a* higher scheduling class, because otherwise those loose the* opportunity to pull in more work from other CPUs.*/if (likely(prev->sched_class <= &fair_sched_class &&rq->nr_running == rq->cfs.h_nr_running)) {p = pick_next_task_fair(rq, prev, rf);if (unlikely(p == RETRY_TASK))goto restart;/* Assumes fair_sched_class->next == idle_sched_class */if (!p) {put_prev_task(rq, prev);p = pick_next_task_idle(rq);}return p;}restart:put_prev_task_balance(rq, prev, rf);for_each_class(class) {p = class->pick_next_task(rq);if (p)return p;}/* The idle class should always have a runnable task: */BUG();
}

在restart 分断处,有put_prev_task_balance函数,就是执行 实时调度器的 在SMP时的平衡处理,即balance_rt函数。所谓平衡处理就是:RT调度器会在多个CPU组成的domain中,对任务进行pull/push处理。

关于任务的pull/push,linux提供了struct plist,基于优先级的双链表,其中任务的组织关系如下图:

pushable_tasks 链表:即可以推到其他CPU 上的进程

pull_rt_task的大概示意图如下:

  • 当前CPU上的优先级任务不高,从另一个CPU的pushable_tasks链表中找优先级更高的任务来执行;

上面的pull_rt_task 就是实现实时调度进程的SMP均衡。

4.2 系统时钟周期调度

scheduer_tick->sched_class->task_tick_rt

static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{struct sched_rt_entity *rt_se = &p->rt;update_curr_rt(rq);//更新时间update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);watchdog(rq, p);/** RR tasks need a special form of timeslice management.* FIFO tasks have no timeslices.*/if (p->policy != SCHED_RR)//先进先出的进程没有时间片,直接退出 return;//当前是RR进程,则减少时间片if (--p->rt.time_slice)return;//重置时间片100msp->rt.time_slice = sched_rr_timeslice;/** Requeue to the end of queue if we (and all of our ancestors) are not* the only element on the queue*///如果该进程不是唯一进程,则排到队尾for_each_sched_rt_entity(rt_se) {if (rt_se->run_list.prev != rt_se->run_list.next) {requeue_task_rt(rq, p, 0);resched_curr(rq);return;}}
}

update_curr_rt 函数是实时调度的核心函数。主要是更新调度信息

/** Update the current task's runtime statistics. Skip current tasks that* are not in our scheduling class.*/
static void update_curr_rt(struct rq *rq)
{struct task_struct *curr = rq->curr;struct sched_rt_entity *rt_se = &curr->rt;u64 delta_exec;u64 now;//判断是否有实时调度进程if (curr->sched_class != &rt_sched_class)return;now = rq_clock_task(rq);//执行时间delta_exec = now - curr->se.exec_start;if (unlikely((s64)delta_exec <= 0))return;schedstat_set(curr->se.statistics.exec_max,max(curr->se.statistics.exec_max, delta_exec));//更新当前进程总的执行时间curr->se.sum_exec_runtime += delta_exec;account_group_exec_runtime(curr, delta_exec);//更新执行开始时间curr->se.exec_start = now;cgroup_account_cputime(curr, delta_exec);if (!rt_bandwidth_enabled())return;for_each_sched_rt_entity(rt_se) {struct rt_rq *rt_rq = rt_rq_of_se(rt_se);int exceeded;if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {raw_spin_lock(&rt_rq->rt_runtime_lock);rt_rq->rt_time += delta_exec;exceeded = sched_rt_runtime_exceeded(rt_rq);if (exceeded)resched_curr(rq);raw_spin_unlock(&rt_rq->rt_runtime_lock);if (exceeded)do_start_rt_bandwidth(sched_rt_bandwidth(rt_rq));}}
}

4.3 运行时统计数据

运行时的统计数据更新,是在update_curr_rt函数中完成的:

  • update_curr_rt函数功能,主要包括两部分:
    1. 运行时间的统计更新处理;
    2. 如果运行时间超出了分配时间,进行时间均衡处理,并且判断是否需要进行限流,进行了限流则需要将RT队列出队,并重新进行调度;

为了更直观的理解,下边还是来两张图片说明一下:

sched_rt_avg_update更新示意如下:

  • rq->age_stamp:在CPU启动后运行队列首次运行时设置起始时间,后续周期性进行更新;
  • rt_avg:累计的RT平均运行时间,每0.5秒减半处理,用于计算CFS负载减去RT在CFS负载平衡中使用的时间百分比;

这上面有一个问题,会导致内核报RT_THRROLLING的问题,即实时进程运行超时,会向其他CPU 借用时间,导致运行时间大于100ms 时间片的95%.

这个问题在最后解释。

五、组调度

和CFS调度器类似, 看一下RT调度器组调度的组织关系图吧:

  • 系统为每个CPU都分配了RT运行队列,以及RT调度实体,任务组通过它包含的RT调度实体来参与调度;
  • 任务组task_group的RT队列,用于存放归属于该组的任务或子任务组,从而形成级联的结构;

看一下实际的组织示意图:

六、带宽控制

RT调度器在带宽控制中,调度时间周期设置的为1s,运行时间设置为0.95s:

/** period over which we measure -rt task CPU usage in us.* default: 1s*/
unsigned int sysctl_sched_rt_period = 1000000;/** part of the period that we allow rt tasks to run in us.* default: 0.95s*/
int sysctl_sched_rt_runtime = 950000;

这两个值可以在用户态通过/sys/fs/cgroup/cpu/rt_runtime_us/sys/fs/cgroup/cpu/rt_period_us来进行设置。

看看函数调用流程:

  • init_rt_bandwidth函数在创建分配RT任务组的时候调用,完成的工作是将rt_bandwidth结构体的相关字段进行初始化:设置好时间周期rt_period和运行时间限制rt_runtime,都设置成默认值;
  • 可以从用户态通过操作/sys/fs/cgroup/cpu下对应的节点进行设置rt_periodrt_runtime,最终调用的函数是tg_set_rt_bandwidth,在该函数中会从下往上的遍历任务组进行设置时间周期和限制的运行时间;
  • enqueue_rt_entity将RT调度实体入列时,最终触发start_rt_bandwidth函数执行,当高精度定时器到期时调用do_sched_rt_period_timer函数;
  • do_sched_rt_period_timer函数,会去判断该RT运行队列的累计运行时间rt_time与设置的限制运行时间rt_runtime之间的大小关系,以确定是否限流的操作。在这个函数中,如果已经进行了限流操作,会调用balance_time来在多个CPU之间进行时间均衡处理,简单点说,就是从其他CPU的rt_rq队列中匀出一部分时间增加到当前CPU的rt_rq队列中,也就是将当前rt_rt运行队列的限制运行时间rt_runtime增加一部分,其他CPU的rt_rq运行队列限制运行时间减少一部分。

来一张效果示意图:

这个上面的定时器处理函数来实现带宽控制,是和CFS调度器是一样的。

七、RT-throttling 出现的问题分析

7.1 出现问题的背景:

Linux 上调度策略为SCHED_FIFO的实时进程是根据优先级抢占运行的。当没有更高优先级的实时进程抢占,而此进程又由于bug等原因长时间运行,不调度其它进程,系统就会出现无响应。这里要分析的RT throttling就是针对此种情况的,它通过限制每个单位时间内分配给实时进程的CPU运行时间,来防止上述情况的出现。

在这里补充一点,内核实时调度还有RR策略,RR调度的时间片是100ms,假设此时有两个RR进程,调度器就是以100ms为周期单位,进行来回调度,也有可能出现RT-throttling的内核日志。

此文章中,谈论简单的FIFO的实时进程。

7.2 系统中的内核配置参数

标准的设置是1s的时间内,实时进程的运行时间是950ms,其余的50ms时间给normal进程使用。

 

sched_rt_period_us值为1000000us=1s,表示单位时间为1s

sched_rt_runtime_us值为950000us=0.95s,表示实时进程的运行时间为0.95s。

这两个接口的实现代码如下:

//kernel/kernel/sysctl.cstatic struct ctl_table kern_table[] = {....{.procname       = "sched_rt_period_us",.data           = &sysctl_sched_rt_period,.maxlen         = sizeof(unsigned int),.mode           = 0644,.proc_handler   = sched_rt_handler,},{.procname       = "sched_rt_runtime_us",.data           = &sysctl_sched_rt_runtime,.maxlen         = sizeof(int),.mode           = 0644,.proc_handler   = sched_rt_handler,},
....}

sched_rt_period_us接口设置的是sysctl_sched_rt_period变量,sched_rt_runtime_us接口设置的是sysctl_sched_rt_runtime变量。读写的实现都是通过sched_rt_handler函数,这里就不具体分析了。

int sched_rt_handler(struct ctl_table *table, int write, void *buffer,size_t *lenp, loff_t *ppos)
{int old_period, old_runtime;static DEFINE_MUTEX(mutex);int ret;mutex_lock(&mutex);old_period = sysctl_sched_rt_period;old_runtime = sysctl_sched_rt_runtime;ret = proc_dointvec(table, write, buffer, lenp, ppos);if (!ret && write) {ret = sched_rt_global_validate();if (ret)goto undo;ret = sched_dl_global_validate();if (ret)goto undo;ret = sched_rt_global_constraints();if (ret)goto undo;sched_rt_do_global();sched_dl_do_global();// 和dl 调度器也有关系}if (0) {
undo:sysctl_sched_rt_period = old_period;sysctl_sched_rt_runtime = old_runtime;}mutex_unlock(&mutex);return ret;
}

7.3 设置RT throttling,那么它是如何工作的?

一个实时调度器的核心函数update_curr_rt:

static void update_curr_rt(struct rq *rq)
{struct task_struct *curr = rq->curr;struct sched_rt_entity *rt_se = &curr->rt;u64 delta_exec;u64 now;if (curr->sched_class != &rt_sched_class)/*判断当前进程调度类*/return;//*运行队列现在的时间与当前进程开始运行时间之差* */now = rq_clock_task(rq);delta_exec = now - curr->se.exec_start;/*更新进程的真实运行时间*/    if (unlikely((s64)delta_exec <= 0))return;schedstat_set(curr->se.statistics.exec_max,max(curr->se.statistics.exec_max, delta_exec));curr->se.sum_exec_runtime += delta_exec;account_group_exec_runtime(curr, delta_exec);curr->se.exec_start = now;cgroup_account_cputime(curr, delta_exec);if (!rt_bandwidth_enabled()) /*判断RT throttling是否开启*/return;for_each_sched_rt_entity(rt_se) {/*/*遍历此实时进程的调度单元*/*/struct rt_rq *rt_rq = rt_rq_of_se(rt_se);int exceeded;if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {raw_spin_lock(&rt_rq->rt_runtime_lock);rt_rq->rt_time += delta_exec; // /*rt_rq的运行时间是否超过了分配给它的时间片*/     exceeded = sched_rt_runtime_exceeded(rt_rq);if (exceeded)resched_curr(rq);raw_spin_unlock(&rt_rq->rt_runtime_lock);if (exceeded)//如果超过,开始限制do_start_rt_bandwidth(sched_rt_bandwidth(rt_rq));}}
}
  • update_curr_rt函数用来更新当前实时进程的运行时间统计值,如果当前进程不是实时进程,即调度类不为rt_sched_class,则直接返回。
  • delta_exec值为此运行队列的当前时间与此进程开始运行时间之差,也即是此进程此次调度运行的时长。然后更新进程的真实运行时间和开始运行时间。
  • rt_bandwidth_enabled函数判断sysctl_sched_rt_runtime变量值是否大于0,如果此变量值设置为RUNTIME_INF(很大的负数),就关掉了RT throttling功能,这里就会直接返回。
  • 然后遍历此实时进程的调度实体,找到相应的就绪队列,更新运行时间后,通过sched_rt_runtime_exceeded函数判断是否此实时进程是否超过了分配给它的时间片。

接着看函数 :sched_rt_runtime_exceeded()

 

//kernel/kernel/sched/rt.cstatic int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)
{u64 runtime = sched_rt_runtime(rt_rq);/*获取当前队列的最大运行时间*/  if (rt_rq->rt_throttled)/*当前队列的实时调度受到限制*/     return rt_rq_throttled(rt_rq);/*当前队列的最大运行时间大于当前队列的调度周期时间*/if (runtime >= sched_rt_period(rt_rq))return 0;balance_runtime(rt_rq);runtime = sched_rt_runtime(rt_rq); /*重新获取当前队列的最大运行时间*/if (runtime == RUNTIME_INF)/*关闭了RT throttling*/return 0;if (rt_rq->rt_time > runtime) {/*累计运行时间大于最大运行时间*/             struct rt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);/** Don't actually throttle groups that have no runtime assigned* but accrue some time due to boosting.*/if (likely(rt_b->rt_runtime)) {rt_rq->rt_throttled = 1;printk_deferred_once("sched: RT throttling activated\n");} else {/** In case we did anyway, make it go away,* replenishment is a joke, since it will replenish us* with exactly 0 ns.*/rt_rq->rt_time = 0;}if (rt_rq_throttled(rt_rq)) {/*检查队列的实时调度是否受到限制*/sched_rt_rq_dequeue(rt_rq); /*将调度实体从实时运行队列中删除*/return 1;}}return 0;
}
  • runtime值为当前队列的最大运行时间rt_runtime。rt_throttled字段表示当前队列的实时调度是否受到限制,如果受到限制了,就直接返回1,在update_curr_rt函数中就会调用resched_task函数执行进程切换,让出cpu。
  • 如果当前队列的最大运行时间大于当前队列的调度周期时间,则返回0,这样此运行队列上的任务还能够继续运行。
  • balance_runtime函数在RT_RUNTIME_SHARE特性使能的情况下如果当前队列的运行时间超过了最大运行时间,则可以从其他cpu上借用时间。具体代码这里先不分析,后面分析。
  • 重新获取当前队列的最大运行时间runtime,如果值等于RUNTIME_INF说明关闭了RT throttling,则直接返回0。
  • 如果累计运行时间大于最大运行时间,就会执行上面的代码片段。rt_b为运行队列rt_rq的进程组带宽控制结构体指针,如果rt_runtime即此进程组的任务运行时间额度值有效,则设置rt_throttled为1,表明此队列的实时调度受到限制,并打印出“sched: RT throttling activated”信息。接着检查队列的实时调度如果受到限制,则返回1,在update_curr_rt函数中让出cpu。

下面讲一下从其他CPU上借用时间:

sched_rt_runtime_exceeded() ->balance_runtime();balance_runtime在当前队列运行时间超过最大运行时间后,可以从其他cpu上借用时间.

static void balance_runtime(struct rt_rq *rt_rq)
{if (!sched_feat(RT_RUNTIME_SHARE)) /*RT_RUNTIME_SHARE支持多个cpu间的rt_runtime共享*/return;if (rt_rq->rt_time > rt_rq->rt_runtime) {raw_spin_unlock(&rt_rq->rt_runtime_lock);do_balance_runtime(rt_rq);raw_spin_lock(&rt_rq->rt_runtime_lock);}
}

RT_RUNTIME_SHARE默认是使能的,SCHED_FEAT(RT_RUNTIME_SHARE, true),位于 kernel/sched/features.h文件中。

它表示支持多个cpu间的rt_runtime共享。如果不支持的话,就直接返回。

如果当前队列的累计运行时间大于最大运行时间,则调用do_balance_runtime函数。

do_balance_runtime:

//kernel/kernel/sched/rt.cstatic void do_balance_runtime(struct rt_rq *rt_rq)
{struct rt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);struct root_domain *rd = rq_of_rt_rq(rt_rq)->rd;int i, weight;u64 rt_period;weight = cpumask_weight(rd->span);raw_spin_lock(&rt_b->rt_runtime_lock);rt_period = ktime_to_ns(rt_b->rt_period);/*任务组一个控制周期的时间*/     for_each_cpu(i, rd->span) {/*找到在另一个cpu上运行的同一任务组的运行队列*/struct rt_rq *iter = sched_rt_period_rt_rq(rt_b, i);s64 diff;if (iter == rt_rq)/*同一运行队列则跳过*/continue;raw_spin_lock(&iter->rt_runtime_lock);/** Either all rqs have inf runtime and there's nothing to steal* or __disable_runtime() below sets a specific rq to inf to* indicate its been disabled and disalow stealing.*/if (iter->rt_runtime == RUNTIME_INF)goto next;/*RT throttling关闭,不允许借用时间*//** From runqueues with spare time, take 1/n part of their* spare time, but no more than our period.*//*最大能够借用时间*/diff = iter->rt_runtime - iter->rt_time;if (diff > 0) {diff = div_u64((u64)diff, weight);if (rt_rq->rt_runtime + diff > rt_period)diff = rt_period - rt_rq->rt_runtime; /*修正后可借用*/iter->rt_runtime -= diff;rt_rq->rt_runtime += diff;/*满足条件退出,否则继续从其他cpu借用*/if (rt_rq->rt_runtime == rt_period) {raw_spin_unlock(&iter->rt_runtime_lock);break;}}
next:raw_spin_unlock(&iter->rt_runtime_lock);}raw_spin_unlock(&rt_b->rt_runtime_lock);
}

rd->span表示此调度域的rq可运行的cpu的一个mask,这里会遍历此mask上的cpu,如果对应的cpu的rq和当前的rq是同一运行队列,则直接跳过;如果对应的cpu的rq已关闭RT throttling功能,则不允许借用时间。内核中关于这块代码的注释是:

Either all rqs have inf runtime and there's nothing to steal or __disable_runtime() below sets a specific rq to inf to indicate its been disabled and disalow stealing.

大概意思是如果所有的运行队列都设置为RUNTIME_INF即关闭了RT throttling功能,则没有时间可以借用。或者某个指定的运行队列调用__disable_runtime()函数,则不允许别的借用自己的时间。

diff是iter运行队列最大能够借用的时间,后面经过修正后,将diff加入到rt_rq的最大可运行时间上。如果新的最大可运行时间等于此任务组的控制周期的时间,则不需要接着再从其他的CPU上借用时间,就直接break退出。

实时进程所在的cpu占用超时,可以向其他的CPU借用,将其他CPU的时间借用过来,这样此实时进程所在的CPU占有率达到100%,这样做的目的是为了避免实时进程由于缺少CPU时间而向其他的CPU迁移,减少不必要的迁移成本。此cpu上为绑定核的普通进程可以迁移到其他cpu上,这样就会得到调度。但是如果此CPU上有进程只绑定在此CPU上,那么就只有在这里饿死了。

所以,此时占用率达到100%,所以会内核日志会出现:sched: RT throttling activated。因为是先借用时间,在判断是否打印内核日志。

7.4 总结

rt_rq实时进程运行队列里面提到rt_time和rt_runtime,一个是运行累计时间,一个是最大运行时间,当运行累计时间超过最大运行时间的时候,rt_throttled则被设置为1。

补充一个隐藏时间算法:

其实还有一个隐藏的时间概念,即sched_rt_period_us(1s),意味着sched_rt_period_us时间内,实时进程可以占用CPU rt_runtime时间,如果实时进程每个时间周期内都没有调度,则在do_sched_rt_period_timer定时器函数中将rt_time减去一个周期,然后比较rt_runtime,恢复rt_throttled。

init_rt_bandwidth()->rt_b->rt_period_timer.function = sched_rt_period_timer;
 

sched_rt_period_timer()->do_sched_rt_period_timer()

static int do_sched_rt_period_timer(struct rt_bandwidth *rt_b, int overrun)
{int i, idle = 1, throttled = 0;const struct cpumask *span;....
raw_spin_lock(&rt_rq->rt_runtime_lock);if (rt_rq->rt_throttled)balance_runtime(rt_rq);runtime = rt_rq->rt_runtime;//overrun来自对周期时间定时器误差的校正rt_rq->rt_time -= min(rt_rq->rt_time, overrun*runtime);if (rt_rq->rt_throttled && rt_rq->rt_time < runtime) {rt_rq->rt_throttled = 0;enqueue = 1;
...
}

相关文章:

linux之调度管理(5)-实时调度器

一、概述 在Linux内核中&#xff0c;实时进程总是比普通进程的优先级要高&#xff0c;实时进程的调度是由Real Time Scheduler(RT调度器)来管理&#xff0c;而普通进程由CFS调度器来管理。 实时进程支持的调度策略为&#xff1a;SCHED_FIFO和SCHED_RR。 SCHED_FIFO&#xff…...

mybatis-plus: mapper-locations: “classpath*:/mapper/**/*.xml“配置!!!解释

和mybatis一样的道理&#xff01;&#xff01;&#xff01;&#xff01;如果不指定这个配置&#xff0c;通常要求 XML 映射文件和 Mapper 接口的包名和结构相同&#xff01;&#xff01;&#xff01;&#xff01; 如果没有配置 mapper-locations&#xff0c;通常文件结构应遵循…...

nacos-operator在k8s集群上部署nacos-server2.4.3版本踩坑实录

文章目录 操作步骤1. 拉取仓库代码2. 安装nacos-operator3. 安装nacos-server 坑点一坑点二nacos-ui页面访问同一集群环境下微服务连接nacos地址配置待办参考文档 操作步骤 1. 拉取仓库代码 &#xff08;这一步主要用到代码中的相关yml文件&#xff0c;稍加修改用于部署容器&…...

面试篇-项目管理

⼀、构建管理 项目为什么选择Maven构建? 选择Maven进行项目构建有以下几个主要原因&#xff1a; 1. 依赖管理&#xff1a;Maven 提供了强大的依赖管理功能&#xff0c;可以自动下载项目所需的第三方库和依赖&#xff0c;并且可以管理这些依赖的版本、范围等信息。这简化了项…...

数仓建设之Oracle常见语法学习

1. 字符串截取 select substr(AAA-BBB, 1, instr(AAA-BBB, -, -1) - 1) 值 from dual; --AAA select substr(AAA-BBB, instr(AAA-BBB, -, -1) 1) 值 from dual; --BBB2. 帆软报表有参数SQL select a.agency_code, a.agency_name, a.agency_typefrom dw.dim_ta_subred_agency…...

物联网智能技术的深入探讨与案例分析

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

python语言基础-5 进阶语法-5.2 装饰器-5.2.2 简单装饰器

声明&#xff1a;本内容非盈利性质&#xff0c;也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站&#xff0c;会尽量附上原文链接&#xff0c;并鼓励大家看原文。侵删。 5.2.2 简单装饰器 装饰器的形式就是一个闭包&#xff0c;下面是一个简单的定义并使用…...

TransFormer--解码器:带掩码的多头注意力层

TransFormer--解码器&#xff1a;带掩码的多头注意力层 以英法翻译任务为例&#xff0c;假设训练数据集样本如下表所示。 原句目标翻译I am goodJe vais bienGood morningBonjourThank you very muchMerci beaucoup 上表所示的数据集由两部分组成&#xff1a;原句和目标句。在…...

【ArcGIS微课1000例】0130:图层组详解与使用

文章目录 一、图层组概述二、创建图层组三、在图层组中管理图层四、对话框中图层组的列表一、图层组概述 图层组包含其他图层。图层组有助于对地图中相关类型的图层进行组织,并且可用于定义高级绘制选项。例如,假设在地图上有两个图层分别用于表示铁路和高速公路。您可将这些…...

Linux中配置ntp服务

NTP&#xff1a;是Network Time Protocol的缩写又 称网络时间协议&#xff0c;是用来使计算机时间同步化的一种协议&#xff0c;用来同步网络中各主机的时 间&#xff0c;在linux系统中早期使用ntp来实现&#xff0c;后来使用chrony来实现。Chrony 应用本身已经有 几年了&#…...

微服务day10-Redis面试篇

Redis主从 搭建主从集群 建立集群时主节点会生成同一的replicationID,交给各个从节点。 集群中的缓冲区是一个环型数组&#xff0c;即若从节点宕机时间过长&#xff0c;可能导致命令被覆盖。 主从集群优化 哨兵原理 哨兵是一个集群来确保哨兵不出现问题。 服务状态监控 选举…...

STL序列式容器之list

相较于vector的连续性空间&#xff0c;list相对比较复杂&#xff1b;list内部使用了双向环形链表的方式对数据进行存储&#xff1b;list在增加元素时&#xff0c;采用了精准的方式分配一片空间对数据及附加指针等信息进行存储&#xff1b; list节点定义如下 template<clas…...

docker:基于Dockerfile镜像制作完整案例

目录 摘要目录结构介绍起始目录package目录target目录sh目录init.sh脚本start.sh脚本stop.sh脚本restart.sh脚本 config目录 步骤1、编写dockerfilescript.sh脚本 2、构件镜像查看镜像 3、保存镜像到本地服务器4、复制镜像文件到指定目录&#xff0c;并执行init.sh脚本5、查看挂…...

微信小程序自定义顶部导航栏(适配各种机型)

效果图 1.pages.js&#xff0c;需要自定义导航栏的页面设置"navigationStyle": "custom" 2.App.vue,获取设备高度及胶囊位置 onLaunch: function () {// 系统信息const systemInfo uni.getSystemInfoSync()// 胶囊按钮位置信息const menuButtonInfo uni.…...

sslSocketFactory not supported on JDK 9+

clientBuilder.sslSocketFactory(SSLSocketFactory) not supported on JDK 9 at okhttp3.internal.platform.Jdk9Platform.trustManager(Jdk9Platform.kt:61) at okhttp3.OkHttpClient$Builder.sslSocketFactory(OkHttpClient.kt:751) at 1.升版本4.9.3以上 2、加个函数获取X…...

[Codesys]常用功能块应用分享-BMOV功能块功能介绍及其使用实例说明

官方说明 功能说明 参数 类型 功能 pbyDataSrcPOINTER TO BYTE指向源数组指针uiSizeUINT要移动数据的BYTE数pbyDataDesPOINTER TO BYTE指向目标数组指针 实例应用-ST IF SYSTEM_CLOCK.AlwaysTrue THENCASE iAutoState OF0: //读写完成信号在下次读写信号的上升沿或复位信号…...

大语言模型通用能力排行榜(2024年11月8日更新)

数据来源SuperCLUE 榜单数据为通用能力排行榜 排名 模型名称 机构 总分 理科 文科 Hard 使用方式 发布日期 - o1-preview OpenAI 75.85 86.07 76.6 64.89 API 2024年11月8日 - Claude 3.5 Sonnet&#xff08;20241022&#xff09; Anthropic 70.88 82.4…...

信息技术引领未来:大数据治理的实践与挑战

信息技术引领未来&#xff1a;大数据治理的实践与挑战 在信息技术日新月异的今天&#xff0c;大数据已成为企业和社会发展的重要驱动力。大数据治理&#xff0c;作为确保数据质量、安全性和合规性的关键环节&#xff0c;正面临着前所未有的实践挑战与机遇。本文将探讨信息技术…...

Git 分⽀规范 Git Flow 模型

前言 GitFlow 是一种流行的 Git 分支管理策略&#xff0c;由 Vincent Driessen 在 2010 年提出。它提供了一种结构化的方法来管理项目的开发、发布和维护&#xff0c;特别适合大型和复杂的项目。GitFlow 定义了一套明确的分支模型和工作流程&#xff0c;使得团队成员可以更有效…...

基于YOLOv8深度学习的公共卫生防护口罩佩戴检测系统(PyQt5界面+数据集+训练代码)

在全球公共卫生事件频发的背景下&#xff0c;防护口罩佩戴检测成为保障公众健康和控制病毒传播的重要手段之一。特别是在人员密集的公共场所&#xff0c;例如医院、学校、公共交通工具等地&#xff0c;口罩的正确佩戴对降低病毒传播风险、保护易感人群、遏制疫情扩散有着至关重…...

Nature Communications 基于触觉手套的深度学习驱动视触觉动态重建方案

在人形机器人操作领域&#xff0c;有一个极具价值的问题&#xff1a;鉴于操作数据在人形操作技能学习中的重要性&#xff0c;如何有效地从现实世界中获取操作数据的完整状态&#xff1f;如果可以&#xff0c;那考虑到人类庞大规模的人口和进行复杂操作的简单直观性与可扩展性&a…...

构建SSH僵尸网络

import argparse import paramiko# 定义一个名为Client的类&#xff0c;用于表示SSH客户端相关操作 class Client:# 类的初始化方法&#xff0c;接收主机地址、用户名和密码作为参数def __init__(self, host, user, password):self.host hostself.user userself.password pa…...

WPF中MVVM工具包 CommunityToolkit.Mvvm

CommunityToolkit.Mvvm&#xff0c;也称为MVVM工具包&#xff0c;是Microsoft Community Toolkit的一部分。它是一个轻量级但功能强大的MVVM&#xff08;Model-View-ViewModel&#xff09;库&#xff0c;旨在帮助开发者更容易地实现MVVM设计模式。 特点 独立于平台和运行时&a…...

学习空闲任务函数

一、user_StopEnterTask 停止 进入任务 /* Private includes -----------------------------------------------------------*/ //includes #include "user_TasksInit.h" #include "user_MPUCheckTask.h"#include "ui.h" #include "ui_Hom…...

Hyper-v中ubuntu与windows文件共享

Hyper-v中ubuntu与windows文件共享 前言相关链接第一步--第一个链接第二步--第二个链接测试与验证 前言 关于Hyper-V的共享我搞了好久&#xff0c;网上的很多教程太过冗余&#xff0c;我直接采用最简单的办法吧 相关链接 Hyper-V中Ubuntu 同windows系统共享文件夹-百度经验 …...

【软件工程】一篇入门UML建模图(类图)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…...

Windows 安装Docker For Desktop概要

Windows 安装docker 下载部分的工作需要使用科学技术。如果没有可以联系博主发送已下载好的文件。 本文档不涉及技术的讲解&#xff0c;仅有安装的步骤。 准备工作 包含下载与环境准备&#xff0c;下载的文件仅下载&#xff0c;在后续步骤进行安装。 微软关于wsl的文档&…...

解决循环依赖报错问题

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘asyncTaskServiceImpl’: Bean with name ‘asyncTaskServiceImpl’ has been injected into other beans [resignServiceImpl] in its raw version as part…...

代码随想录第46期 单调栈

这道题主要是单调栈的简单应用 class Solution { public:vector<int> dailyTemperatures(vector<int>& T) {vector<int> result(T.size(),0);stack<int> st;st.push(0);for(int i1;i<T.size();i){if(T[i]<T[st.top()]){st.push(i);}else{wh…...

中仕公考怎么样?事业编面试不去有影响吗?

事业编考试笔试已经通过&#xff0c;但是面试不去参加会有影响吗&#xff1f; 1. 自动放弃面试资格&#xff1a;未能按时出席事业单位的面试将被视为主动放弃该岗位的竞争机会。 2. 个人信誉问题&#xff1a;面试作为招聘流程的关键步骤&#xff0c;无故缺席可能被解释为诚信…...

有没有类似书签的wordpress主题/建立网站的主要步骤

准备两台Tomcat7与一个Redis集群。将jedis-2.9.0.jar、commons-pool2-2.4.2.jar、tomcat-redis-session-manager-2.1.0加入到两个Tomcat lib中。链接: https://pan.baidu.com/s/1dFvoUVB 密码: r333系统有多个JDK环境&#xff0c;需要将两个Tomcat7指定为JDK7&#xff0c;修改b…...

做职业背景调查的网站/培训总结精辟句子

运行 $mocha会运行全部./test/*的测试文件 运行指定的部分文件 $ mocha --grep login-failiure.js $ mocha --grep login-*.js运行指定的测试 假设这样的文件&#xff1a; it(logs a, function(done) {console.log(a);done(); });it(logs b, function(done) {console.log(b);do…...

开发电子商务系统的五个步骤/seo技术培训

ViewPager的简介&#xff1a; 必定开发者已经使用过viewPager&#xff0c;例如多张图片结合的新手引导页&#xff0c;或者现在普遍应用的主界面都是FragmentviewPager来实现。它是support-v4下的一个类&#xff0c;直接继承ViewGroup。最大的作用是使界面左右滑动。使用很简单…...

做电影网站怎么赚钱/莆田百度seo公司

继承&#xff1a;extends. 继承的优点&#xff1a;代码的可重用性。子类可以扩展父类的属性和方法。父类的属性和方法可用于子类。 使用“is&#xff0c;a”关系判断继承。 父类&#xff1a; public class Pet{private String name; //名字private int health; //健康值private…...

wordpress草稿发布/南京seo网站优化

常见的STL容器--集合setset翻译为集合&#xff0c;是一个内部自动有序(递增)且不含重复元素的容器。如果要使用set,需要使用头文件#include<set>set的定义 set<typename> name; //和vector类似set容器内元素的访问:只能通过迭代器(iterator)访问 //注意:可以通过*i…...

南县做网站推荐/黑帽seo

一.故障描述由8块盘组成的RAID5, 上层是EXT3文件系统,由于误删除导致文件系统中的邮件丢失二.镜像磁盘为防止数据恢复过程中由于误操作对原始磁盘造成二次破坏, 使用winhex软件为每块磁盘做镜像, 以后所有的数据恢复操作都在镜像盘上进行, 不会对原始磁盘造成影响镜像结果如下&…...