cgroup限制内存

2023-09-16 22:26:08

首先简单介绍下cgroup限制cpu的使用率,写一段代码如下:

#include <stdio.h>
#include <pthread.h>


int main()
{
int i = 0;
for(;;)i++;
return 0;
}

很明显,这里面是单核拉满,然后top看下进程的cpu使用率,如下所示:
在这里插入图片描述
很明显,截图中的cpu使用率是正常的,现在限制cpu的使用率。
进入到目录/sys/fs/cgroup/cpu,创建目录cputest,进入到cputest,执行命令:
echo 200000 > cpu.cfs_quota_us
echo 1000000 > cpu.cfs_period_us
echo 25120 > tasks

这三句话中,前两句是限制cpu使用率为20%,第三句是设置进程,设置后的截图如下:
在这里插入图片描述
很明显,截图符合咱们的预期。

现在来限制内存:
写代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int i = 0;
int size = 500 * 1024 * 1024;
char *p = (char *)malloc(size);
memset(p, 0, size);
while(1)
{
for(i = 0; i < size; i++)
p[i]=i;
sleep(1);
}
return 0;
}

运行效果如下,很明显,占用物理内存大概为500M
在这里插入图片描述

现在限制该进程使用的物理内存为20M,进入到目录/sys/fs/cgroup/memory,创建目录memtest,进入到该目录。执行下列命令:
echo 20M > memory.limit_in_bytes
echo 18604 > tasks
这两句中,第一句是设置内存限制为20M,第二个是设置进程号。此时再看top截图,如下所示:
在这里插入图片描述
可以看到物理内存依然是500M的样子,没有减少,这跟想象中完全不一样,然后排查了几个小时,没有进展,后面尝试先在cgroup中设置进程的内存限制,再使进程开辟内存,代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int i = 0;
int size = 500 * 1024 * 1024;
sleep(60);
char *p = (char *)malloc(size);
memset(p, 0, size);
while(1)
{
for(i = 0; i < size; i++)
p[i]=i;
sleep(1);
}
return 0;
}

可以看到,这里面在开辟内存之前,sleep 60秒,为此,咱们需要在这60秒之内,将进程id设置到cgroup中。
运行程序,产生进程号18837,执行命令echo 18837 > tasks,然后等待60秒结束,top信息如下所示:
在这里插入图片描述
很明显,这次有效果,占用的物理内存接近20M,咱们在top界面按下f,将swap选中,再次截图如下:
在这里插入图片描述
此时看到SWAP内存为480M多点,这个结果完全符合咱们的期望。
以上表明,在设置进程的内存限制时,要在进程尚未开辟物理内存时设置,一旦进程开辟了很多的物理内存,再限制物理内存,使得物理内存使用量下降,已经为时已晚。

咱们再考虑多进程的情况,正常来说多进程架构比较稳,一个服务程序可能产生多个进程,而我们想控制的是这个服务程序总的内存使用量,即其产生的所有子进程的内存占用之和不要超过某个值,这该如何设置。
写一个多进程应用,代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int i = 0;
int size = 500 * 1024 * 1024;
int childpid = 0;
char *p = NULL;
sleep(60);
childpid = fork();

p = (char *)malloc(size);
memset(p, 0, size);
while(1)
{
for(i = 0; i < size; i++)
p[i]=i;
sleep(1);
}
return 0;
}

可以看出,这里面创建了一个子进程,父进程和子进程都占用了500M内存,咱们等到60s后,执行top命令:
在这里插入图片描述
这里咱们未设置内存限制,情况完全正确。
现在限制这个应用程序(两个进程)的内存限制为20M。

重新运行该程序,查看进程号19920,注意代码中的fork在sleep之后,故而此时只有一个进程号, 在60s内。
echo 19920 > tasks。
60s之后,子进程产生了,top命令截图如下:
在这里插入图片描述
可以看到这两个进程占用的物理内存加起来大概20M。
但是咱们刚才只将父进程的id 19920设置到tasks文件中。
我们现在看下这个tasks文件里面的内容,截图如下:
在这里插入图片描述
可以看到,这里面有两个进程号,但是我只设置了19920到tasks中,另外一个是cgroup内部设置进去。
查原因,发现是有个进程组的概念,将进程组的首id设置进去,后续产生的进程id都会被cgroup设置进去。

截图如下,可以查看进程组,注意我敲的是两个命令,可以看出19931不是一个进程组,19920既是进程组id,也是该进程组的第一个进程id。子进程默认会继承父进程的进程组id。
在这里插入图片描述

现在再考虑下应用程序里面有多个进程组的情况,默认情况下子进程会继承父进程的进程组id。但是子进程的进程组id也可以进行修改,代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int i = 0;
int size = 500 * 1024 * 1024;
int childpid = 0;
char *p = NULL;
sleep(60);
childpid = fork();
if(childpid==0)
{
setpgid(0, getpid());
}
p = (char *)malloc(size);
memset(p, 0, size);
while(1)
{
for(i = 0; i < size; i++)
p[i]=i;
sleep(1);
}
return 0;
}

代码中,子进程的逻辑块中,用setpgid设置其进程组id为其自身id,在sleep 60秒内,产生的进程id是20098,然后执行命令: echo 20098 > tasks

过60秒后,查看进程如下:
在这里插入图片描述
可以看到,此时有两个进程组,top截图如下:
在这里插入图片描述
可以看出,两个进程的物理内存之和大概是20M,由此推断就算父进程和子进程是不同的进程组,进程限制也对整个应用程序有效。

我们再查看下tasks里面的内容:
在这里插入图片描述
发现父子进程就算是位于不同的进程组,只要设置第一个进程id进去,cgroup会自动将子进程的id设置进去。

其实笔者在更复杂的环境中测试过,一个应用程序产生大量的进程,也是这些所有的进程的内存加起来,大致满足cgroup的受限条件,而这些进程id也会被写入到tasks中。

注意,我这里说的是所有的进程的内存加起来,大致满足,有个大致,其实我遇到过所有子进程内存加起来,超过设置的20M的情况,但是超出的不多,大概超出2M。具体原因未能明白。

更多推荐

【UML】软件工程中常用图:类图、部署图、时序图、状态图

作者简介:前言:UML中的很多东西平时都听过、用过,诸如类图、时序图等,本文将详细详细讲一下UML中常用的几类图,并且会引入一个完整的例子来讲解,UML在工程上到底该怎么合理使用。目录1.概述1.1.什么是UML?1.2.UML用在何处?2.静态结构2.1.类图2.2.部署图3.描述动态3.1.顺序图3.2.状态图4.

【UML】详解UML类图

目录1.概述2.权限3.关系3.1.连线关系3.2.依赖3.3.泛化(继承)3.4.实现3.5.关联3.6.聚合3.7.组合1.概述UML是什么?书面化一点的说法是:UML(UnifiedModelingLanguage),统一建模语言,是一种用于软件工程和系统设计的标准图形化建模语言。它旨在帮助开发人员、设计师和分析

8年经验之谈 —— App测试常用的两种工具

一、监控工具DDMS的全称是DalvikDebugMonitorService,是Android开发环境中的Dalvik虚拟机调试监控服务。提供测试设备截屏、查看特定进程正在运行的线程以及堆信息、Logcat、广播状态信息、模拟电话呼叫、模拟接收及发送SMS、虚拟地理坐标等服务。启动DDMSEclipse中启动方法:1

Java 泛型

一、泛型简介Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。下面给一个Java泛型的简单例子:publicstatic<E>voidprintArray(E[]in

【产品运营】如何提升B端产品竞争力(下)

“好产品不是能力内核,做好产品的流程才是”一、建立需求池和需求反馈渠道需求池管理是B端产品进化最重要的环节,它的重要性远超产品设计、开发等其他环节。维护需求池有主动和被动两种。主动维护是产品经理在参与售前、迭代、交付、售后、竞品分析、老板沟通等活动时自己发掘、记录需求,主动收集需求的渠道很多,但不同渠道收集到的需求质量

两届 TOKEN 2049 之间,孙宇晨和波场的布局与野心

2022年在新加坡举办的TOKEN2049大会上,波场TRON创始人、火币全球顾问委员会成员孙宇晨作为特邀嘉宾出席,并曾提出“波场TRON下一步的发展目标是成为主流金融机构”的生态愿景,揭示了波场生态的全新发展方向,以及孙宇晨作为区块链和加密技术的长期布道者,对加密行业未来发展趋势的展望。时隔一年,2023TOKEN2

Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单

项目说明随着公司的快速发展,企业人员和经营规模不断壮大,公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境,最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范,以及审计监督要求;通过电子化平台提高招投标工作的公开性和透明性;通过电子化招投标,使得招标采购的质量更高、速度

Typescript中的逆变与协变

许多不是很熟悉TS的朋友对于逆变和协变的概念会感到莫名的恐惧,没关系。它们仅仅代表阐述表现的概念而已,放心我们并不会从概念入手而是通过实例来逐步为你揭开它的面纱。逆变(函数入参)首先,我们先来思考这样一个场景:leta!:{a:string;b:number};letb!:{a:string};b=a我们都清楚TS属于

时序数据库 TimescaleDB 安装与使用

TimescaleDB是一个时间序列数据库,建立在PostgreSQL之上。然而,不仅如此,它还是时间序列的关系数据库。使用TimescaleDB的开发人员将受益于专门构建的时间序列数据库以及经典的关系数据库(PostgreSQL),所有这些都具有完整的SQL支持。本文介绍TimescaleDB的CentOS7环境源码

关于RISC-V安全性的全面综述

目录摘要引言RISC-V安全综述通用平台的安全要求信任的根源与硬件安全模块OTP管理模块安全内存对称加密(如AES)引擎不对称加密[131](例如,公钥RSA)引擎HASH/HAMC引擎随机数/位生成(例如TRNG[136])引擎TrustedBootServicesRISC-V体系结构安全性的构建块RISC-V体系结

RISC-V Reader 笔记(六)RV32V

RV32V早期并行数据计算:采用SIMD单指令多数据,把一个64位宽寄存器拆成若干个32168位长度部分并行计算。这种方法前期看起来十分简单诱人。但是后来如果要扩展SIMD寄存器宽度,也要复杂化指令集,复杂开销越来越大。向量操作:把数据取出来放入长长的向量寄存器中,流水线并行运算后从向量寄存器中分别取回到内存。并且,时

热文推荐