cgroup的实现相对namespace要复杂一些,网上也有一些代码分析,大家对代码分析的兴趣估计也不大,所以这里就不放代码分析了,主要对其使用进行说明,么么哒。
Cgroup是linux内核集成的资源控制机制,cgroup与用户态交互通过特殊文件系统cgroup文件系统,进行交互,所有设置或者查看cgroup的动作都可以通过cgroup文件系统下的文件完成,因此除了编译内核的时候需要打开特定的选项之外,还需在系统启动之后挂载cgroup文件系统方能使用cgroup的控制功能。几个重要概念:
Ø cgroup: 一组进程的行为控制。要对某个进程进行资源限制,就将进程添加到cgroup中,一个cgroup可以有多个进程。
Ø hierarchy: 一组cgroup的集合,可以理解成cgroup的根,cgroup是hierarchy的结点。一个hierarchy的资源限制为1,代表拥有系统所有资源,cgroup资源限制小于1,分配系统资源。
Ø subsystem: cgroup可以进行多种资源限制,某种资源限制就是一个subsystem,所有的subsystem构成了资源限制的所有资源种类。
三者之间的主要关系:
Ø 创建新hierarchy时,系统中的所有任务都是那个hierarchy的根cgroup的初始成员。
Ø 一个subsystem最多只能附加到一个hierarchy。
Ø 一个hierarchy可以附加多个subsystem。
Ø 一个进程可以是多个cgroup的成员,但是这些cgroup必须在不同的hierarchy下。
Ø 创建子进程时,子进程自动成为父进程所在 cgroup 的成员。可根据需要将该子进程移动到不同的 cgroup 中。
Cgroup包含了11个子系统,分别是:
Ø Blkio:控制块设备的访问,比如带宽等。
Ø cpu :控制进程占用cpu的多少。
Ø Cpuacct: 记录cgroup 中进程使用的 CPU 情况。
Ø Cpuset:为 cgroup 中的进程分配CPU和内存节点。
Ø Devices:控制进程对设备的访问。
Ø Freezer:挂起或者恢复 cgroup 中的进程。
Ø Memory:设定 cgroup 中进程的内存限制,统计使用的内存资源。
Ø net_cls:使用等级识别符(classid)标记网络数据包,使得Linux 流量控制程序(tc)识别具体 cgroup 中的数据包。
Ø Ns:命名空间子系统,默认创建cgroup的时候会创建命名空间,新的内核不支持。
Ø Debug:用于调试。
Ø Perf:按cgroup进行性能统计。
Cgroup与用户态的交互通过cgroup文件系统完成,cgroup的每一控制箱在cgroup文件系统下都会对应一个文件,通过对文件的读写,来实现资源的控制。cgroup文件系统的定义:
static struct file_system_type cgroup_fs_type = {
.name = "cgroup",
.get_sb = cgroup_get_sb,
.kill_sb = cgroup_kill_sb,
};
定义了两个函数指针,定义了一个文件系统必须实现了的两个操作get_sb,kill_sb,即获得超级块和释放超级块。这两个操作会在使用mount系统调用挂载cgroup文件系统时使用。
cgroup 超级块的定义:
static const struct super_operations cgroup_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
.show_options = cgroup_show_options,
.remount_fs = cgroup_remount,
};
cgroup 索引块定义:
static const struct inode_operations cgroup_dir_inode_operations = {
.lookup = simple_lookup,
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.rename = cgroup_rename,
};
在cgroup文件系统中,使用mkdir创建cgroup或者用rmdir删除cgroup时,就会调用相应的函数指针指向的函数。
cgroup 文件操作定义:
static const struct file_operations cgroup_file_operations = {
.read = cgroup_file_read,
.write = cgroup_file_write,
.llseek = generic_file_llseek,
.open = cgroup_file_open,
.release = cgroup_file_release,
};
对cgroup目录下的控制文件进行操作时,会调用该操作函数。而这些函数将会根据访问的不同的文件调用其对应的操作函数。cgroup文件系统为cgroups控制文件定义了一个cftype的结构体。cftype的定义:
struct cftype {
char name[MAX_CFTYPE_NAME];
int private; /*
mode_t mode;
size_t max_write_len;
int (*open)(struct inode *inode, struct file *file);
ssize_t (*read)(struct cgroup *cgrp, struct cftype *cft,
struct file *file,
char __user *buf, size_t nbytes, loff_t *ppos);
u64 (*read_u64)(struct cgroup *cgrp, struct cftype *cft);
s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
int (*read_map)(struct cgroup *cont, struct cftype *cft,
struct cgroup_map_cb *cb);
int (*read_seq_string)(struct cgroup *cont, struct cftype *cft,
struct seq_file *m);
ssize_t (*write)(struct cgroup *cgrp, struct cftype *cft,
struct file *file,
const char __user *buf, size_t nbytes, loff_t *ppos);
int (*write_u64)(struct cgroup *cgrp, struct cftype *cft, u64 val);
int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
int (*write_string)(struct cgroup *cgrp, struct cftype *cft,
const char *buffer);
int (*trigger)(struct cgroup *cgrp, unsigned int event);
int (*release)(struct inode *inode, struct file *file);
int (*register_event)(struct cgroup *cgrp, struct cftype *cft,
struct eventfd_ctx *eventfd, const char *args);/*
void (*unregister_event)(struct cgroup *cgrp, struct cftype *cft,
struct eventfd_ctx *eventfd);
};
cftype中除了定义文件的名字和相关权限标记外,还定义了对文件进行操作的函数指针。不同的文件可以有不同的操作,因为不同的文件涉及到的是内核不同的子系统不同的功能。
1.1.1. Cpu
cpuset 子系统为 cgroup 分配独立 CPU 和内存节点。
typedef enum {
CS_CPU_EXCLUSIVE,
CS_MEM_EXCLUSIVE,
CS_MEM_HARDWALL,
CS_MEMORY_MIGRATE, /*进行内存迁移*/
CS_SCHED_LOAD_BALANCE, /*该set下的cpu进行负载均衡*/
CS_SPREAD_PAGE, /*和后面的两个标志共同设置平均使用允许的内存节点*/
CS_SPREAD_SLAB,
} cpuset_flagbits_t;
如果系统支持热插拔,那么热插拔之后如果cpuset的cpu集合或者内存节点集合为空,该cpuset关联的进程将移动到上层非空cpuset中。
同一层的task_group和进程被当成同样的调度实体来选择,当被选到的是task_group时,则对task_group的孩子节点重复这个过程,直到选到一个运行的进程。当设置一个cgroup的shares值时,该cgroup当作一个整体和剩下的进程或其他cgroup分享cpu时间。比如,在根cgroup下建立cgroup A,将其shares值设1024,再建立cgroup B,将其shares设为2048,再将一些进程分别加入到这两个cgroup中,则长期调度的结果应该是A:B:C=1:2:1(C是系统中未加入到A或B的进程)。
Cpu子系统则为cgroup分配时间比例,cpuacct则统计cgroup中进程使用的时间。
项目 |
功能 |
说明 |
||||||||||||||
Cpuset.cpus |
限制进程能够使用的cpu节点 |
|
||||||||||||||
Cpuset.mems |
限制进程能够使用的内存节点 |
|
||||||||||||||
cpu.shares |
假设cgroup A的tasks的cpu.shares值为1,cgroup B的tasks的cpu.shares值为2,则cgroup B的进程占用的cpu时间是cgroup A上进程的2倍 |
|
||||||||||||||
cpu.rt_runtime_us |
以微秒为单位指定在某个时间段中 cgroup 中的实时任务对 CPU 资源的最长连续访问时间 |
|
||||||||||||||
cpu.rt_period_us |
以微秒为单位指定在某个时间段中 cgroup 对 CPU 资源访问重新分配的频率 |
|
||||||||||||||
cpuset.cpu_exclusive |
是否共享该cgroup指定的cpu,设置为1一个cpu只能出现在一个cgroup里面 |
|
||||||||||||||
cpuset.mem_exclusive |
是否共享该cgroup指定的memory node |
|
||||||||||||||
cpuset.sched_load_balance |
是否对cgroup的所有CPU做负载平衡 |
|
||||||||||||||
cpuset.sched_relax_domain_level |
包含 -1 到小正数间的整数,它代表内核应尝试平衡负载的 CPU 宽度范围。
|
|
||||||||||||||
cpuset.memory_migrate |
cpuset.mems 中的值更改时是否应该将内存中的页迁移到新节点的标签(0 或者 1) |
|
||||||||||||||
cpuset.mem_hardwall |
是否应将内存页面的内核分配限制在为这个 cpuset 指定的内存节点 |
|
||||||||||||||
cpuset.memory_pressure |
cgroup产生的平均内存压力 |
|
||||||||||||||
cpuset.memory_pressure_enabled |
是否计算cgroup中的内存压力 |
|
||||||||||||||
cpuset.memory_spread_page |
是否将文件系统缓存平均分配到cgroup的内存节点上 |
|
||||||||||||||
cpuset.memory_spread_slab |
是否将用于文件输入输出缓冲平均分配到cgroup的内存节点上 |
|
新的实现增加了对cpu上限的设置,其值设置方法和命名都和cpu.rt_runtime_us和cpu.rt_period_us差不多,这个文档早的时候写的,这里偷个懒就不补充了。
1.1.2. Mem
memory 子系统可以设定 cgroup 中进程使用的内存限制,并自动统计进程使用的内存资源。memory子系统通过linux的resource counter机制实现。
resource counter是内核为子系统提供的一种资源管理机制。包括了用于记录资源的数据结构和相关函数。Resource counter定义了一个res_counter的结构体来管理特定资源,定义如下:
struct res_counter {
unsigned long long usage;
unsigned long long max_usage;
unsigned long long limit;
unsigned long long soft_limit;
unsigned long long failcnt;/*
spinlock_t lock;
struct res_counter *parent;
};
Usage用于记录当前已使用的资源,max_usage用于记录使用过的最大资源量,limit用于设置资源的使用上限,进程组不能使用超过这个限制的资源,soft_limit用于设定一个软上限,进程组使用的资源可以超过这个限制,failcnt用于记录资源分配失败的次数,管理可以根据这个记录,调整上限值。Parent指向父节点,这个变量用于处理层次性的资源管理。
memory子系统定义了一个叫mem_cgroup的结构体来管理cgroup相关的内存使用信息,定义如下:
struct mem_cgroup {
struct cgroup_subsys_state css;
struct res_counter res;
struct res_counter memsw;
struct mem_cgroup_lru_info info;
spinlock_t reclaim_param_lock;
int prev_priority;
int last_scanned_child;
bool use_hierarchy;
atomic_t oom_lock;
atomic_t refcnt;
unsigned intswappiness;
int oom_kill_disable;
bool memsw_is_minimum;
struct mutex thresholds_lock;
struct mem_cgroup_thresholds thresholds;
struct mem_cgroup_thresholds memsw_thresholds;
struct list_head oom_notify;
unsigned long move_charge_at_immigrate;
struct mem_cgroup_stat_cpu *stat;
};
mem_cgroup中包含了两个res_counter成员,分别用于管理memory资源和memory+swap资源,如果memsw_is_minimum为true,则res.limit=memsw.limit,即当进程组使用的内存超过memory的限制时,不能通过swap来缓解。
Cgroup通过在分配内存,换入换出的时候进行计数来管理cgroup中内存。内核线程如果属于某个cgroup,其使用内存同样受到cgroup控制。页高速缓存的内存也在cgroup控制之内。
项目 |
功能 |
说明 |
Memory.stat |
当前cgroup中的内存实时状况 |
|
Memory.usage_in_bytes |
当前cgroup中所有进程使用的物理内存之和 |
单位bytes |
Memory.memsw.usage_in_bytes |
当前cgroup中所有进程使用的物理内存和swap之和 |
|
Memory.max.usage_in_bytes |
当前cgroup使用物理内存最大值 |
|
Memory.memsw.max_usage_in_bytes |
当前cgroup使用物理内存和swap之和的最大值 |
|
Memory.limit_in_bytes |
设置当前cgroup的物理内存的上限值(包括页高速缓存) |
|
Memory.memsw.limit_in_bytes |
对物理内存和swap之和的限制 |
|
Memory.failcnt |
物理内存值达到所设置上限值的次数 |
|
Memory.memsw.failcnt |
物理内存和swap之和达到所设置上限值的次数 |
|
Memory.force_empty |
释放cgroup使用的所有内存 |
只有在cgroup中没有进程时才使用 |
Memory.swappines |
交换值,用于计算交换倾向经验值 |
|
Memory.use_hierarchy |
指定内存回收是否按一个hierarchy来进行,如果设置为1,内存子系统从其中一个超过限额的子cgroup中回收内存,如果设置为0,则只是从当前cgroup中的进程回收 |
|
Memory.oom_control |
内存超过限制并且回收失败后事后通过oom_killer机制处理 |
|
Memory.soft_limit_in_bytes |
内存软限制 |
|
Memory.move_charge_at_immigrate |
页迁移计数控制 |
|
Memory.numa.stat |
每个节点的内存控制信息 |
|
Memory.tcp.limit_in_bytes |
当前cgroup能使用的tcp内存总大小 |
|
Memory.tcp.usage_in_bytes |
当前cgroup所有进程使用的tcp内存量 |
|
Memory_kmem.tcp.filcnt |
当前cgroup中所有进程tcp缓存之和超过限制的次数 |
|
Memory.keme.tcp.max_usage_in_bytes |
当前cgroup中tcp使用缓存的峰值 |
|
1.1.3. Device
devices只控制块设备和字符设备的访问。有inode的设备文件通过在open中对比权限来控制cgroup中的进程能够访问的devices。inode不存在的设备就必须通过mknod中对比权限来控制进程对设备的访问。
项目 |
功能 |
说明 |
Devices.allow |
允许cgroup中进程访问的设备名 |
|
Devices.deny |
禁止cgroup中进程访问的设备名 |
|
Devices.list |
Cgroup中进程访问的设备 |
|
1.1.4. Net
网络的资源控制通过kernel的tc实现。
项目 |
功能 |
说明 |
Net_cls.classid |
流量控制中cgroup分类器的id号 |
|
Net_prio.ifpriomap |
设置某个设备上的优先级 |
|
Net_prio.prioidx |
全网唯一的一个标识,只读 |
|
1.1.5. Blkio
cgroup的blkio子系统管理块设备访问,有2种限制模式:
Ø throttle,限制每个进程能使用的iops或者bps。
Ø weight,限制每个进程能使用的iops的比例,必须通过CFQ调度器来实现。可以通过修改/sys/block/sda/queue/scheduler文件完成。io带宽相对控制只是针对受控对象都很繁忙的时候控制其上限速度,在某个cgroup比较闲的时候,不再满足这个规律,其他cgroup可能消耗掉更多的带宽。
要使用blkio的weight限制需要注意:
Ø 必须direct io, 如果buffered io因为最终写IO的进程不是发起IO的进程,结果会有很大的偏差。
Ø 调度器必须是CFQ。
在CFQ调度器中,所有的异步请求都属于根cgroup,不受子cgroup的限制,因此在子cgroup中对异步io进行资源限制没有意义。
项目 |
功能 |
说明 |
blkio.weight |
Io访问能力的相对权重 |
|
blkio.weight_device |
特定设备的io带宽的相对权重 |
|
blkio.throttle.read_bps_device |
特定设备的读bps上限值 |
|
blkio.throttle.write_bps_device |
特定设备的写bps上限值 |
|
blkio.throttle.read_iops_device |
特定设备的读iops上限值 |
|
blkio.throttle.write_iops_device |
特定设备的写iops上限值 |
|
blkio.io_merged |
请求合并到I/O操作请求的次数 |
|
blkio.io_queued |
Cgroup I/O操作排队的请求次数 |
|
blkio.io_service_bytes |
转换到具体设备或者由具体设备中转换出的字节数 |
|
blkio.io_serviced |
在具体设备中执行的I/O操作数 |
|
blkio.io_service_time |
在具体设备中的I/O操作请求发送和请求完成之间的时间 |
|
blkio.io_wait_time |
所有io操作在队列中等待的时间总和 |
|
blkio.reset_stats |
重新设定在其它伪文件中记录的统计数据 |
|
blkio.sectors |
转换到具体设备或者由具体设备转换出的扇区数 |
|
blkio.throttle.io_service_bytes |
转换到具体设备或者由具体设备中转换出的字节数 |
|
blkio.throttle.io_serviced |
在具体设备中执行的I/O操作数 |
|
blkio.time |
对具体设备的I/O访问时间 |
|
blkio.avg_queue_size |
I/O操作的平均队列大小 |
|
blkio.group_wait_time |
每个队列等待获取时间片的等待时间总和 |
|
blkio.empty_time |
不包括等待时间在内的处理时间总和 |
|
blkio.idle_time |
Io调度程序等待更好的请求时空转的时间 |
|
blkio.dequeue |
I/O操作请求被具体设备从队列中移除的次数 |
|