linux 之cgroup

1、Linux 调度策略

源码路径:include/uapi/linux/sched.h

 2、查看进程的调度策略命令- chrt

         - chrt  -p  #进程ID         

                

         -chrt -p -f  #修改进程当前的调度策略 

3、实时进程的要求

所谓的实时进程, 也就是那些对响应时间要求比较高的进程.

这类进程需要在限定的时间内处理用户的请求, 因此, 在限定的这段时间内, 需要占用所有CPU资源, 并且不能被其它进程打断.

在这种情况下, 如果实时进程中出现了类似死循环之类的情况, 就会导致整个系统无响应.

因为实时进程的CPU优先级高, 并且未处理完之前是不会释放CPU资源的.

所以, 内核中需要有一种方式来限制实时进程的CPU资源占用.

4、通过命令查看系统中的实时进程的占用cpu的时间配置

sysctl -n kernel.sched_rt_period_us     #实时进程调度占用cpu 的us时间

sysctl -n kernel.sched_rt_runtime_us   #实时进程实际运行的时间,占用95%period_us

这两个参数的差值时间,是用于调度其他进程,避免实时进程卡住,导致整个系统死掉;

如果觉得占比或多或少,可以通过 sysctl -w kernel.sched_rt_runtime_us= ? ,进行调整;

4、cgroup  控制资源

打开内核配置文件中:CONFIG_CGROUPS=y

第一步:挂在cgroup 文件系统,通过cgroup文件系统,就可以限制cpu资源

mount -t cgroup -o cpu cgroup /mnt

 查看挂载后的文件,

第二步:创建cgroup 的子

#cd /mnt

#mkdir {A,B}   在  /mnt/ 路径下 创建了 文件目录 A 和文件目录B 

#cd A    进入目录A

#cat ./cpu.shares   查看cpu的占用为 1024

# echo pid > ./tasks  把某个进程加入到子系统A中

# echo $$  获取当前shell/终端的进程ID 

第三步:修改B中占用的CPU率

修改 B中 cpu.shares 的1024 为 512 ,算百分比: 512/(1024+512) =1/3;

5、cpu绑定

上面所做的是控制每个组的CPU的总体占用率, 但是不能控制某个组的进程固定在某个物理CPU上运行。

要想将 cgroup 绑定到某个固定的CPU上, 需要使用 cpuset 子系统.

首先, 查看系统是否支持 cpuset 子系统, 也就是看内核编译选项 CONFIG_CPUSETS 是否设为y。

第一步:重新挂载 mount -t cgroup -o cpuset cgroup /mnt

第二步:分配cpuset.cpus 和 cpuset.mems, echo 1 > ./cpuset.cpus echo 1024 > ./cpuset.mems 

第三步: echo pid > ./tasks 

完成进程的绑核;

6、内存的控制

mount -t cgroup -o memory memcg /mnt

在目录/mnt下, memory.limit_in_bytes 是限制内存的大小,

echo 10M > /mnt/A/memory.limit_in_bytes     #设置组A的内存限制10M

echo PID > /mnt/A/tasks                  #进程加入组A

7、cgroup v1 和cgroup v 2 的研究

内核在cmdline 增加 cgroup_no_v1=all,禁用 cgroup v1使用 cgroup v2;

代码分析:

struct task_struct{
#ifdef CONFIG_CPUSETS
        /* Protected by ->alloc_lock: */
        nodemask_t                      mems_allowed;
        /* Seqence number to catch updates: */
        seqcount_spinlock_t             mems_allowed_seq;
        int                             cpuset_mem_spread_rotor;
        int                             cpuset_slab_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
        /* Control Group info protected by css_set_lock: */
        struct css_set __rcu            *cgroups;
        /* cg_list protected by css_set_lock and tsk->alloc_lock: */
        struct list_head                cg_list;
#endif

}

task_struct 指向 struct css_set 数据,

struct css_set {

struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
 

}

一个进程可以关联多个资源对象,进程通过 css_set 结构来收集不同控制组的 资源统计对象

如上所示;所以,当把一个进程添加到一个 控制组 时,将会把 控制组 关联的 资源统计对象 添加到进程的 cgroups 字段中,从而使进程受到这些 资源统计对象 的限制;

这个结构struct cgroup_subsys_state 最重要的就是存储的进程与特定子系统相关的信息。通过它,可以将task_struct和cgroup连接起来了:task_struct->css_set->cgroup_subsys_state->cgroup。

struct cgroup_subsys  代表具体的子系统,memory 或者 cpu; 这个结构体里包含树的数据结构,根据这个结构体,cgroup 文件系统可以构成树的形状;

每种资源子系统都由一个名为 cgroup_subsys 的结构来描述,如下所示:

struct cgroup_subsys {
        struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);
        int (*css_online)(struct cgroup_subsys_state *css);
        void (*css_offline)(struct cgroup_subsys_state *css);
        void (*css_released)(struct cgroup_subsys_state *css);
        void (*css_free)(struct cgroup_subsys_state *css);
        void (*css_reset)(struct cgroup_subsys_state *css);
        void (*css_rstat_flush)(struct cgroup_subsys_state *css, int cpu);
        int (*css_extra_stat_show)(struct seq_file *seq,
                                   struct cgroup_subsys_state *css);

        int (*can_attach)(struct cgroup_taskset *tset);
        void (*cancel_attach)(struct cgroup_taskset *tset);
        void (*attach)(struct cgroup_taskset *tset);
        void (*post_attach)(void);
        int (*can_fork)(struct task_struct *task,
                        struct css_set *cset);
        void (*cancel_fork)(struct task_struct *task, struct css_set *cset);
        void (*fork)(struct task_struct *task);
        void (*exit)(struct task_struct *task);
        void (*release)(struct task_struct *task);
        void (*bind)(struct cgroup_subsys_state *root_css);

        bool early_init:1;

        const char *name; //子系统的名字
        /* link to parent, protected by cgroup_lock() */
        struct cgroup_root *root;   //根节点
 

...
}

从 cgroup_subsys 结构的定义可以看出,其主要定义了一些方法和关联的层级。比如:create 方法主要用于当新建一个 控制组 时,创建一个新的 资源统计对象 与其关联;而 root 字段指向关联的层级根节点;

如内存子系统的定义如下:

// 定义在文件:./mm/memcontrol.c

struct cgroup_subsys mem_cgroup_subsys = {
    .name        = "memory",
    .subsys_id   = mem_cgroup_subsys_id,
    .create      = mem_cgroup_create,
    .pre_destroy = mem_cgroup_pre_destroy,
    .destroy     = mem_cgroup_destroy,
    .populate    = mem_cgroup_populate,
    .attach      = mem_cgroup_move_task,
    .early_init  = 0,
};

struct cgroup 结构体对应 cgroup 文件系统的目录,结构体如下:

struct cgroup {

        struct kernfs_node *kn;         /* cgroup kernfs entry */
        struct cgroup_file procs_file;  /* handle for "cgroup.procs" */
        struct cgroup_file events_file; /* handle for "cgroup.events" */

       /* Private pointers for each registered subsystem */
        struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];

        //这个 struct cgroup_subsys_state 是各个子系统的抽象出的通用部分。//

}

在 Linux 内核中也有类似的 “计数器“,使用 cgroup_subsys_state 结构来表示(我们称它为 资源统计对象),其定义如下

struct cgroup_subsys_state {
    struct cgroup *cgroup; // 指向控制组对象
    atomic_t refcnt;       // 引用计数器
    unsigned long flags;   // 标志位
};

cgroup_subsys_state 结构看起来非常简单,这只是表面现象。内核为了将所有的 资源统计对象 抽象化(也就是都能用 cgroup_subsys_state 指针来指向所有类型的 资源统计对象),才定义出这个通用的部分,实际上的 资源统计对象 是比较复杂的

例如内存的 资源统计对象 定义如下:

struct mem_cgroup {
    // 资源统计对象通用部分
    struct cgroup_subsys_state css;

    // 资源统计对象私有部分
    struct res_counter res;  // 用于统计进程组的内存使用情况
    struct mem_cgroup_lru_info info;
    int prev_priority;
    struct mem_cgroup_stat stat;
};

mem_cgroup 结构与 cgroup_subsys_state 结构的关系如下图所示:

资源统计对象 必须与 控制组 绑定,才能实现限制 控制组 对资源的使用。前面我们了解到 cgroup 结构中有个名为 subsys 的字段,如下代码所示:

struct cgroup {
    ...
    // 当前控制组关联的资源统计对象
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; 
    ...
};

数据结构之间的关系总图:

内核启动调用流程:

start_kernel调用cgroup_init_early在系统启动时进行CGroup初始化;

start_kernel->cgroup_init_early 

start_kernel->cgroup_init,在start_kernel 函数后面执行;

下面是每个子系统的创建流程:

start_kernel->cgroup_init_early->cgroup_init_subsys;如下图所示

cgroup_init_subsys函数分析:

static void __init cgroup_init_subsys(struct cgroup_subsys *ss, bool early)
{
        struct cgroup_subsys_state *css;
   dump_stack();
        pr_debug("Initializing cgroup subsys %s\n", ss->name);

        mutex_lock(&cgroup_mutex);

        idr_init(&ss->css_idr);
        INIT_LIST_HEAD(&ss->cfts);

        /* Create the root cgroup state for this subsystem */
        ss->root = &cgrp_dfl_root;
        css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss));
        /* We don't handle early failures gracefully */
        BUG_ON(IS_ERR(css));
        init_and_link_css(css, ss, &cgrp_dfl_root.cgrp);

        /*
         * Root csses are never destroyed and we can't initialize
         * percpu_ref during early init.  Disable refcnting.
         */
        css->flags |= CSS_NO_REF;

        if (early) {
                /* allocation can't be done safely during early init */
                css->id = 1;
        } else {
                css->id = cgroup_idr_alloc(&ss->css_idr, css, 1, 2, GFP_KERNEL);
                BUG_ON(css->id < 0);
        }

        /* Update the init_css_set to contain a subsys
         * pointer to this state - since the subsystem is
         * newly registered, all tasks and hence the
         * init_css_set is in the subsystem's root cgroup. */
        init_css_set.subsys[ss->id] = css;

        have_fork_callback |= (bool)ss->fork << ss->id;
        have_exit_callback |= (bool)ss->exit << ss->id;
        have_release_callback |= (bool)ss->release << ss->id;
        have_canfork_callback |= (bool)ss->can_fork << ss->id;

        /* At system boot, before all subsystems have been
         * registered, no tasks have been forked, so we don't
         * need to invoke fork callbacks here. */
        BUG_ON(!list_empty(&init_task.tasks));

        BUG_ON(online_css(css));

        mutex_unlock(&cgroup_mutex);
}

css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss));  这个是通过css_alloc 回调函数,具体就是每个子系统的cgroup_subsys 的全局方法,进行创建 cgroup_subsys_state;

对于 CPU 来讲,css_alloc 函数就是 cpu_cgroup_css_alloc。这里面会调用 sched_create_group 创建一个 struct task_group。在这个结构中,第一项就是 cgroup_subsys_state,也就是说,task_group 是 cgroup_subsys_state 的一个扩展,最终返回的是指向 cgroup_subsys_state 结构的指针,可以通过强制类型转换变为 task_group。

cpu_cgroup_subsys

cpu_cgroup_css_alloc:

struct task_group:

在 task_group 结构中,有一个成员是 sched_entity,是调度的实体,也即这一个 task_group 也是一个调度实体;

接下来,online_css 会被调用。对于 CPU 来讲,online_css 调用的是 cpu_cgroup_css_online。它会调用 sched_online_group->online_fair_sched_group.

通过循环遍历每个可能的CPU,该函数在每个CPU上执行一系列操作,包括更新时钟、附加调度实体到CFS队列和同步限制。这些操作旨在在每个CPU上启动公平调度组的调度。

在 cpu子系统中,即在cgroup 中,看到的文件,是什么?

在cpu_cgrp_subsys 对象中,有 

        .legacy_cftypes = cpu_legacy_files,
        .dfl_cftypes    = cpu_files,
,这两个对象是具体的操作文件。

有了这些文件,如何与上层进行通信?这里是通过 虚拟文件系统 cgroup 文件系统;

在cgroup_init 最后,就是建立cgourp_fs_type 和cgroup2_fs_type.

当需要挂载 cgroup 文件系统时,通常会调用 cgroup_init_fs_context 函数来初始化 cgroup 文件系统的上下文。该函数会设置一些必要的参数,如文件系统类型、挂载标志等,并返回一个已初始化的文件系统上下文对象。

然后,将该文件系统上下文对象传递给 mount 函数,以执行实际的挂载操作。mount 函数会使用 cgroup_init_fs_context 返回的文件系统上下文对象来完成 cgroup 文件系统的挂载过程;

对于 cgroup 文件系统补充:

cgroup_init_cftypes() 把 cgroup_kf_ops 赋值给 cgroup_base_files.

虚拟文件系统-> kernfs->kernfs_ops;

总结: cgroup 资源控制强大,可以参考cgroup配置文件,进行善用;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值