标签:睡眠 文件的 ps实现 unix 自动生成 release 等等 中间 tgid
cgroups资源限制
上一节中Docker背后的内核知识(一),我们了解了Docker背后使用的资源隔离技术namespace,通过系统调用构建了一个相对隔离的shell环境,也可以称之为简单的“容器”。这一节将讲解另一个强大的内核工具——cgroups。它不仅可以限制被namespace隔离起来的资源,还可以为资源设置权重、计算使用量、操控任务(进程或线程)开启和暂停等等。在介绍完基本概念后,将详细讲解Docker中使用到的cgroups内容。
cgroups是什么
cgroups(Control Groups)最初叫Process Container,由Google工程师(Paul Menage和Rohit Seth)于2006年提出,后来因为Container有多重含义容易引起误解,就在2007年更名为Control Groups,并被整合进Linux内核。顾名思义就是把进程放到一个组里面统一加以控制。官方的定义如下:
cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。
通俗的来说,cgroups可以限制、记录任务组使用的物理资源(包括:CPU、Memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。
对开发者来说,cgroups有如下四个特点:
本质上来说,cgroups是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
cgroups的作用
实现cgroups的主要目的是为不同用户层面的资源管理,提供一个统一化的接口。从单个进程的资源控制到操作系统层面的虚拟化。cgroups提供了以下四大功能:
过去有一段时间,内核开发者甚至把namespace也作为一个cgroups的子系统加入进来,也就是说cgroups曾经甚至还包含了资源隔离的能力。但是资源隔离会给cgroups带来许多问题,如pid namespace加入后,PID在循环出现的时候,cgroup会出现了命名冲突、cgroup创建后进入新的namespace导致其他子系统资源脱离了控制等等,所以在2011年就被移除了。
术语表
组织结构与基本规则
传统的Unix进程管理,实际上是先启动init进程作为根节点,再由init节点创建子进程作为子节点,而每个子节点又可以创建新的子节点,如此往复,形成一个树状结构。而系统中的多个cgroups也构成类似的树状结构,子节点从父节点继承属性。
它们最大的不同在于,系统中多个cgroup构成的层级并非单根结构,可以允许存在多个。如果任务模型是由init作为根节点构成的一棵树,那么系统中的多个cgroup则是由多个层级构成的森林。这样做的目的也很好理解,如果只有一个层级,那么所有的任务都将被迫绑定其上的所有子系统,这会给某些任务造成不必要的限制。在Doccker中,每个子系统独自构成一个层级,这样做非常易于管理。
了解了cgroups的组织结构,我们再来了解cgroups、任务、子系统、层级四者间的相互关系及其基本规则。
图1-4 同一个层级可以附加一个或多个子系统
图1-5 一个已经附加在某个层级上的子系统不能附加到其他含有别的子系统的层级上
图1-6 一个任务不能属于同一个层级的不同cgroup
图1-7 刚fork/clone出的子任务在初始状态与其父任务处于同一个cgroup
子系统简介
子系统实际上就是cgroups的资源控制系统,每种子系统独立地控制一种资源,目前Docker使用如下九种子系统,其中,net_cls任务子系统在内核中已经广泛实现,但是Docker尚未使用。以下是它们的用途:
上述子系统如何使用虽然很重要,但Docker并没有对cgroup本身做增强,容器用户一般也不需要直接操作cgroup。Linux中cgroup的实现形式表现为一个文件系统,因此需要mount这个文件系统才能够使用,挂载成功后,就能看到各类子系统。
# mount -t cgroup cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd) cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
在/sys/fs/cgroup/cpu的cpu子目录下创建控制组,控制组目录创建完成后,它下面就会有很多类似的文件了。
# cd /sys/fs/cgroup/cpu # mkdir cg1 # ls cg1/ cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
下面的例子展示如何限制当前shell的进程ID(PID)的CPU使用配额:
# echo $$ 14113 # echo 14113 >> /sys/fs/cgroup/cpu/cg1/tasks #限制14113进程 # echo 2000 > /sys/fs/cgroup/cpu/cg1/cpu.cfs_quota_us #将CPU限制为最高使用20%
在Docker的实现中,Docker daemon会在单独挂载了每一个子系统的控制目录(比如/sys/fs/cgroup/cpu)下创建一个名为docker的控制组,然后在docker控制组里面,再为每个容器创建一个以容器ID为名称的容器控制组,这个容器里的所有进程的进程号都会写到该控制组tasks中,并且在控制文件(比如cpu.cfs_quota_us)中写入预设的限制参数值。
cgroups实现方式及工作原理简介
在对cgroups规则和子系统有一定了解后,下面简单介绍操作系统内核级别上cgroups的工作原理。cgroups的实现本质上是给系统进程挂上钩子,当任务运行的过程中涉及到某个资源时,就会触发钩子上所附带的子系统进行检测,根据资源类别的不同,使用对应的技术进行资源限制和优先级分配。
cgroups如何判断资源超限及超出限额之后的措施
对于不同的系统资源,cgroups提供了统一的接口对资源进行控制和统计,但限制的具体方式则不尽相同。比如memory子系统,会在描述内存状态的“mm_struct”结构体中记录它所属的cgroup,当进程需要申请更多内存时,就会触发cgroup用量检测,用量超过cgroup规定的限额,则拒绝用户的内存申请,否则就给予相应内存并在cgroup的统计信息中记录。实际实现要比以上描述复杂得多,不仅需考虑内存的分配与回收,还需考虑不同类型的内存如cache (缓存)和swap(交换区内存拓展)等。
进程所需的内存超过它所属的cgroup最大限额以后,如果设置了OOM Control(内存超限控制),那么进程就会收到OOM信号并结束;否则进程就会被挂起,进人睡眠状态,直到cgroup中其他进程释放了足够的内存资源为止。Docker中默认是开启OOM Control的。其他子系统的实现与此类似,cgroups提供了多种资源限制的策略供用户选择。
cgnmp与任务之间的关联关系
实现上, cgroup与任务之间是多对多的关系,所以它们并不直接关联,而是通过一个中间结构把双向的关联信息记录起来。每个任务结构体task_struct中都包含了一个指针,可以査询到对应cgroup的情况,同时也可以査询到各个子系统的状态,这些子系统状态中也包含了找到任务的指针,不同类型的子系统按需定义本身的控制信息结构体,最终在自定义的结构体中把子系统状态指针包含进去,然后内核通过container_of(这个宏可以通过一个结构体的成员找到结构体自身)等宏定义来获取对应的结构体,关联到任务,以此达到资源限制目的。同时,为了让cgroups便于用户理解和使用,也为了用精简的内核代码为cgroup提供熟悉的权限和命名空间管理,内核开发者们按照Linux虚拟文件系统转换器(VirtualFilesystem Switch, VFS)接口实现了一套名为cgroup的文件系统,非常巧妙地用来表示cgroups的层级概念,把各个子系统的实现都封装到文件系统的各项操作中。
Docker在使用cgroup时的注意事项
在实际的使用过程中,Docker需要通过挂载cgroup文件系统新建一个层级结构,挂载时指定要绑定的子系统。把cgroup文件系统挂载上以后,就可以像操作文件一样对cgroups的层级进行浏览和操作管理(包括权限管理、 子文件管理等)。除了cgroup文件系统以外,内核没有为cgroups的访问和操作添加任何系统凋用。
如果新建的层级结构要绑定的子系统与目前已经存在的层级结构完全相同,那么新的挂载会重用原来已经存在的那一套(指向相同的css_set)。否则,如果要绑定的子系统已经被别的层级绑定,就会返回挂载失败的错误。如果一切顺利,挂载完成后层级就被激活并与相应子系统关联起来,可以开始使用了。
目前无法将一个新的子系统绑定到激活的层级上,或者从一个激活的层级中解除某个子系统的绑定。
当一个顶层的cgroup文件系统被卸载(umount)时,如果其中创建过深层次的后代cgroup目录,那么就算上层的cgroup被卸载了,层级也是激活状态,其后代cgroup中的配置依旧有效。只有递归式地卸载层级中的所有cgroup,那个层级才会被真正删除。
在创建的层级中创建文件夹,就类似于fork了一个后代cgroup,后代cgroup中默认继承原有cgroup中的配置属性,但是可以根据需求对配罝参数进行调整。这样就把一个大的cgroup系统分割成一个个嵌套的、可动态变化的“软分区”。
/sys/fs/cgroup/cpu/docker/<container-ID>下文件的作用
前面已经说过,以资源开头(比如cpu.shares)的文件都是用来限制这个cgroup下任务的可用的配置文件。一个cgroup创建完成,不管绑定了何种子系统,其目录下都会生成以下几个文件,用来描述cgroup的相应信息。同样,把相应信息写入这些配置文件就可以生效。
一个cgroup创建完成,不管绑定了何种子系统,其日录下都会生成以下几个文件,用来描述cgroup的相应信息。同样,把相应信息写入这些配置文件就可以生效,内容如下:
标签:睡眠 文件的 ps实现 unix 自动生成 release 等等 中间 tgid
原文地址:https://www.cnblogs.com/beiluowuzheng/p/10015177.html