极简组调度-CGroup如何限制cpu

这篇具有很好参考价值的文章主要介绍了极简组调度-CGroup如何限制cpu。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 说明
1> linux内核关于task调度这块是比较复杂的,流程也比较长,要从源码一一讲清楚很容易看晕,因此需要简化,抓住主要的一个点,抛开无关的部分才能讲清楚核心思想
2> 本篇文章主要是讲清楚在cfs公平调度算法中,CGroup如何限制cpu使用的主要过程,所以与此无关的代码一律略过
3> 本篇源码来自CentOS7.6的3.10.0-957.el7内核
4> 本篇内容以《极简cfs公平调度算法》为基础,里面讲过的内容这里就不重复了
5> 为了极简,这里略去了CGroup嵌套的情况
 
2. CGroup控制cpu配置
CGroup控制cpu网上教程很多,这里就不重点讲了,简单举个创建名为test的CGroup的基本流程
1> 创建一个/sys/fs/cgroup/cpu/test目录
2> 创建文件cpu.cfs_period_us并写入100000,创建cpu.cfs_quota_us并写入10000
表示每隔100ms(cfs_period_us)给test group分配一次cpu配额10ms(cfs_quota_us),在100ms的周期内,group中的进程最多使用10ms的cpu时长,这样就能限制这个group最多使用单核10ms/100ms = 10%的cpu
3> 最后创建文件cgroup.procs,写入要限制cpu的pid即生效
 
3. CGroup控制cpu基本思想
1> 《极简cfs公平调度算法》中我们讲过cfs调度是以se为调度实例的,而不是task,因为group se也是一种调度实例,所以将调度实例抽象为se,统一以se进行调度
极简组调度-CGroup如何限制cpu
2> CGroup会设置一个cfs_period_us的时长的定时器,定时给group分配cfs_quota_us指定的cpu配额
3> 每次group下的task执行完一个时间片后,就会从group的cpu quota减去该task使用的cpu时长
4> 当group的cpu quota用完后,就会将整个group se throttle,即将其从公平调度运行队列中移出,然后等待定时器触发下个周期重新分配cpu quota后,重启将group se移入到cpu rq上,从而达到控制cpu的效果。
 
一句话说明CGroup的控制cpu基本思想:
进程执行完一个时间片后,从cpu quota中减去其执行时间,当quota使用完后,就将其从rq中移除,这样在一个period内就不会再调度了。
 
4. 极简CGroup控制cpu相关数据结构
4.1 名词解释
 
说明
task group
进程组,为了支持CGroup控制cpu,引入了组调度的概念,task group即包含所有要控制cpu的task集合以及配置信息。
group task
本文的专有名词,是指一个进程组下的task,这些task受一个CGroup控制
cfs_bandwidth
task_group的重要成员,包含了所要控制cpu的period,quota,定时器等信息
throttle
当group se在一个设定的时间周期内,消耗完了指定的cpu配额,则将其从cpu运行队列中移出,并不再调度。
注意:处于throttled状态的task仍是Ready状态的,只是不在rq上。
unthrottle
将throttle状态的group se,重新加入到cpu运行队列中调度。
 
4.2 cfs调度相关数据结构
struct cfs_rq
{
    struct rb_root tasks_timeline;                      // 以vruntime为key,se为value的红黑树根节点,schedule时,cfs调度算法每次从这里挑选vruntime最小的se投入运行
    struct rb_node* rb_leftmost;                        // 最左的叶子节点,即vruntime最小的se,直接取这个节点以加快速度
    sched_entity* curr;                                 // cfs_rq中当前正在运行的se
    struct rq* rq;                                       /* cpu runqueue to which this  cfs_rq is attached */
    struct task_group* tg;                              /* group that "owns" this  runqueue */
    int throttled;                                      // 表示该cfs_rq所属的group se是否被throttled
    s64 runtime_remaining;                              // cfs_rq从全局时间池申请的时间片剩余时间,当剩余时间小于等于0的时候,就需要重新申请时间片
};
 
struct sched_entity
{
    unsigned int            on_rq;                          // se是否在rq上,不在的话即使task是Ready状态也不会投入运行的
    u64              vruntime;                              // cpu运行时长,cfs调度算法总是选择该值最小的se投入运行
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq* cfs_rq;                        // se所在的cfs_rq,如果是普通task  se,等于rq的cfs_rq,如果是group中的task,则等于group的cfs_rq
    /* rq "owned" by this entity/group: */
    struct cfs_rq* my_q;                          // my_q == NULL表示是一个普通task se,否则表示是一个group se,my_q指向group的cfs_rq
};
 
struct task
{
    struct sched_entity se;
};
 
struct rq
{
    struct cfs_rq cfs;                          // 所有要调度的se都挂在cfs rq中
    struct task_struct* curr;                   // 当前cpu上运行的task
};

本文中的sched_entity定义比《极简cfs公平调度算法》中的要复杂些,各种cfs_rq容易搞混,这里讲一下cfs公平调度挑选group task调度流程(只用到了my_q这个cfs_rq),以梳理清楚其关系

1> 当se.my_q为NULL时,表示一个task se,否则是group se
 
2> 选择当group task3的流程
极简组调度-CGroup如何限制cpu
 
3> 选择当group task的代码
task_struct *pick_next_task_fair(struct rq *rq)
{
    struct cfs_rq *cfs_rq = &rq->cfs;       // 开始的cfs_rq为rq的cfs
    do {
        se = pick_next_entity(cfs_rq);      // 《极简cfs公平调度算法》中讲过这个函数,其就是取cfs_rq->rb_leftmost,即最小vruntime的se
        cfs_rq = group_cfs_rq(se);          // 取se.my_q,如果是普通的task se,cfs_rq = NULL,这里就会退出循环,如果是group se,cfs_rq = group_se.my_q,然后在group se的cfs_rq中继续寻找vruntime最小的se
    } while (cfs_rq);
  
    return task_of(se);
}
 
cfs_rq *group_cfs_rq(struct sched_entity *grp)
{
    return grp->my_q;
}

 

4.3 CGroup控制cpu的数据结构
struct cfs_bandwidth
{
    ktime_t period;                             // cpu.cfs_period_us的值
    u64 quota;                                  // cpu.cfs_quota_us的值
    u64 runtime;                                // 当前周期内剩余的quota时间
    int timer_active;                           // period_timer是否激活
    struct hrtimer period_timer;                // 定时分配cpu quota的定时器,定时器触发时会更新runtime
};
 
struct task_group
{
    struct sched_entity** se;                   /* schedulable entities of this group  on each cpu */
    struct cfs_rq** cfs_rq;                     /* runqueue "owned" by this group on  each cpu */
    struct cfs_bandwidth cfs_bandwidth;         // 管理记录CGroup控制cpu的信息
};

 

1> task_group.se是一个数组,每个cpu都有一个其对应的group se

极简组调度-CGroup如何限制cpu
 
2>task_group.cfs_rq也是一个数组,每个cpu都有一个其对应的cfs_rq,每个cpu上的group se.my_q指向该cpu上对应的group cfs_rq,group下的task.se.cfs_rq也指向该group cfs_rq
极简组调度-CGroup如何限制cpu
 
3> cfs_bandwidth是CGroup管理控制cpu的关键数据结构,具体用途见定义
 
5. 极简流程图
从throttle到unthrottle:
极简组调度-CGroup如何限制cpu
 
6. 极简code
6.1 检测group se cpu quota的使用
1>《极简cfs公平调度算法》中我们讲过,task调度的发动机时钟中断触发后,经过层层调用,会到update_curr()这里,update_curr()不仅++了当前se的vruntime,还调用 account_cfs_rq_runtime()统计并检测group se是否使用完了cpu quota
void update_curr(struct cfs_rq* cfs_rq)
{
    struct sched_entity* curr = cfs_rq->curr;
    curr->vruntime += delta_exec;   // 增加se的运行时间
    account_cfs_rq_runtime(cfs_rq, delta_exec);
}
 
2> account_cfs_rq_runtime()--了cfs_rq->runtime_remaining,如果runtime_remaining不足就调用assign_cfs_rq_runtime()从task group中分配,当分配不到(即表示当前周期的cpu quota用完了)就设置resched标记
void account_cfs_rq_runtime(struct cfs_rq* cfs_rq, u64 delta_exec)
{
    cfs_rq->runtime_remaining -= delta_exec;
    if (cfs_rq->runtime_remaining > 0)
        return;
    // 如果runtime_remaining不够了,则要向task group分配cpu quota,分配失败则设置task的thread flag为TIF_NEED_RESCHED,表示需要重新调度
    if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr))
        resched_curr(cfs_rq->rq);
}

 

3> assign_cfs_rq_runtime()就是从task_group.cfs_bandwidth.runtime减去要分配的时间片,如果其为0就分配失败
/* returns 0 on failure to allocate runtime */
int assign_cfs_rq_runtime(struct cfs_rq* cfs_rq)
{
    struct cfs_bandwidth* cfs_b = cfs_rq->tg->cfs_bandwidth;;
 
    // 如果有限制cpu,则减去最小分配时间,如果cfs_b->runtime为0,那就没有时间可分配了,本函数就会返回0,表示分配失败
    amount = min(cfs_b->runtime, min_amount);
    cfs_b->runtime -= amount;
    cfs_rq->runtime_remaining += amount;
    return cfs_rq->runtime_remaining > 0;
}

 

6.2 throttle
6.1中我们看到cpu quota被使用完了,标记了resched,要进行重新调度了,但并没有看到throttle。这是因为上面的代码还在中断处理函数中,是不能进行实际调度的,所以只设置resched标记,真正throttle干活的还是在schedule()中(还记得《极简cfs公平调度算法》中讲的task运行时间片到了后,进行task切换,也是这样干的吗?)
极简组调度-CGroup如何限制cpu
 
1> 每次中断返回返回或系统调用返回时(见ret_from_intr),都会判定TIF_NEED_RESCHED标记,如有则会调用schedule()重新调度,《极简cfs公平调度算法》中未暂开讲put_prev_task_fair(),而throttle就是在这里干的
void schedule()
{
    prev = rq->curr;
    put_prev_task_fair(rq, prev);
    // 选择下一个task并切换运行
    next = pick_next_task(rq);
    context_switch(rq, prev, next);
}
 
2> put_prev_task_fair() → put_prev_entity() → check_cfs_rq_runtime()
void put_prev_task_fair(struct rq* rq, struct task_struct* prev)
{
    struct sched_entity* se = &prev->se;
    put_prev_entity(se->cfs_rq, se);
}
 
void put_prev_entity(struct cfs_rq* cfs_rq, struct sched_entity* prev)
{
    check_cfs_rq_runtime(cfs_rq);
}

 

3> check_cfs_rq_runtime()这里判定runtime_remaining不足时,就要调用throttle_cfs_rq()进行throttle
void check_cfs_rq_runtime(struct cfs_rq* cfs_rq)
{
    if (cfs_rq->runtime_remaining > 0)
        return;
    throttle_cfs_rq(cfs_rq);
}

 

4> throttle_cfs_rq()将group se从rq.cfs_rq中移除,这样整个group下的task就不再会被调度了
void throttle_cfs_rq(struct cfs_rq* cfs_rq)
{
    struct sched_entity*  se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];       // 取对应cpu rq上的group se
    dequeue_entity(se->cfs_rq, se, DEQUEUE_SLEEP);                          //从cpu rq中删除group se
    cfs_rq->throttled = 1;                                                  // 标记group cfs_rq被throttled
}

 

6.3 cpu quota重新分配
6.2中group se被从rq移除后,不再会被调度,这时经过一个period周期,定时器激活后,就会再次加入到rq中重新调度
极简组调度-CGroup如何限制cpu
 
1> cfs_bandwidth的定期器初始化回调函数为sched_cfs_period_timer()
viod init_cfs_bandwidth(struct cfs_bandwidth* cfs_b)
{
    hrtimer_init(&cfs_b->period_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    cfs_b->period_timer.function = sched_cfs_period_timer;
}

 

2> 定时器到期后回调sched_cfs_period_timer(),其只是简单调用实际干活的do_sched_cfs_period_timer()
enum hrtimer_restart sched_cfs_period_timer(struct hrtimer* timer)
{
    idle = do_sched_cfs_period_timer(cfs_b, overrun);
    return idle ? HRTIMER_NORESTART : HRTIMER_RESTART;
}

 

3> do_sched_cfs_period_timer()调用__refill_cfs_bandwidth_runtime()重新分配task_group的runtime,然后调用distribute_cfs_runtime()进行unthrottle
int do_sched_cfs_period_timer(struct cfs_bandwidth* cfs_b, int overrun)
{
    __refill_cfs_bandwidth_runtime(cfs_b);
    distribute_cfs_runtime(cfs_b, runtime, runtime_expires);
}

 

4> __refill_cfs_bandwidth_runtime()就是将task_group.cfs_bandwidth.runtime重置为设置的cpu quota
void __refill_cfs_bandwidth_runtime(struct cfs_bandwidth* cfs_b)
{
    cfs_b->runtime = cfs_b->quota;
}

 

5> distribute_cfs_runtime()调用unthrottle_cfs_rq()将所有se加回到rq上去,这样group下的task就能重新调度了
u64 distribute_cfs_runtime(struct cfs_bandwidth* cfs_b, u64 remaining, u64  expires)
{
    struct cfs_rq* cfs_rq;
    list_for_each_entry_rcu(cfs_rq, &cfs_b->throttled_cfs_rq, throttled_list)
    {
        unthrottle_cfs_rq(cfs_rq);
    }
}
 
void unthrottle_cfs_rq(struct cfs_rq* cfs_rq)
{
    se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];
    enqueue_entity(cfs_rq, se, ENQUEUE_WAKEUP);     // 将se加回rq.cfs_rq的红黑树上
}

 文章来源地址https://www.toymoban.com/news/detail-413870.html

 

到了这里,关于极简组调度-CGroup如何限制cpu的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用cgroup工具对服务器某些/全部用户进行计算资源限制

    主要介绍,如何对指定/所有用户进行资源限定(这里主要介绍cpu和内存占用限制),防止某些用户大量占用服务器计算资源,影响和挤占他人正常使用服务器。 安装 cgroup 管理工具 使用 mount -t cgroup 命令检查验证 可以通过编写 /etc/cgconfig.conf 和 /etc/cgrules.conf 文件进行计算资

    2024年02月10日
    浏览(44)
  • 极简cfs公平调度算法

    1. 说明 1 linux内核关于task调度这块是比较复杂的,流程也比较长,要从源码一一讲清楚很容易看晕 2 本篇文章主要是讲清楚cfs公平调度算法如何将task在时钟中断驱动下切换调度,所以与此无关的代码一律略过 3 本篇只讲最简单的task调度,略过组调度,组调度在下一篇《极简

    2023年04月14日
    浏览(39)
  • ubuntu 如何在docker容器内部查看允许使用的cpu详细信息以及管理员是否限制了docker容器可使用的cpu核心范围

    如题。最近在跑强化学习代码,发现自己的代码跑的好慢好慢好慢,怀疑是可使用的cpu数量被管理员限制了,于是去学习了一下如何查看docker容器中cpu的相关信息。 这将输出关于系统处理器的详细信息,包括每个 CPU 的数量、型号和速度。 在 Docker 容器中,默认情况下,可以

    2024年02月13日
    浏览(48)
  • linux如何限制ssh

    在使用ssh远程登录我们的linux操作系统的时候,ssh 根据 ip,port来确定主机 为了增加系统的安全与稳定性 我们可以在终端(服务器)进行对ip 以及 port的限定,从而过滤掉不符合要求的ssh,让其无法连接到目标主机 1.限定port (配置较为复杂,安全系数较高) ssh 重要配置文件在

    2024年02月06日
    浏览(33)
  • linux的cgroup

    Linux的cgroup(Control Group)是一种内核特性,用于对进程组进行资源限制、优先级管理和统计等操作。cgroup可以将一组相关的进程组织在一起,并对它们施加各种资源控制策略,以确保系统资源的有效分配和管理。下面是对Linux的cgroup系统的详细描述: cgroup概念: cgroup是一种层

    2024年02月13日
    浏览(32)
  • linux中cgroup的简单使用

    Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在

    2024年02月14日
    浏览(28)
  • CKA 10_Kubernetes工作负载与调度 资源调度 资源限制 LimitRanger 资源配额 ResourceQuota

    官方文档: 概念 | 策略 | 限制范围 官方文档: 概念 | 策略 | 资源配额 默认情况下, Kubernetes 集群上的容器运行使用的计算资源没有限制。 使用资源配额,集群管理员可以以名字空间为单位,限制其资源的使用与创建。 在命名空间中,一个 Pod 或 Container 最多能够使用命名空

    2024年02月08日
    浏览(68)
  • Linux关于memory cgroup的几个要点

    概述 本文讲述memory cgroup比较容易误解的一些逻辑,如果不太经常使用和解决问题的话,对于memory cgroup的认知会比较浅显:cgroup memory用来限制进程的内存使用,但是我们进一步想如下的问题: 进程的内存可以分很多类型,比如page cache,slab,anon memory等,到底是限制的哪些内

    2024年02月07日
    浏览(34)
  • docker对cpu资源做限制

    ` 资源限制:可以对任务使用的资源总额进行限制 优先级分配:通过分配的cpu时间片数量以及磁盘IO带宽大小,实际上相当于控制了任务运行优先级 资源统计:可以统计系统的资源使用量,如cpu时长,内存用量等 任务控制:cgroup可以对任务执行挂起、恢复等操作 1、设置CPU使

    2024年02月08日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包