cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。

本文以 Ubuntu 16.04 系统为例介绍 cgroups,所有的 demo 均在该系统中演示。

为什么要了解 cgroups

在以容器技术为代表的虚拟化技术大行其道的时代了解 cgroups 技术是非常必要的!比如我们可以很方便的限制某个容器可以使用的 CPU、内存等资源,这究竟是如何实现的呢?通过了解 cgroups 技术,我们可以窥探到 linux 系统中整个资源限制系统的脉络。从而帮助我们更好的理解和使用 linux 系统。

cgroups 的主要作用

实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:

  • 资源限制:cgroups 可以对任务是要的资源总额进行限制。比如设定任务运行时使用的内存上限,一旦超出就发 OOM。
  • 优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。
  • 资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。
  • 任务控制:cgroups 可以对任务执行挂起、恢复等操作。

相关概念

Task(任务) 在 linux 系统中,内核本身的调度和管理并不对进程和线程进行区分,只是根据 clone 时传入的参数的不同来从概念上区分进程和线程。这里使用 task 来表示系统的一个进程或线程。

Cgroup(控制组) cgroups 中的资源控制以 cgroup 为单位实现。Cgroup 表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另一个 cgroup。

Subsystem(子系统) cgroups 中的子系统就是一个资源调度控制器(又叫 controllers)。比如 CPU 子系统可以控制 CPU 的时间分配,内存子系统可以限制内存的使用量。以笔者使用的 Ubuntu 16.04.3 为例,其内核版本为 4.10.0,支持的 subsystem 如下( cat /proc/cgroups):
 blkio         对块设备的 IO 进行限制。
 cpu           限制 CPU 时间片的分配,与 cpuacct 挂载在同一目录。
 cpuacct     生成 cgroup 中的任务占用 CPU 资源的报告,与 cpu 挂载在同一目录。
 cpuset       给 cgroup 中的任务分配独立的 CPU(多处理器系统) 和内存节点。
 devices     允许或禁止 cgroup 中的任务访问设备。
 freezer      暂停/恢复 cgroup 中的任务。
 hugetlb     限制使用的内存页数量。              
 memory    对 cgroup 中的任务的可用内存进行限制,并自动生成资源占用报告。
 net_cls      使用等级识别符(classid)标记网络数据包,这让 Linux 流量控制器(tc 指令)可以识别来自特定 cgroup 任务的数据包,并进行网络限制。
 net_prio    允许基于 cgroup 设置网络流量(netowork traffic)的优先级。
 perf_event  允许使用 perf 工具来监控 cgroup。
 pids          限制任务的数量。

Hierarchy(层级) 层级有一系列 cgroup 以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的 cgroup 节点可以包含零个或多个子节点,子节点继承父节点挂载的子系统。一个操作系统中可以有多个层级。

cgroups 的文件系统接口

cgroups 以文件的方式提供应用接口,我们可以通过 mount 命令来查看 cgroups 默认的挂载点:

$ mount | grep cgroup

第一行的 tmpfs 说明 /sys/fs/cgroup 目录下的文件都是存在于内存中的临时文件。
第二行的挂载点 /sys/fs/cgroup/systemd 用于 systemd 系统对 cgroups 的支持,相关内容笔者今后会做专门的介绍。
其余的挂载点则是内核支持的各个子系统的根级层级结构。

需要注意的是,在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。换句话说,系统是不建议我们在 /sys/fs/cgroup 目录下创建新的目录并挂载其它子系统的。这一点与之前的操作系统不太一样。

下面让我们来探索一下 /sys/fs/cgroup 目录及其子目录下都是些什么:

/sys/fs/cgroup 目录下是各个子系统的根目录。我们以 memory 子系统为例,看看 memory 目录下都有什么?

这些文件就是 cgroups 的 memory 子系统中的根级设置。比如 memory.limit_in_bytes 中的数字用来限制进程的最大可用内存,memory.swappiness 中保存着使用 swap 的权重等等。

既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。

查看进程所属的 cgroups

可以通过 /proc/[pid]/cgroup 来查看指定进程属于哪些 cgroup:

每一行包含用冒号隔开的三列,他们的含义分别是:

  • cgroup 树的 ID, 和 /proc/cgroups 文件中的 ID 一一对应。
  • 和 cgroup 树绑定的所有 subsystem,多个 subsystem 之间用逗号隔开。这里 name=systemd 表示没有和任何 subsystem 绑定,只是给他起了个名字叫 systemd。
  • 进程在 cgroup 树中的路径,即进程所属的 cgroup,这个路径是相对于挂载点的相对路径。

既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。

cgroups 工具

在介绍通过 systemd 应用 cgroups 之前,我们先使用 cgroup-bin 工具包中的 cgexec 来演示 demo。Ubuntu 默认没有安装 cgroup-bin 工具包,请通过下面的命令安装:

$ sudo apt install cgroup-bin

demo:限制进程可用的 CPU

在我们使用 cgroups 时,最好不要直接在各个子系统的根目录下直接修改其配置文件。推荐的方式是为不同的需求在子系统树中定义不同的节点。比如我们可以在 /sys/fs/cgroup/cpu 目录下新建一个名称为 nick_cpu 的目录:

$ cd  /sys/fs/cgroup/cpu
$ sudo mkdir nick_cpu

然后查看新建的目录下的内容:

是不是有点吃惊,cgroups 的文件系统会在创建文件目录的时候自动创建这些配置文件!

让我们通过下面的设置把 CPU 周期限制为总量的十分之一:

$ sudo su
$ echo > nick_cpu/cpu.cfs_period_us
$ echo > nick_cpu/cpu.cfs_quota_us

上面的两个参数眼熟吗?没错,笔者在《Docker: 限制容器可用的 CPU》一文中介绍的 "--cpu-period=100000 --cpu-quota=200000" 就是由它们实现的。

然后创建一个 CPU 密集型的程序:

void main()
{
unsigned int i, end; end = * * ;
for(i = ; i < end; )
{
i ++;
}
}

保存为文件 cputime.c 编译并通过不同的方式执行:

$ gcc cputime.c -o cputime
$ sudo su
$ time ./cputime
$ time cgexec -g cpu:nick_cpu ./cputime

time 命令可以为我们报告程序执行消耗的时间,其中的 real 就是我们真实感受到的时间。使用 cgexec 能够把我们添加的 cgroup 配置 nick_cpu 应用到运行 cputime 程序的进程上。 上图显示,默认的执行只需要 2s 左右。通过 cgroups 限制 CPU 资源后需要运行 23s。

demo:限制进程可用的内存

这次我们来限制进程可用的最大内存,在 /sys/fs/cgroup/memory 下创建目录nick_memory:

$ cd  /sys/fs/cgroup/memory
$ sudo mkdir nick_memory

下面的设置把进程的可用内存限制在最大 300M,并且不使用 swap:

# 物理内存 + SWAP <=  MB;** =
$ sudo su
$ echo > nick_memory/memory.limit_in_bytes
$ echo > nick_memory/memory.swappiness

然后创建一个不断分配内存的程序,它分五次分配内存,每次申请 100M:

#include<stdio.h>
#include<stdlib.h>
#include<string.h> #define CHUNK_SIZE 1024 * 1024 * 100 void main()
{
char *p;
int i; for(i = ; i < ; i ++)
{
p = malloc(sizeof(char) * CHUNK_SIZE);
if(p == NULL)
{
printf("fail to malloc!");
return ;
}
// memset() 函数用来将指定内存的前 n 个字节设置为特定的值
memset(p, , CHUNK_SIZE);
printf("malloc memory %d MB\n", (i + ) * );
}
}

把上面的代码保存为 mem.c 文件,然后编译:

$ gcc mem.c -o mem

执行生成的 mem 程序:

$ ./mem

此时一切顺利,然后加上刚才的约束试试:

$ cgexec -g memory:nick_memory ./mem

由于内存不足且禁止使用 swap,所以被限制资源的进程在申请内存时被强制杀死了。

下面再使用 stress 程序测试一个类似的场景(通过 stress 程序申请 500M 的内存):

$ sudo cgexec -g memory:nick_memory stress --vm  --vm-bytes  --vm-keep --verbose

stress 程序能够提供比较详细的信息,进程被杀掉的方式是收到了 SIGKILL(signal 9) 信号。

实际应用中往往要同时限制多种的资源,比如既限制 CPU 资源又限制内存资源。使用 cgexec 实现这样的用例其实很简单,直接指定多个 -g 选项就可以了:

$ cgexec -g cpu:nick_cpu -g memory:nick_memory ./cpumem

总结

cgroups 是 linux 内核提供的功能,由于牵涉的概念比较多,所以不太容易理解。本文试图在介绍概念性内容的同时,用最简单的 demo 演示 cgroups 的用法。希望直观的 demo 能够帮助大家理解 cgroups。

参考:

Linux Control Group 简介

linux cgroups 简介的更多相关文章

  1. linux cgroups简介(下)Cgroups 与 Systemd

    Cgroups 是 linux 内核提供的一种机制,如果你还不了解 cgroups,请参考前文<Linux cgroups 简介>先了解 cgroups.当 Linux 的 init 系统 ...

  2. linux cgroups简介(上)

    Linux CGroups简介 1.CGroups是什么 与Linux namespace对比来看,Linux namespace用来限制进程的运行范围或者运行环境的可见性,比如:uts限制进程读取到 ...

  3. linux 文件系统简介

    linux文件系统简介   文件系统是linux的一个十分基础的知识,同时也是学习linux的必备知识. 本文将站在一个较高的视图来了解linux的文件系统,主要包括了linux磁盘分区和目录.挂载基 ...

  4. Docker之Linux Cgroups

    Linux Cgroups介绍 上面是构建Linux容器的namespace技术,它帮进程隔离出自己单独的空间,但Docker又是怎么限制每个空间的大小,保证他们不会互相争抢呢?那么就要用到Linux ...

  5. Linux 内核简介

    Linux内核简介 一.系统架构 (1). Linux系统架构 ##用户空间: 文件系统 C库 ##内核空间: 接口 内核 (2). Linux内核架构 二.Linux内核源代码 下载地址 www.k ...

  6. Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装

    原文:Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装 Linux内核分析(一) 从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底 ...

  7. Docker 基础技术之 Linux cgroups 详解

    PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...

  8. Linux基础学习(1)--Linux系统简介

    第一章——Linux系统简介 1.UNIX和Linux发展史: 1.1 unix发展史: (1)1965年,美国麻省理工学院(MIT).通用电气公司(GE)及AT&T的贝尔实验室联合开发Mul ...

  9. Linux内核分析——第一章 Linux内核简介

    第一章   Linux内核简介 一.Unix的历史 1.Unix系统成为一个强大.健壮和稳定的操作系统的根本原因: (1)简洁 (2)在Unix中,很多东西都被当做文件对待.这种抽象使对数据和对设备的 ...

随机推荐

  1. leetcode-67.二进制求和

    leetcode-67.二进制求和 Points 数组 数学 题意 给定两个二进制字符串,返回他们的和(用二进制表示). 输入为非空字符串且只包含数字 1 和 0. 示例 1: 输入: a = &qu ...

  2. java--反射机制总结

    一.什么是Java反射机制? JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的以及动态调用对象的方法的功能 ...

  3. Python 反射机制之hasattr()、getattr()、setattr() 、delattr()函数

    反射机制 先看看我对Java中反射机制的通俗理解:反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化 ...

  4. 微信小程序中的循环遍历问题

    比如:如果在微信小程序中要遍历输出 0-9 的数,我们会使用for循环 ;i<;i++){ console.log(i); } 确实结果也是这样: 但是,如果我在循环时同时调用wx的api接口1 ...

  5. java反射笔记

    反射(reflect) 1. Class对象 1.1 什么是Class对象 当JVM加载某个class文件的时候,会自动创建一个唯一的Class对象(注意:由同一个类加载器加载的class文件),这个 ...

  6. c/c++ lambda 表达式 介绍

    lambda 表达式 介绍 问题:假设有个需求是,在vector<string>找出所有长度大于等于4的元素.标准库find_if函数的第三参数是函数指针,但是这个函数指针指向的函数只能接 ...

  7. mysql 数据库 命令行的操作——对表和字段的操作

    一.对表的操作 1.查看所有表 show tables: 2.创建表 create table 表名(字段1 类型1 约束1 ,字段2 类型2 约束2): 3.修改表的名字 rename table ...

  8. python中根据字符串导入模块module

    python中根据字符串导入模块module 需要导入importlib,使用其中的import_module方法 import importlib modname = 'datetime' date ...

  9. IPerf——网络测试工具介绍与源码解析(3)

    [线程的生成]   生成线程时需要传入一个thread_Settings类型的变量,thread_Settings包含所有线程运行时需要的信息,命令行选项参数解析后所有得到的属性都存储到该类型的变量中 ...

  10. AI学习---TensorFlow框架介绍[图+会话+张量+变量OP+API]

    TensorFlow的数据流图 TensorFlow的结构分析: 图 + 会话 TensorFlow = 构图阶段(数据与操作的执行步骤被描绘出一个图) + 执行图阶段(使用回话执行构建好的图中操作) ...