LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Android/Linux下CGroup框架分析及其使用

1 cgroup介绍

CGroup是control group的简称,它为Linux kernel提供一种任务聚集和划分的机制,可以限制、记录、隔离进程组(process groups)所使用的资源(cpu、memory、I/O等)。CGroup也是LXC为实现虚拟化所使用的资源管理手段。CGroup本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。这些具体的资源管理功能称为CGroup子系统。

CGroup子系统包含如下:

子系统

主要功能

blkio

设置限制每个块设备的输入输出控制。

cpu

使用调度程序为CGroup任务提供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

增加了对没group的检测跟踪的能力,即可以检测属于某个特定的group的所有线程以及运行在特定CPU上的线程。

1.1 subsystem、task、hierarchy介绍及其关系

任务task,在CGroup中,任务就是系统的一个进程。

CGroup就是一组按照某种标准进行划分的进程。CGroup中的资源控制都是以control group为单位实现。一个进程可以加入到某个控制族群,也可以一个进程组迁移到另一个控制族群。

subusystem即子系统,是CGroup中可添加删除的模块,是CGroup下对资源进行的一种封装,比如内存资源、CPU资源、网络资源等。一个子系统就是一个资源控制器,鼻子痛必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级后,这个层级上的所有控制组全都受到这个子系统的控制。

hierarchy可以认为是一系列cgroups的集合。hierarchy是这个集合的根。

相互关系:

每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup,此 cgroup 在创建层级时自动创建,后面在该层级中创建的 cgroup 都是此

cgroup 的后代)的初始成员;

一个子系统最多只能附加到一个层级;

一个层级可以附加多个子系统;

一个任务可以是多个 cgroup 的成员,但是这些 cgroup 必须在不同的层级;

系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的 cgroup。

clip_image002

图表 1CGroup层级图

1.2 mount介绍

更详细信息参考:

linux内核mount系统调用源码分析http://blog.csdn.net/wugj03/article/details/41958029/

linux系统调用mount全过程分析http://blog.csdn.net/skyflying2012/article/details/9748133

在系统启动时,mount需要的CGroup子系统:

mount cgroup none /dev/cpuctl cpu

在用户空间将mount命令转换成系统调用sys_mount:

asmlinkage long sys_mount(char __user *dev_name, char __user *dir_name,

char __user *type, unsigned long flags,

void __user *data);

从sys_mount到具体文件系统的.mount调用流程如下:

sys_mount(fs/namespace.c)

  -->do_mount(kernel_dev, dir_name, kernel_type, flags, (void *)data_pate)

    -->do_new_mount (&path, type_page, flags, mnt_flags,dev_name, data_page)

      --> vfs_kern_mount(type, flags, name, data)

        --> mount_fs(type, flags, name, data)

          --> type->mount(type, flags, name, data)

            --> cgroup_mount(fs_type, flags, unused_dev_name, data)

 

 

struct file_system_type {

const char *name; 文件系统名称

int fs_flags;

struct dentry *(*mount) (struct file_system_type *, int,

const char *, void *); 挂载文件系统的调用。

void (*kill_sb) (struct super_block *); 卸载文件系统的调用

struct module *owner; VFS内部调用时使用

struct file_system_type * next;

struct hlist_head fs_supers;

struct lock_class_key s_lock_key;

struct lock_class_key s_umount_key;

struct lock_class_key s_vfs_rename_key;

struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

struct lock_class_key i_lock_key;

struct lock_class_key i_mutex_key;

struct lock_class_key i_mutex_dir_key;

}

2 代码分析

2.1 核心

2.1.1 框架结构图

CGoup核心主要创建一系列sysfs文件,用户空间可以通过这些节点控制CGroup各子系统行为。各子系统模块根据参数,在执行过程中或调度进程道不同CPU上,或控制CPU占用时间,或控制IO带宽等等。另,在每个进程的proc文件系统中都有一个cgroup,显示该进程对应的CGroup各子系统信息。

image

 

如果CGroup需要early_init,start_kernel调用cgroup_init_early在系统启动时进行CGroup初始化。

int __init cgroup_init_early(void)

{

static struct cgroup_sb_opts __initdata opts;

struct cgroup_subsys *ss;

int i;

init_cgroup_root(&cgrp_dfl_root, &opts);

cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

RCU_INIT_POINTER(init_task.cgroups, &init_css_set);

for_each_subsys(ss, i) {

ss->id = i;

ss->name = cgroup_subsys_name[i];

if (ss->early_init)

cgroup_init_subsys(ss, true);

}

return 0;

}

CGroup的起点是start_kernel->cgroup_init,进入CGroup的初始化,主要注册cgroup文件系统和创建、proc文件,初始化不需要early_init的子系统。

int __init cgroup_init(void)

{

for_each_subsys(ss, ssid) { 遍历所有子系统,初始化,根据属性配置不同文件节点

if (ss->early_init) { early_init是能的已经在cgroup_init_early中初始化

struct cgroup_subsys_state *css =

init_css_set.subsys[ss->id];

css->id = cgroup_idr_alloc(&ss->css_idr, css, 1, 2,

GFP_KERNEL);

BUG_ON(css->id < 0);

} else {

cgroup_init_subsys(ss, false);

}

list_add_tail(&init_css_set.e_cset_node[ssid],

&cgrp_dfl_root.cgrp.e_csets[ssid]);

cgrp_dfl_root.subsys_mask |= 1 << ss->id;

if (cgroup_legacy_files_on_dfl && !ss->dfl_cftypes)

ss->dfl_cftypes = ss->legacy_cftypes;

if (!ss->dfl_cftypes)

cgrp_dfl_root_inhibit_ss_mask |= 1 << ss->id;

根据不同类型,创建不同节点列表。

if (ss->dfl_cftypes == ss->legacy_cftypes) {

WARN_ON(cgroup_add_cftypes(ss, ss->dfl_cftypes));

} else {

WARN_ON(cgroup_add_dfl_cftypes(ss, ss->dfl_cftypes));

WARN_ON(cgroup_add_legacy_cftypes(ss, ss->legacy_cftypes));

}

if (ss->bind)

ss->bind(init_css_set.subsys[ssid]);

}

err = sysfs_create_mount_point(fs_kobj, "cgroup"); 创建挂载节点/sys/fs/cgroup

if (err)

return err;

err = register_filesystem(&cgroup_fs_type); 注册cgroup_fs_type文件系统。

if (err < 0) {

sysfs_remove_mount_point(fs_kobj, "cgroup");

return err;

}

proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations); 创建/proc/cgroups节点

return 0;

}

/proc/<pid>/cgroup指向proc_cgroup_show,用于显示此pid所对应的cgroup路径信息:

static const struct pid_entry tid_base_stuff[] = {

#ifdef CONFIG_CGROUPS

ONE("cgroup", S_IRUGO, proc_cgroup_show),

#endif

}

CGroup的debug接口在CONFIG_CGROUP_DEBUG使能后打开:

struct cgroup_subsys debug_cgrp_subsys = {

.css_alloc = debug_css_alloc,

.css_free = debug_css_free,

.legacy_cftypes = debug_files,

};

cgroup_fs_type作为mount命令的参数,其中cgroup_mount为各subsystem进行mount工作。

 

image

2.1.2 核心结构体分析

struct cgroup_subsys {

struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);

创建cgroup_subsys_state结构体,和css_free相对。

int (*css_online)(struct cgroup_subsys_state *css); 在subsystem相关资源分配完之后,进行online相关操作,和css_offline相对。

void (*css_offline)(struct cgroup_subsys_state *css);

void (*css_released)(struct cgroup_subsys_state *css);

void (*css_free)(struct cgroup_subsys_state *css);

void (*css_reset)(struct cgroup_subsys_state *css);

void (*css_e_css_changed)(struct cgroup_subsys_state *css);

int (*allow_attach)(struct cgroup_subsys_state *css,

struct cgroup_taskset *tset);

int (*can_attach)(struct cgroup_subsys_state *css,

struct cgroup_taskset *tset);

void (*cancel_attach)(struct cgroup_subsys_state *css,

struct cgroup_taskset *tset);

void (*attach)(struct cgroup_subsys_state *css,

struct cgroup_taskset *tset);

allow_attach和can_attach都是在将task附着到cgroup之前进行检查,如果失败则停止附着过程。

cancel_attach是在can_attache成功之后,但是attache又失败的情况下调用。

void (*fork)(struct task_struct *task); 将一个task写入cgroup

void (*exit)(struct cgroup_subsys_state *css, 将一个task移出cgroup

struct cgroup_subsys_state *old_css,

struct task_struct *task);

void (*bind)(struct cgroup_subsys_state *root_css);

int disabled;

int early_init; 是否需要进行early_init,如果需要会在cgroup_init_early中提前进行初始化。

bool broken_hierarchy;

bool warned_broken_hierarchy;

/* the following two fields are initialized automtically during boot */

int id;

#define MAX_CGROUP_TYPE_NAMELEN 32

const char *name;

/* link to parent, protected by cgroup_lock() */

struct cgroup_root *root;

/* idr for css->id */

struct idr css_idr;

/*

* List of cftypes. Each entry is the first entry of an array

* terminated by zero length name.

*/

struct list_head cfts;

/*

* Base cftypes which are automatically registered. The two can

* point to the same array.

*/

struct cftype *dfl_cftypes; /* for the default hierarchy */

struct cftype *legacy_cftypes; /* for the legacy hierarchies */

CGroup子系统的节点,这些节点都是在xxx_cgrp_subsys中定义的。

/*

* A subsystem may depend on other subsystems. When such subsystem

* is enabled on a cgroup, the depended-upon subsystems are enabled

* together if available. Subsystems enabled due to dependency are

* not visible to userland until explicitly enabled. The following

* specifies the mask of subsystems that this one depends on.

*/

unsigned int depends_on;

}

legacy hierarchy类型的接口文件:

static struct cftype cgroup_legacy_base_files[] = {

{

.name = "cgroup.procs",

},

{

.name = "cgroup.clone_children",

.read_u64 = cgroup_clone_children_read,

.write_u64 = cgroup_clone_children_write,

},

{

.name = "cgroup.sane_behavior",

.flags = CFTYPE_ONLY_ON_ROOT,

.seq_show = cgroup_sane_behavior_show,

},

{

.name = "tasks",

},

{

.name = "notify_on_release",

.read_u64 = cgroup_read_notify_on_release,

.write_u64 = cgroup_write_notify_on_release,

},

{

.name = "release_agent",

},

{ } /* terminate */

}

cgroup.procs:属于该分组的PID列表,仅包括多线程进程的线程leader的TID,这点和tasks不同。

cgroup.clone_children:仅适用于cpuset。如果使能,创建子cpuset时,就会拷贝父cpust的配置。

cgroup.sane_behavior:未实现。

tasks:属于该分组的线程TID列表。

notify_on_release:设置是否执行release_agent,为1时使能。

release_agent:删除分组时执行的命令,这个文件只存在于根分组。

2.2 子系统

CGroup子系统都定义在cgroup_subsys.h中;kernel/cgroup.c包含此头文件,定义了两个结构体数组cgroup_subsys和cgroup_subsys_name。CGroup core对于各子系统的引用都是通过cgroup_subsys这个数组。

/* generate an array of cgroup subsystem pointers */

#define SUBSYS(_x) [_x ## _cgrp_id] = &_x ## _cgrp_subsys,

static struct cgroup_subsys *cgroup_subsys[] = {

#include <linux/cgroup_subsys.h>

};

#undef SUBSYS

/* array of cgroup subsystem names */

#define SUBSYS(_x) [_x ## _cgrp_id] = #_x,

static const char *cgroup_subsys_name[] = {

#include <linux/cgroup_subsys.h>

};

#undef SUBSYS

2.2.1 blkio

block/blk-cgroup.c中定义了blkio子系统结构体:

struct cgroup_subsys io_cgrp_subsys = {
    .css_alloc = blkcg_css_alloc,
    .css_offline = blkcg_css_offline,
    .css_free = blkcg_css_free,
    .can_attach = blkcg_can_attach,
    .bind = blkcg_bind,
    .dfl_cftypes = blkcg_files,
    .legacy_cftypes = blkcg_legacy_files,
    .legacy_name = "blkio",
#ifdef CONFIG_MEMCG
    /*
     * This ensures that, if available, memcg is automatically enabled
     * together on the default hierarchy so that the owner cgroup can
     * be retrieved from writeback pages.
     */
    .depends_on = 1 << memory_cgrp_id,
#endif
};

 

由于默认init.rc没有加载,可以添加创建相关以便研究:

mkdir /dev/blkio  0700 root system
mount cgroup none /dev/blkio blkio

由于cfq_iosched.c中cfq_init注册了blkcg_policy_cfq,配置节点就变成如下列表。

CFQ,Complete Fairness Queueing。CFQ调度器主要目的提供一种进程间公平分配磁盘带宽的调度方法。

深度阅读:http://lxr.free-electrons.com/source/Documentation/block/cfq-iosched.txt

blkio.io_merged
blkio.io_merged_recursive
blkio.io_queued
blkio.io_queued_recursive
blkio.io_service_bytes
blkio.io_service_bytes_recursive
blkio.io_service_time
blkio.io_service_time_recursive
blkio.io_serviced
blkio.io_serviced_recursive
blkio.io_wait_time
blkio.io_wait_time_recursive
blkio.leaf_weight
blkio.leaf_weight_device
blkio.reset_stats
blkio.sectors
blkio.sectors_recursive
blkio.time
blkio.time_recursive
blkio.weight
blkio.weight_device

 

2.2.2 cpu

kernel/sched/core.c定义了cpu_cgrp_subsys结构体,需要进行early_init。

struct cgroup_subsys cpu_cgrp_subsys = {

.css_alloc = cpu_cgroup_css_alloc,

.css_free = cpu_cgroup_css_free,

.css_online = cpu_cgroup_css_online,

.css_offline = cpu_cgroup_css_offline,

.fork = cpu_cgroup_fork,

.can_attach = cpu_cgroup_can_attach,

.attach = cpu_cgroup_attach,

.allow_attach = subsys_cgroup_allow_attach,

.exit = cpu_cgroup_exit,

.legacy_cftypes = cpu_files,

.early_init = 1,

};

这里重点分析一下cpu_files,在实际使用中打开了shares/rt_runtime_us/rt_perios_us。

其中shares是针对CFS进程的,rt_runtime_us/rt_perios_us是针对RT进程的。

另外cfs_perios_us/cfs_quota_us是设置CFS进程占用的CPU带宽。

static struct cftype cpu_files[] = {
#ifdef CONFIG_FAIR_GROUP_SCHED
    {
        .name = "shares",
        .read_u64 = cpu_shares_read_u64,
        .write_u64 = cpu_shares_write_u64,
    },
#endif
#ifdef CONFIG_CFS_BANDWIDTH
    {
        .name = "cfs_quota_us",
        .read_s64 = cpu_cfs_quota_read_s64,
        .write_s64 = cpu_cfs_quota_write_s64,
    },
    {
        .name = "cfs_period_us",
        .read_u64 = cpu_cfs_period_read_u64,
        .write_u64 = cpu_cfs_period_write_u64,
    },
    {
        .name = "stat",
        .seq_show = cpu_stats_show,
    },
#endif
#ifdef CONFIG_RT_GROUP_SCHED
    {
        .name = "rt_runtime_us",
        .read_s64 = cpu_rt_runtime_read,
        .write_s64 = cpu_rt_runtime_write,
    },
    {
        .name = "rt_period_us",
        .read_u64 = cpu_rt_period_read_uint,
        .write_u64 = cpu_rt_period_write_uint,
    },
#endif
    { }    /* terminate */
}

下面是Android一个配置,根目录下面对应的是前台应用,bg_non_interactive对应的是后台应用:

/dev/cpuctl/cpu.shares  1024

/dev/cpuctl/cpu.rt_runtime_us 950000

/dev/cpuctl/cpu.rt_period_us  1000000

/dev/cpuctl/bg_non_interactive/cpu.shares  52

/dev/cpuctl/bg_non_interactive/cpu.rt_runtime_us 10000

/dev/cpuctl/bg_non_interactive/cpu.rt_period_us  1000000

cpu.shares:保存了整数值,用来设置cgroup分组任务获得CPU时间的相对值。举例来说,cgroup A和cgroup B的cpu.share值都是1024,那么cgroup A 与cgroup B中的任务分配到的CPU时间相同,如果cgroup C的cpu.share为512,那么cgroup C中的任务获得的CPU时间是A或B的一半。

从上面的数据可以看出,默认分组与bg_non_interactive分组cpu.share值相比接近于20:1。由于Android中只有这两个cgroup,也就是说默认分组中的应用可以利用95%的CPU,而处于bg_non_interactive分组中的应用则只能获得5%的CPU利用率。

52/(1024+52)=4.8%

 

cpu.rt_runtime_us:主要用来设置cgroup获得CPU资源的周期,单位为微妙。

cpu.rt_period_us:主要是用来设置cgroup中的任务可以最长获得CPU资源的时间,单位为微秒。设定这个值可以访问某个cgroup独占CPU资源。最长的获取CPU资源时间取决于逻辑CPU的数量。比如cpu.rt_runtime_us设置为200000(0.2秒),cpu.rt_period_us设置为1000000(1秒)。在单个逻辑CPU上的获得时间为每秒为0.2秒。 2个逻辑CPU,获得的时间则是0.4秒。

从上面数据可以看出,默认分组中单个逻辑CPU下每一秒内可以获得0.95秒执行时间。bg_non_interactive分组下单个逻辑CPU下每一秒内可以获得0.01秒。

疑问点:在shares和rt_period_us/cfs_period_us之间时间究竟如何分配?是先按照shares分组分配CPU时间,然后组内优先RT,剩下来给CFS?还是先RT进程按照shares分配,剩下来给CFS按照shares分配。分配时间shares在先还是RT/CFS之间在先?

 

cpu.cfs_runtime_us

cpu.cfs_quota_us

一个测试脚本:

#!/usr/bin/env python

# coding=utf-8

i = 0

while True:

    i = i + 1

1.执行loop.py,python loop.py &。

2.top监控,可以看出CUP占用率在100%。

image

3.echo pid > tasks,将loop.py进程加入cpu组。

4.echo 10000 > cpu.cfs_quota_us,在cpu.cfs_period_us为100000的情况下占用率应该为10%.

image

5.下面依次为5000、50000对应占用率为5%、50%的情况。

image

image

 

 

2.2.3 cpuacct

kernel/sched/cpuacct.c中定义cpuacct_cgrp_subsys子系统结构体:

struct cgroup_subsys cpuacct_cgrp_subsys = {
    .css_alloc    = cpuacct_css_alloc,
    .css_free    = cpuacct_css_free,
    .legacy_cftypes    = files,
    .early_init    = 1,
};

其中legacy_cftypes的files是cpuacct子系统的精髓:

static struct cftype files[] = {
    {
        .name = "usage",
        .read_u64 = cpuusage_read,
        .write_u64 = cpuusage_write,
    },
    {
        .name = "usage_percpu",
        .seq_show = cpuacct_percpu_seq_show,
    },
    {
        .name = "stat",
        .seq_show = cpuacct_stats_show,
    },
    { }    /* terminate */
};

要理解每个统计信息的含义就绕不开struct cpuacct这个结构体。

usage:是所有usage_percpu之和。

usage_percpu:是每个CPU的使用量。

stat:分别统计user和system两种类型的,user对应CPUTIME_USER、CPUTIME_NICE,system对应CPUTIME_SYSTEM、CPUTIME_IRQ、CPUTIME_SOFTIRQ。

struct cpuacct {
    struct cgroup_subsys_state css;  通过此成员可以和子系统关联起来
    /* cpuusage holds pointer to a u64-type object on every cpu */
    u64 __percpu *cpuusage;  per-CPU类型成员,记录每个cpu被使用程度
    struct kernel_cpustat __percpu *cpustat;  per-CPU类型成员,分类统计CPU使用时间,粒度更细,包括:
};

enum cpu_usage_stat {
    CPUTIME_USER,
    CPUTIME_NICE,
    CPUTIME_SYSTEM,
    CPUTIME_SOFTIRQ,
    CPUTIME_IRQ,
    CPUTIME_IDLE,
    CPUTIME_IOWAIT,
    CPUTIME_STEAL,
    CPUTIME_GUEST,
    CPUTIME_GUEST_NICE,
    NR_STATS,
};

下面HiKey的一个瞬间的cpuacct值:

cpuacct.stat
cpuacct.usage
cpuacct.usage_percpu

user 2312 system 2662
57822767793
11525417930 10088633594 7132090309 7864757745 4475737297 5532987302 8750543618 2452703748

我们将其转换成每个CPU用量的百分比,结合cpuset的设置。可以得出结论:

cpu0的任务最重,所有类型的进程都可能在cpu0上调度。,cluster0要比cluster1更多的被使用。

cpu7最少被使用,因为只有top-app才会使用。

/dev/cpuset/cpus  0-7
/dev/cpuset/background/cpus  0
/dev/cpuset/foreground/cpus  0-6
/dev/cpuset/system-background/cpus  0-3
/dev/cpuset/top-app/cpus  0-7

image

2.2.4 cpuset

cpuset是一个用来分配限制CPU和Memory资源的CGroup子系统。cpuset使用sched_setaffinity系统调用来设置tasks的CPU亲和性,使用mbind和set_mempolicy包含Memory策略中的Memory Nodes。调度器不会在cpuset之外的CPU上面调度tasks,页分配器也不会在mems_allowed之外的内存中分配。

cpuset提供了一种灵活配置CPU和Memory资源的机制。Linux中已经有配置CPU资源的cpu子系统和Memory资源的memory子系统。

kernel/cpuset.c中定义了子系统cpuset结构体如下:

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,

.bind = cpuset_bind,

.legacy_cftypes = files,

.early_init = 1,

};

 

cpu_exclusive cpu资源是否专用?

cpus 当前cpuset的CPU列表。

effective_cpus 有效的CPU列表

effective_mems 有效的memory

mem_exclusive memory资源是否专用?

mem_hardwall

memory_migrate 如果置位,则将页面移到cpusets节点

memory_pressure 测量当前cpuset的paging压力

memory_spread_page if set, spread page cache evenly on allowed nodes

memory_spread_slab if set, spread slab cache evenly on allowed nodes

mems 当前cpuset的Memory Nodes列表

sched_load_balance 当前cpuset是否进行负载均衡

sched_relax_domain_level the searching range when migrating tasks

如果设置了cpu/memory专用,除了直接父子,其他cpuset不可以使用相应的CPU或者Memory Nodes。

其他项的详细解释见:Documentation/cgroups/cpust.txt。

cpuset结构体中设置的cpus_allowed、mems_allowed、effective_cpus、effective_mems都会在写入cpus、mems节点是更行到当前cpuset下的task相关的。

其他情况还包括CPU hotplug的时候动态更新cpus_allowed信息。

cpuset_write_resmask

  -->update_cpumask

  -->update_nodemask

    -->update_cpumasks_hier

    -->update_nodemasks_hier

      -->update_tasks_cpumask 遍历当前cpuset下所有的task的cpus_allowed

      -->update_tasks_nodemask 更新当前cpuset下所有task的mems_allowed

        -->set_cpus_allowed_ptr 将cpuset的cpumask赋给task->cpus_allowed,将task转移到合适的CPU;如果CPU被拔出,则将其迁移到其它被允许的CPU上。

        --> cpuset_migrate_mm 将memory区域从一个node迁移到另一个

          --> do_migrate_pages 在两个node之间移动页

 

 

struct cpuset {

struct cgroup_subsys_state css;

unsigned long flags; /* "unsigned long" so bitops work */

/* user-configured CPUs and Memory Nodes allow to tasks */

cpumask_var_t cpus_allowed; 和task_struct->cpus_allowed相对应

nodemask_t mems_allowed; 和task_struct->mems_allowed相对应

/* effective CPUs and Memory Nodes allow to tasks */

cpumask_var_t effective_cpus;

nodemask_t effective_mems;

default hierarchy:effective_mask=configured_mask&parent’s effective_mask

legacy hierarchy: user-configured masks = effective masks

nodemask_t old_mems_allowed;

struct fmeter fmeter; /* memory_pressure filter */

int attach_in_progress;

/* for custom sched domain */

int relax_domain_level;

}

cpuset在Android的应用主要差异就是不同组配置不同的cpus,根据进程类型细分。

可以看出优先级越高的进程可以占用的cpu越多。

/dev/cpuset/cpus  0-7
/dev/cpuset/background/cpus  0
/dev/cpuset/foreground/cpus  0-6
/dev/cpuset/system-background/cpus  0-3
/dev/cpuset/top-app/cpus  0-7

只有根节点的mem_exclusive使能,其他都未使能。

/dev/cpuset/mem_exclusive  1
/dev/cpuset/background/mem_exclusive  0
/dev/cpuset/foreground/mem_exclusive  0
/dev/cpuset/system-background/mem_exclusive  0
/dev/cpuset/top-app/mem_exclusive  0

2.2.5 devices

security/device_cgroup.c定义devices子系统结构体:

struct cgroup_subsys devices_cgrp_subsys = {
    .css_alloc = devcgroup_css_alloc,
    .css_free = devcgroup_css_free,
    .css_online = devcgroup_online,
    .css_offline = devcgroup_offline,
    .legacy_cftypes = dev_cgroup_files,
};

2.2.6 hugetlb

mm/hugetlb_cgroup.c

struct cgroup_subsys hugetlb_cgrp_subsys

2.2.7 memory

mm/memcontrol.c中定义了memory子系统memory_cgrp_subsys如下:

struct cgroup_subsys memory_cgrp_subsys = {

.css_alloc = mem_cgroup_css_alloc,

.css_online = mem_cgroup_css_online,

.css_offline = mem_cgroup_css_offline,

.css_free = mem_cgroup_css_free,

.css_reset = mem_cgroup_css_reset,

.can_attach = mem_cgroup_can_attach,

.cancel_attach = mem_cgroup_cancel_attach,

.attach = mem_cgroup_move_task,

.allow_attach = mem_cgroup_allow_attach,

.bind = mem_cgroup_bind,

.dfl_cftypes = memory_files,

.legacy_cftypes = mem_cgroup_legacy_files,

.early_init = 0,

};

 

memory_files和mem_cgroup_legacy_files的解释如下:

cgroup.event_control  event_fd的接口

memory.failcnt 显示内存(进程内存+页面缓存) 达到限制值的次数

memory.force_empty 强制释放分配给分组的内存

memory.kmem.failcnt 显示内存(进程内存+页面缓存)+交换区到达限制值的次数

memory.kmem.limit_in_bytes

memory.kmem.max_usage_in_bytes 显示记录的内存(进程内存+页面缓存)+交换区使用量的最大值

memory.kmem.slabinfo

memory.kmem.tcp.failcnt

memory.kmem.tcp.limit_in_bytes

memory.kmem.tcp.max_usage_in_bytes

memory.kmem.tcp.usage_in_bytes

memory.kmem.usage_in_bytes

memory.limit_in_bytes 显示当前内存(进程内存+页面缓存)的使用量的限制值

memory.max_usage_in_bytes 显示记录的内存使用量的最大值

memory.memsw.failcnt

memory.memsw.limit_in_bytes 显示当前内存(进程内存+页面缓存)+交换区使用量的限制值

memory.memsw.usage_in_bytes 显示当前内存(进程内存+页面缓存)+交换区使用量的使用值

memory.memsw.max_usage_in_bytes

memory.memsw.usage_in_bytes 显示当前内存(进程内存+页面缓存)+交换区使用量

memory.move_charge_at_immigrate

memory.oom_control

memory.pressure_level  设置内存压力通知

memory.soft_limit_in_bytes

memory.stat 输出统计信息

memory.swappiness 设置、显示针对分组的swappiness

memory.usage_in_bytes 显示当前内存(进程内存+页面缓存)的使用量

memory.use_hierarchy 设置、显示层次结构的使用

Android下的一个实例,显示/dev/memcfg/apps并未使用:

Item /dev/memcfg /dev/memcfg/apps

memory.failcnt
memory.force_empty
memory.kmem.failcnt
memory.kmem.limit_in_bytes
memory.kmem.max_usage_in_bytes
memory.kmem.slabinfo
memory.kmem.tcp.failcnt
memory.kmem.tcp.limit_in_bytes
memory.kmem.tcp.max_usage_in_bytes
memory.kmem.tcp.usage_in_bytes
memory.kmem.usage_in_bytes
memory.limit_in_bytes
memory.max_usage_in_bytes
memory.memsw.failcnt
memory.memsw.limit_in_bytes
memory.memsw.max_usage_in_bytes
memory.memsw.usage_in_bytes
memory.move_charge_at_immigrate
memory.oom_control
memory.pressure_level
memory.soft_limit_in_bytes
memory.stat
memory.swappiness
memory.usage_in_bytes
memory.use_hierarchy

0
cat: memory.force_empty: Invalid argument
0
9223372036854771712
0
slabinfo - version: 2.1
0
2251799813685247
0
0
0
9223372036854771712
0
0
9223372036854771712
0
644067328
0
oom_kill_disable 0 nder_oom 0
cat: memory.pressure_level: Invalid argument
9223372036854771712
cache 446771200 …
60
644067328
0

0
cat: memory.force_empty: Invalid argument
0
9223372036854771712
0
slabinfo - version: 2.1
0
9223372036854771712
0
0
0
9223372036854771712
0
0
9223372036854771712
0
0
0
oom_kill_disable 0 under_oom 0
cat: memory.pressure_level: Invalid argument
9223372036854771712
cache 0
60
0
0

两者的memory.stat如下:

/dev/memcfg /dev/memcfg/apps

cache 446832640
rss 197902336
rss_huge 2097152
mapped_file 321081344
dirty 0
writeback 0
swap 0
pgpgin 252747
pgpgout 96874
pgfault 335718
pgmajfault 3715
inactive_anon 1814528
active_anon 201084928
inactive_file 362172416
active_file 79249408
unevictable 262144
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 446832640
total_rss 197902336
total_rss_huge 2097152
total_mapped_file 321081344
total_dirty 0
total_writeback 0
total_swap 0
total_pgpgin 252747
total_pgpgout 96874
total_pgfault 335718
total_pgmajfault 3715
total_inactive_anon 1814528
total_active_anon 201084928
total_inactive_file 362172416
total_active_file 79249408
total_unevictable 262144

cache 0
rss 0
rss_huge 0
mapped_file 0
dirty 0
writeback 0
swap 0
pgpgin 0
pgpgout 0
pgfault 0
pgmajfault 0
inactive_anon 0
active_anon 0
inactive_file 0
active_file 0
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 0
total_rss 0
total_rss_huge 0
total_mapped_file 0
total_dirty 0
total_writeback 0
total_swap 0
total_pgpgin 0
total_pgpgout 0
total_pgfault 0
total_pgmajfault 0
total_inactive_anon 0
total_active_anon 0
total_inactive_file 0
total_active_file 0
total_unevictable 0

2.2.8 net_cls

net/core/netclassid_cgroup.c

struct cgroup_subsys net_cls_cgrp_subsys

2.2.9 net_prio

net/core/netprio_cgroup.c

struct cgroup_subsys net_prio_cgrp_subsys

2.2.10 net_perf

kernel/event/core.c

struct cgroup_subsys perf_event_cgrp_subsys

2.2.11 perf_event

kernel/events/core.c

struct cgroup_subsys perf_event_cgrp_subsys

3 CGroup在Android中的应用

# Mount cgroup mount point for cpu accounting

mount cgroup none /acct cpuacct

 

schedtune是ARM/Linaro为了EAS新增的一个子系统,主要用来控制进程调度选择CPU以及boost触发。

这部分涉及到EAS、Android进程调度策略等相关知识,另起一篇专门介绍《Android中关于cpu/cpuset/schedtune的应用》。

https://git.linaro.org/people/john.stultz/android-dev.git

branch:remotes/origin/android-hikey-linaro-4.4-EASv5.2+aosp

# Create energy-aware scheduler tuning nodes

mkdir /dev/stune

mount cgroup none /dev/stune schedtune

 

# root memory control cgroup, used by lmkd

mkdir /dev/memcg 0700 root system

mount cgroup none /dev/memcg memory

 

# Create cgroup mount points for process groups

mkdir /dev/cpuctl

mount cgroup none /dev/cpuctl cpu

 

# sets up initial cpusets for ActivityManager
mkdir /dev/cpuset
mount cpuset none /dev/cpuset

以上sysfs节点在Android中都有对应的HAL层库文件,其中cpuctl/cpuset/stune对应libcutils.so,代码在system/core/libcutils中,主要根据进程的不同分类,进行CPU、memory资源控制;memory子系统对应lmkd系统服务,代码在system/core/lmkd,主要在内存紧张情况下,杀死低优先级进程,以达到释放内存的目的。

针对libcutils.so会在《Android中关于cpu/cpuset/schedtune的应用》,lmkd会在《Android中基于CGroup的memory子系统HAL层分析-lmkd》。

posted on 2016-12-21 17:26  ArnoldLu  阅读(14545)  评论(0编辑  收藏  举报

导航