cgroup子系统
cgroup是现代andriod的基础,最初提出也是andriod内部的人员提出的,后来被实现在linux内核内。通过cgroup可以将定额的系统资源(如CPU、内存等)分配给特定的一组进程。默认情况下编译内核时打开cgroup的系统中所有进程位于同一个cgroup,就是根,这个cgroup享有所有的系统资源。
你可以通过cgroup文件系统建立一个新的cgroup,然后配置这个新的cgroup,配置的内容包括为其分配进程,分配资源等。这个创建和分配的所有的过程都是通过cgroup文件系统通过shell echo写进文件完成的。
cgroup的如今的应用领域不断扩大,各个发行版纷纷开始默认打开cgroup进行进程组的调度和资源规划。并且用户端的LXC虚拟化方案也是使用cgroup分配资源实现的用户端模拟虚拟运行环境。也正是这个最初不被看好的LXC(比起xen、kvm),由于Docker技术的出现,而开始被广泛使用在云计算中。
1. 在用户端使用cgroup
详细的在kernelhack 里有。
如果你的内核中编译时打开了cgroup,在用户端要使用cgroup为应用程序分组分配资源,你还可以安装cgroup的用户端控制程序:libcgroup,这个程序是个服务(centos),你可以作出cgroup规划,启动的时候会根据规划配置脚本为你建好cgroup架构,还提供了几个方便的操作cgroup的脚本。当然也可以什么都不安装,因为cgroup可以直接原生操作cgroup文件系统完成使用。通过mount -t cgroup -o debug cgroup /cgroup等命令进行操作,此时就会在/cgroup下生成很多默认的文件,如果在这个目录下建立文件,就意味着你建立了子cgroup,你ls一下你建的文件就会发现,里面的内容与根目录下自动生成的大致相同。cgroup文件系统通过这种方式完成树形的层次组织。
建立一个cgroup并不能真正的完成工作,你需要为其分配可用的资源和将不同的进程放进去。当建立第一个cgroup时,系统会把所有的进程都放进去。也就是你在/cgroup/tasks文件cat,会发现所有的进程(所有相关的配置和使用方法并不是固定的,linux在变化)。如果你在下级的cgroup,例如/cgroup/test的task文件中将一个进程的PID echo到文件,就会发现,这个进程并没有从上层cgroup的task列表中消失而是同时出现在了下层test cgroup的task文件中,这就表示该进程现在归test cgroup控制。但由于test cgroup是下层的cgroup,所以进程依然存在于上层的cgroup,而同一层的cgroup之间却不能含有相同的进程。
一个属于某个cgroup的进程的子进程都会自动属于同一个cgroup。可以看出cgroup是分层次的,这个层次叫做层级。而每个cgroup内部都可以针对cpu、网络、磁盘等资源进行配置,这些可以配置的内容叫做子系统。
我们知道实时进程对CPU的使用很可能是独占的,内核为我们提供了不从调度算法本身,而是通过使用cgroup限制实时进程使用CPU的方法:RT Scheduling和RT Throttling。这个为何不在调度本身设计?因为实时的概念只是尽快的抢占CPU,而不涉及什么时候释放(直到更实时的)。实时的策略是对的,但是如果出现了bug,将有很大概率导致系统卡死。在cgroup文件系统中,使用cpu.rt_period_us和cput.rt_runtime_us可以限制本组的实时进程的CPU占用。
还可以使用cgroup将用户端的进程分成一个个组,然后以组为单位设置调度方法。例如使用Fair Group Scheduling(该机制利用内核的CFS调度机制),实现各个分组之间的公平调度算法。
还可以使用cgroup的cpuset功能为每个进程组规定使用哪些cpu,以及每个cpu的亲和度。还可以针对特定的程序组对他们的内存进行限制、统计和强制释放。还可以针对IO操作,设置分组的IO优先级。将后台执行磁盘操作的程序放到较低优先级的分组,前台需要高响应速度的放在较高优先级分组。如此既可以有效的利用IO吞吐量,又可以不影响前台软件的使用性能。(echo 100 > /cgroup/low/blkio.weight)
2. cgroup组件架构
上面的使用只是简单的原理性的介绍,如果要真正的使用需要详细的查阅文档。我们分析cgroup在架构层次上的组织情况。
Cgroup本身是分层级的,一个根层下面像一棵树一样可以分很多层。每一层的cgroup文件系统目录下都有该层对应的资源配置文件。这些可以配置的资源都是cgroup子系统。这些子系统包括:
- blkio -- 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
- cpu -- 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
- cpuacct -- 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
- cpuset -- 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
- devices -- 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
- freezer -- 这个子系统挂起或者恢复cgroup 中的任务。
- memory -- 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
- net_cls -- 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
- net_prio -- 这个子系统提供了一种动态控制每个网卡流量优先级的功能
- ns -- 名称空间子系统
由于系统资源只有一份,所以同一个子系统在同一个的cgroup只能出现一次。
2.1 CPU子系统
cpu子系统用于控制cgroup中所有进程可以使用的cpu时间片。附加了cpu子系统的hierarchy下面建立的cgroup的目录下都有一个cpu.shares的文件,对其写入整数值可以控制该cgroup获得的时间片。例如:在两个 cgroup 中都将 cpu.shares 设定为 1 的任务将有相同的 CPU 时间,但在 cgroup 中将 cpu.shares 设定为 2 的任务可使用的 CPU 时间是在 cgroup 中将 cpu.shares 设定为 1 的任务可使用的 CPU 时间的两倍(同一层级)
。
cpu子系统是通过Linux CFS调度器实现的。按照作者Ingo Molnar的说法:"CFS百分之八十的工作可以用一句话概括:CFS在真实的硬件上模拟了完全理想的多任务处理器"。可以说CFS调度器虽然实现的是绝对公平,但实际上却是为不公平的使用cpu而准备的。
cpu子系统调度CPU的访问控制,这里有2种调度模式:
2.1.1 CFS(Completely Fair Scheduler)
基于linux的CFS及各group的cpu的权重,在cgroup的task间分配CPU在CFS中,如果CPU空闲较多,那么group的task可能可以获得额外的CPU资源。
cpu.cfs_period_us:该group的cpu分配周期(微妙),如果想让该group在1s内能又0.5秒的cpu使用时间设置cpu.cfs_period_us为1000000,设置cpu.cfs_quota_us为500000;如果想让该group使用2个CPU,设置cpu.cfs_quota_us为2000000,设置cpu.cfs_period_us为1000000
cpu.cfs_quota_us:该指为-1时表示不限制CPU使用
cpu.stat:统计CPU的使用状态
nr_periods:CPU的使用周期(有多少个cpu.cfs_period_us)
nr_throttled :超出CPU限制的次数
throttled_time:被KILL的任务的cpu使用时间
cpu.shares:一个整数值(大于等于2),指定了使用CPU的权重(相对于系统上的所有CPU),2048的group可使用的cpu资源位1024的2倍。
2.1.2 RTS(Real-Time scheduler)
直接通过tasks的cpu使用时间来调度CPU。
cpu.rt_period_us:(仅适用于实时任务调度)该参数指定了一段时间内分给该group的CPU使用时间(微秒)。如果该值设置为200000,cpu.rt_period_us设置为1000000,那么每秒该group有0.2秒的cpu使用时间
cpu.rt_runtime_us:(仅适用于实时任务调度)指定了该group可以持续使用CPU的最长时间。如果想让该group在1秒内有0.2秒的cpu使用时间,设置cpu.rt_runtime_us为200000,设置cpu.rt_period_us为1000000;如果想让该group有2个cpu的资源设置改制为2000000,设置cpu.rt_period_us为1000000。
2.2 cpuacct
这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
cpuacct.usage:该group及其子group的cpu总使用时间(纳秒),内容为0时,充值cpuacct的数据
cpuacct.stat:该group及其子group的cpu的用户和和内核态的分别使用时间 单位为$USER_HZ
cpuacct.usage_percpu:该group及其子group的cpu分别使用时间(纳秒)
2.3 cpuset
这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
cpuset.cpus:绑定该group的cpu节点,如绑定该进程可以使用4,5,6,17,18 5个cpu,格式如下:4-6,17,18
cpu.mems:绑定该group的内存节点,格式如上
cpuset.memory_migrate:布尔值,默认0,指定当内存节点变化时,原内存页面是否迁移到新的内存节点上。
cpuset.cpu_exclusive:布尔值,默认0,指定该group的子group是否可以共享该group的cpu
cpuset.mem_exclusive:布尔值,默认0,指定该group的子group是否可以共享该group的内存
cpuset.mem_hardwall:布尔值,默认0,内核为该group分配的进程是否应该仅仅在指定的内存节点上。
cpuset.memory_pressure:统计了该group内存压力的平均值(仅在cpuset.memory_pressure_enabled启用是有效)该值为:每秒该group试图尝试回收内存的次数*1000
cpuset.memory_pressure_enabled:布尔值,默认0
cpuset.memory_spread_page:布尔值,默认0,是否均衡使用该group的内存节点
cpuset.memory_spread_slab:布尔值,默认0,是否均衡使用该group的cpu节点
cpuset.sched_load_balance:布尔值,默认1,是否平均分配该group的cpu负载到该group的节点上,注意,如果父group启用了这项,那么当前项就不在有效。
cpuset.sched_relax_domain_level:一个-1至5;代表系统试图进行负载均衡的类型(仅在 cpuset.sched_load_balance启用时有效)
l -1:使用系统默认值
l 0:定期负载均衡
l 1:实时在同一内核线程间进行负载均衡
l 2:实时在同一内核包间负载均衡
l 3:实时在同一cpu节点或者刀片上负载均衡
l 4:实时在多个CPU(NUMA)节点负载均衡
l 5:实时在所有cpu间负载均衡
memory memory 子系统可以设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。memory子系统是通过linux的resource counter机制实现的。
memory.stat:统计内存使用状态
memory.usage_in_bytes:当前cgroup的内存使用情况
memory.memsw.usage_in_bytes:当前group的内存+swap的内存使用情况
memory.limit_in_bytes:设定最大的内存使用量,可以加单位(k/K,m/M,g/G)
memory.memsw.limit_in_bytes:设定内存和swap总和的最大使用量,其他同上。注意:设置memory.memsw.limit_in_bytes前要设置memory.limit_in_bytes
memory.failcnt:统计达到内存限制(memory.limit_in_bytes)的次数
memory.memsw.failcnt:统计达到内存+swap限制(memory.memsw.limit_in_bytes)的次数
memory.force_empty:当设置为0时,情况该group的所有内存页;该选项只有在当前group没有tasks才可以使用
memory.swappiness:针对该group的交换分区的优先级,类似vm.swappiness。注意:1: 不能调整root的group的swappiness。2: 不能调整有子group的swappiness
memory.use_hierarchy:布尔值,默认0;指定是否在整个group层限制内存
memory.oom_control:布尔值;默认0;指定是否杀掉超出内存使用限制的进程。0:kill哪些超出内存使用范围的进程,1:暂停哪些超出内存使用范围的进程,指定有多余的内存。该项也指出了是否有进程因为内存使用过多被暂停,under_oom为1
2.4 blkio
该子系统提供了2中方式来控制IO
1:基于权重
每个group都可以设置一个数值,根据数值的不通,系统分配相应的IO。
blkio.weight:一个100-1000的数值。 echo 1000 > blkio.weight
blkio.weight_device:一个100-1000的数值,指定设备的IO权重。 echo "8:0 500" > blkio.weight_device
以上2个值同一个group只能存在一个
2:基于速度:
每个group都有一个最大的速度,该group的进程IO不能大于这个速度
主要控制项:
blkio.throttle.read_bps_device:指定该设备上的最大读速度(bytes/s)
echo "8:0 10485760" > blkio.throttle.read_bps_device
blkio.throttle.read_iops_device:制定该设备上的最大读IO(IO read/s)
echo "8:0 10" > blkio.throottle.read_iops_device
下面2个跟上面2个类似:
blkio.throttle.write_bps_device
blkio.throttle.write_iops_device
主要记录项:
blkio.throttle.io_serviced:记录设备IO操作总数:
8:0 Read 172227
8:0 Write 120543
8:0 Sync 261857
8:0 Async 30913
8:0 Total 292770
blkio.throttle.io_service_bytes:记录设备读取总数:
8:0 Read 3339509760
8:0 Write 7702714368
8:0 Sync 4183794688
8:0 Async 6858429440
8:0 Total 11042224128
其他配置项:
blkio.reset_stats:对当前文件写入一个整数可重置当前所有数据
blkio.time:指定设备的cgroup控制的IO访问时间(ms)
blkio.sectors:指定设备的扇区操作数
blkio.io_service_time:制定设备的IO工作时间(ns)
blkio.io_wait_time:Cgroup等待IO的时间
blkio.io_merged:被合并的IO请求
blkio.io_queued:被cgroup放到队列的IO请求
注意如果不启用增强版的IO隔离,cgroup的IO隔离仅仅对顺序IO有效,启用后对逻辑IO也有效,默认启用
echo 1 > /sys/block//queue/iosched/group_isolatio
devices
devices子系统是通过提供device whilelist 来实现的,devices子系统通过在内核对设备访问的时候加入额外的检查来实现;而devices子系统本身只需要管理好可以访问的设备列表就行了。
devices.allow:指定该group各设备的访问权限,有4个字段
type:
a:所有设备
b:块设备
c:字符型
major,minor:主副设备号
access:
r:读
w:写
m:创建新设备
devices.allow:指定该group各设备的不可访问权限,语法同上
devices.list:统计该group所有权限
2.5 freezer
该文件可能读出的值有三种,其中两种就是前面已提到的FROZEN和THAWED,分别代表进程已挂起和已恢复(正常运行),还有一种可能的值为FREEZING,显示该值表示该cgroup中有些进程现在不能被frozen。当这些不能被frozen的进程从该cgroup中消失的时候,FREEZING会变成FROZEN,或者手动将FROZEN或THAWED写入一次。
freezer.state:(仅对非root的group有效)
FROZEN:挂起进程
FREEZING:显示该值表示有些进程不能被frozen;当不能被挂起的进程从cgroup消失时,变成FROZEN,或者受到改为FROZEN或THAWED
THAWED :恢复进程
2.6 ns
ns子系统是一个比较特殊的子系统。ns子系统没有自己的控制文件,而且没有属于自己的状态信息。
ns子系统实际上是提供了一种同命名空间的进程聚类的机制。具有相同命名空间的进程会在相同cgroup中。
2.6 net_cls
net_cls.classid :控制该control的类ID(基于tc)
格式如下:0xAAAABBBB A代码主类编号,B代码副类编号,如果没有就写0,并且0是可以省略的
0x10001=0x0000100001=0x1:1
2.7 net_prio
net_prio.prioidx:只读文件,包含内核中用来表示该group的唯一整数值
net_prio.ifpriomap:指定各个网卡的该group的优先级格式: eth0 2
注意,1:子group默认使用父group的优先级
2:值越大优先级越低