一、cgroup学习

1.什么是Cgroup

Cgroups 是 control groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO 等等)的机制。最初由 google 的工程师提出,后来被整合进 Linux 内核。Cgroups 也是 LXC 为实现虚拟化所使用的资源管理手段,可以说没有 cgroups 就没有 LXC。

目前 cgroup 已经具有的子系统如下:

子系统        主要功能
---------------------------------------------
blkio        设置限制每个块设备的输入输出控制。
cpu            限制task group的CPU使用。
cpuacct        产生CGroup任务的CPU资源报告,CPU Accounting Controller。
cpuset        如果是多核CPU,这个子系统就会为CGroup任务分配单独的CPU和内存。
devices        允许或拒绝cgroup任务对设备的访问。
freezer        暂停或恢复CGroup任务。
hugetlb        允许限制CGroup 的HubeTLB使用
memory        设置每个CGroup的内存限制以及产生内存资源报告。
net_cls        标记每个网络包以供CGroup方便使用。
net_prio    提供接口以供动态调节程序的网络传输优先级。
perf_event    增加了对没CGroup的检测跟踪的能力,即可以检测属于某个特定的group的所有线程以及运行在特定CPU上的线程。

 

2.Cgroup可以做什么

1.限制进程组可以使用的资源数量,限制进程最大使用的内存等。
2.进程组的优先级控制,比如为某个进程组分配特定的cpu share。
3.记录进程组使用的资源数量,比如记录某个进程CPU的使用时间。
4.进程组隔离,比如通过namespace以达到隔离的目的。
5.进程组控制,比如可以将进程组挂起或恢复。

 

3.Cgroup的模型

(1) 进程模型
在说Cgroup的模型之前,先回顾下进程模型,在linux系统上,所有的进程都有一个共同的父进程,叫做init进程,这个进程在内核启动的时候开始执行,然后通过init进程启动其他的进程,这些进程都是init的子进程。因为所有的进程都有一个共同的父进程。那么linux的进程模型就是一个单继承层次的模型,或者称之为树状模型。除此之外每一个linux进程但是除了init进程,都继承了一些环境变量(例如PATH环境变量)。
(2) Cgroup模型
Cgroup其实和进程类似,Cgroup也是继承体系,并且子Cgroup继承其父Cgroup的某些属性,两者最基本的差别在于,进程是单继承体系。而Cgroup可以存在多个不同的继承体系(就是可以有多个单继承体系,每个单继承体系互不影响)。

 

4.Cgroup的一些概念

在Cgroup中有这样四个概念,可以说理解了这四个概念,那么对于如何使用Cgroup:

Subsystems: 称之为子系统,一个子系统就是一个资源控制器,比如cpu子系统就是控制cpu时间分配的一个控制器。
Hierarchies: 可以称之为层次体系,也可以称之为继承体系,指的是Control Groups是按照层次体系的关系进行组织的。
Control Groups: 一组按照某种标准划分的进程。进程可以从一个Control Groups迁移到另外一个Control Groups中,同时Control Groups中的进程也会受到这个组的资源限制。
Tasks: 在Cgroups中,Tasks就是系统的一个进程。

 

5.Subsystems

在Red_Hat_Enterprise_Linux-6系列的linux中,默认提供了如下子系统,功能如下:

blkio: 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等) 。
cpu: 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
cpuacct: 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
cpuset: 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
devices: 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
freezer: 这个子系统挂起或者恢复 cgroup 中的任务。
memory: 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
net_cls:这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
ns: 名称空间子系统。

Ubuntu中的目录是/sys/fs/cgroup, 但是高通的Android手机中这个目录下是空的,存在于/dev目录下,cgroup.rc指定了各个子系统的文件的路径。

/dev/cgroup_info # strings cgroup.rc
blkio:/dev/blkio
cgroup2:/dev/cg2_bpf /dev/cpuctl
cpuacct:/acct
cpuset:/dev/cpuset //system-background、top-app也在这里面。
freezer:/dev/freezer
memory:/dev/memcg
schedtune:/dev/stune

也可以# mount | grep cgroup 来查看都挂载在哪里了。

6.Tasks文件不仅可以读,还可以写,你将一个进程的进程号写入到某个cgroup目录下的tasks里面,你就将这个进程加入了相应的cgroup。

7.cgroup文件系统和VFS文件系统类似,cgroups的定义如下

struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .mount = cgroup_mount,
    .kill_sb = cgroup_kill_sb, //kill_sb释放超级块。
    .fs_flags = FS_USERNS_MOUNT,
};

8.Cgroup在kernel中的入口是:subsys_initcall(cgroup_sysfs_init); //msm-4.19\kernel\cgroup\cgroup.c

9.在cgroup文件系统中,对目录下的文件进行操作时,会调用struct kernfs_ops cgroup_kf_ops 结构体中指向的函数。比如:对文件进行读操作时,会调用 cgroup_file_read,在 cgroup_file_read 中,会根据需要调用该文件对应的 cftype 结构体定义的对应读函数。

cgroups通过实现cgroup文件系统来为用户提供管理cgroup的工具,而cgroup文件系统是基于Linux VFS实现的。相应地,cgroups为控制文件定义了相应的数据结构cftype,对其操作由cgroup文件系统定义的通过操作捕获,再调用cftype定义的具体实现。

cgroup中的每一个文件的后台动作都是 使用以空结尾的struct cftype数组来定义对cgroup文件进行操作时的后台动作,如 static struct cftype swap_files[] 通过模块初始化时执行cgroup_add_legacy_cftypes()添加到cgroup系统中,如 cgroup_add_legacy_cftypes(&memory_cgrp_subsys, memsw_cgroup_files)

10.Cgroup是将任意进程进行分组化管理的Linux内核功能。cgroup本身提供将进程进行分组化管理的功能和接口的基础结构。而后的Android操作系统也就凭借着这个技术,为每个应用程序分配不同的cgroup,将每个程序进行隔离,达到了一个应用程序不会影响其他应用程序环境的目的。

11.概念

task:一个进程。
control group:控制族群,按照某种标准划分的进程组。
hierarchy:层级,control group可以形成树形的结构,有父节点,子节点,每个节点都是一个control group,子节点继承父节点的特定属性。
subsystem:子系统。子系统就是资源控制器,每种子系统就是一个资源的分配器,比如cpu子系统是控制cpu时间分配的。

可以使用lssubsys -al来列出系统支持多少种子系统,和使用ls /sys/fs/cgroup/(ubuntu)来显示已经挂载的子系统.

12.# mount | grep cgroup可以看挂载目录,到对应的挂载目录下,创建一个文件夹,就创建了一个control group了。
实验1:

/dev/freezer # mkdir mytest
/dev/freezer # ls mytest/
cgroup.clone_children  cgroup.procs  freezer.parent_freezing  freezer.self_freezing  freezer.state  notify_on_release  tasks  yourtest
/dev/freezer/mytest/ # echo $$ > tasks //当前shell进程就被设置进去了,之后更改属性文件的值可以对其进行设置。

实验2:

/dev/cpuset # mkdir mytest
/dev/cpuset/mytest # echo "3" > cpus //使用CPU3
/dev/cpuset/mytest # ps -A | grep system_server
1772
/dev/cpuset/mytest # echo 1772 > tasks

上面操作就将system_server线程绑定到CPU3上了。

实验3:

/dev/memcg # mkdir my_mem_ctl
/dev/memcg/my_mem_ctl # ls
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.move_charge_at_immigrate  memory.swappiness
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.memsw.failcnt             memory.oom_control               memory.usage_in_bytes
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.limit_in_bytes      memory.pressure_level            memory.use_hierarchy
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.memsw.max_usage_in_bytes  memory.soft_limit_in_bytes       notify_on_release
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.memsw.usage_in_bytes      memory.stat                      tasks
/dev/memcg/my_mem_ctl # echo $$ > tasks
/dev/memcg/my_mem_ctl # echo 10M > memory.limit_in_bytes
/dev/memcg/my_mem_ctl # cat memory.limit_in_bytes
10485760

上面是限制当前shell进程最大使用10M内存。

相关文件解释:

memory.limit_in_bytes  显示当前内存(进程内存+页面内存)的使用量
memory.memsw.usage_in_bytes  显示当前内存(进程内存+页面内存)+交换区的使用量
memory.limit_in_bytes  设置、显示内存(进程内存+页面内存)使用量的限制值
memory.memsw.limit_in_bytes  设置、显示内存(进程内存+页面内存)+交换区使用量的限制值
memory.failcnt  显示当前内存(进程内存+页面内存)达到限制值的次数
memory.memsw.failcnt 显示当前内存(进程内存+页面内存)+交换区达到限制值的次数
memory.max_usage_in_bytes 显示记录的内存(进程内存+页面内存)使用量的最大值
memory.memsw.max_usage_in_bytes 显示记录的内存(进程内存+页面内存)+交换区使用量的最大值
memory.stat 输出统计信息
memory.force_empty 强制释放分配给分组的内存
memory.use_hierarchy  设置、显示层次结构的使用
memory.swappiness  设置、显示针对分组的swappiness(相当于sysctl的vm.swappiness)

 

二、cgroup使用

1. 需要区分cgroup的公共配置文件和特有配置文件(基于kernel-5.10)

cgroup1_base_files["tasks"].write 回调 group-v1.c
    cgroup1_tasks_write
cgroup1_base_files["cgroup.procs"].write 回调
    cgroup1_procs_write
        __cgroup1_procs_write
            trace_android_vh_cgroup_set_task //允许在此加HOOK

比如cpuset特有文件使用的文件在 legacy_files[] 中表示,没有"tasks"、"cgroup.procs"文件。也就是说这个HOOK是所有cgroup共用的,目前已经存在的cgroup有freezer、memory、cpuset、cpu、blkiofreezer、memory、cpuset、cpu、blkio ...,也就说每次前后台切换此HOOK会被调用多次!