在真正的生产实践过程中,对于CPU的隔离要求比容器的默认策略要严格的多,因而需要对于Linux内核底层机制有所理解,才能很好的做CPU隔离,甚至在离线业务混合部署隔离等策略。
本文不打算讲述Cgroup的使用层原理,因为这类文章已经比较多了,而是希望从更深层的原理去解析。
一、系统的初始化与Cgroup的初始化
cgroup的机制起作用要从Linux系统的初始化开始。
内核是如何初始化的呢?
内核的启动从入口函数start_kernel()开始。在init/main.c文件中,start_kernel相当于内核的main函数。打开这个函数,你会发现,里面是各种各样初始化函数XXXX_init。
在操作系统里面,先要有个创始进程init_task,它的定义是struct task_struct init_task = INIT_TASK(init_task)。它是系统创建的第一个进程,我们称为0号进程。这是唯一一个没有通过fork或者kernel_thread产生的进程,是进程列表的第一个。
rest_init的第一大工作是,用kernel_thread(kernel_init, NULL, CLONE_FS)创建第二个进程,这个是1号进程。1号进程对于操作系统来讲,有“划时代”的意义。因为它将运行一个用户进程。
rest_init第二大事情就是第三个进程,就是2号进程kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),这里的函数kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。
在系统初始化的时候,cgroup也会进行初始化,在start_kernel中,cgroup_init_early和cgroup_init都会被调用进行各个Cgroup子系统的初始化。
其实是初始化一些Cgroup相关的内核数据结构,例如对于CPU来讲,有下面的数据结构。
cpuset_cgrp_subsys
struct cgroup_subsys cpuset_cgrp_subsys = {
.css_alloc = cpuset_css_alloc,
.css_online = cpuset_css_online,
.css_offline = cpuset_css_offline,
.css_free = cpuset_css_free,
.can_attach = cpuset_can_attach,
.cancel_attach = cpuset_cancel_attach,
.attach = cpuset_attach,
.post_attach = cpuset_post_attach,
.bind = cpuset_bind,
.fork = cpuset_fork,
.legacy_cftypes = files,
.early_init = true,
};
cpu_cgrp_subsys
struct cgroup_subsys cpu_cgrp_subsys = {
.css_alloc = cpu_cgroup_css_alloc,
.css_online = cpu_cgroup_css_online,
.css_released = cpu_cgroup_css_released,
.css_free = cpu_cgroup_css_free,
.fork = cpu_cgroup_fork,
.can_attach = cpu_cgroup_can_attach,
.attach = cpu_cgroup_attach,
.legacy_cftypes = cpu_files,
.early_init = true,
};
cgroup_init_subsys会对各个cgroup_subsys做初始化,cgroup_init_subsys里面会做两件事情,一个是调用cgroup_subsys的css_alloc函数创建一个cgroup_subsys_state;另外就是调用online_css,也即调用cgroup_subsys的css_online函数,激活这个cgroup。
对于CPU来讲,css_alloc函数就是cpu_cgroup_css_alloc。这里面会调用 sched_create_group创建一个struct task_group。
struct task_group {
struct cgroup_subsys_state css;
#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity **se;
struct cfs_rq **cfs_rq;
unsigned long shares;
#ifdef CONFIG_SMP
atomic_long_t load_avg ____cacheline_aligned;
#endif
#endif
struct rcu_head rcu;
struct list_head list;
struct task_group *parent;
struct list_head siblings;
struct list_head children;
struct cfs_bandwidth cfs_bandwidth;
};
在task_group结构中,有一个成员是sched_entity,也说明CPU的隔离和调度是强相关的。
二、CPU和进程的调度机制
在Linux里面,进程大概可以分成两种。
一种称为实时进程,也就是需要尽快执行返回结果的那种。这就好比我们是一家公司,接到的客户项目需求就会有很多种。有些客户的项目需求比较急,比如一定要在一两个月内完成的这种,客户会加急加钱,那这种客户的优先级就会比较高。
另一种是普通进程,大部分的进程其实都是这种。这就好比,大部分客户的项目都是普通的需求,可以按照正常流程完成,优先级就没实时进程这么高,但是人家肯定也有确定的交付日期。
那很显然,对于这两种进程,我们的调度策略肯定是不同的。
进程在Linux内核中被task_struct结构进行管理,在task_struct中,有一个成员变量,我们叫调度策略。
它有以下几个定义:
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
policy指定;"],[20,"\n","24:\"evrr\"|bullet-id:\"iYwU\"|bullet:\"circle\""],[20,"fair_sched_class就是普通进程的调度策略;"],[20,"\n","24:\"r7lj\"|bullet-id:\"iYwU\"|bullet:\"circle\""],[20,"idle_sched_class就是空闲进程的调度策略。"],[20,"\n","24:\"JAqt\"|bullet-id:\"iYwU\"|bullet:\"circle\""]]">
在task_struct里面,还有这样的成员变量:
const struct sched_class *sched_class;
调度策略的执行逻辑,就封装在这里面,它是真正干活的那个。
sched_class有几种实现:
-
stop_sched_class优先级最高的任务会使用这种策略,会中断所有其他线程,且不会被其他任务打断;
-
dl_sched_class就对应上面的deadline调度策略;
-
rt_sched_class就对应RR算法或者FIFO算法的调度策略,具体调度策略由进程的task_struct->policy指定;
-
fair_sched_class就是普通进程的调度策略;
-
idle_sched_class就是空闲进程的调度策略。
普通进程使用的调度策略是fair_sched_class,CFS全称Completely Fair Scheduling,叫完全公平调度。
在每个CPU上都有一个队列rq,这个队列里面包含多个子队列,例如rt_rq和cfs_rq,不同的队列有不同的实现方式,cfs_rq就是用红黑树实现的。
当某个CPU需要找下一个任务执行的时候,会按照优先级依次调用调度类,不同的调度类操作不同的队列。当然rt_sched_class先被调用,它会在rt_rq上找下一个任务,只有找不到的时候,才轮到fair_sched_class被调用,它会在cfs_rq上找下一个任务。这样保证了实时任务的优先级永远大于普通任务。
sched_class定义的与调度有关的函数。
-
enqueue_task向就绪队列中添加一个进程,当某个进程进入可运行状态时,调用这个函数;
-
dequeue_task 将一个进程从就绪队列中删除;
-
pick_next_task 选择接下来要运行的进程;
-
put_prev_task 用另一个进程代替当前运行的进程;
-
set_curr_task 用于修改调度策略;
-
task_tick 每次周期性时钟到的时候,这个函数被调用,可能触发调度。
在内核初始化的时候,css_online函数会激活cgroup。这里面,对于每一个CPU,取出每个CPU的运行队列rq,也取出task_group的sched_entity,然后通过attach_entity_cfs_rq将sched_entity添加到运行队列中。
这样调度的时候,cgroup就能起作用了。
三、将Cgroup暴露到cgroupfs进行管理
在Linux里面,一切皆文件。因而cgroup的配置也是通过类似文件系统的机制来的。
我们先来看一下Linux文件系统的机制。
想要操作文件系统,第一件事情就是挂载文件系统。