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配置文件,进行善用;