不同平台下对进程资源进行限制(CPU与内存)
因实际工作中发现, 如果不对某些进程硬件资源进行限制, 可能某个进程会把操作系统资源耗尽, 导致操作系统死机等问题出现。
于是就想, 是否有什么方法可以限制指定进程内存使用上限, 避免其无上限申请内存。
Windows
Windows 平台可通过作业对象对进程所占资源进行限制。
作业对象允许将进程组作为一个单元进行管理。 作业对象是可访问的、安全的、可共享的对象,用于控制与其关联的进程的属性。 针对某个作业对象执行的操作会影响与该作业对象关联的所有进程。 示例包括强制实施工作集大小和进程优先级等限制,或终止与作业关联的所有进程。
创建一个作业对象
若要创建作业对象,需 CreateJobObject 函数。 创建作业时,不会与作业关联任何进程。
若要将进程与作业相关联,请使用 AssignProcessToJobObject 函数。 进程与作业关联后,无法断开关联。 一个进程可以与嵌套作业层次结构中的多个作业相关联。
如下代码展示了如何创建一个作业对象, 以及与当前进程进行关联:
// 创建 Job 对象
HANDLE hJob = CreateJobObject(nullptr, nullptr);
if (hJob == nullptr) {
std::cerr << "Failed to create Job object" << std::endl;
return 1;
}
// 此处配置作业对象
// 将当前进程加入 Job 对象
if (!AssignProcessToJobObject(hJob, GetCurrentProcess())) {
std::cerr << "Failed to assign process to Job object" << std::endl;
CloseHandle(hJob);
return 1;
}
//...
CloseHandle(hJob);
若某进程与作业对象关联后, 在该进程中调用CreateProcess创建任何子进程也将与作业进行关联。
判断某进程是否在作业中运行, 可调用IsProcessInJob得知
若要终止当前与作业对象关联的所有进程,请使用TerminateJobObject 函数。
资源限制
资源限制主要通过调用SetInformationJobObject, 支持的限制选项有:
- JOBOBJECT_BASIC_LIMIT_INFORMATION:基本限制信息, 如用户模式下执行时间限制、CPU亲和性、工作集大小等
- JOBOBJECT_BASIC_UI_RESTRICTIONS:基本用户界面限制
- JOBOBJECT_CPU_RATE_CONTROL_INFORMATION: 主要用于限制CPU
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION: 主要用于限制内存
- JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION: 通知限制的信息, 如IO读写多大时进行通知等。
使用 SP3 和 Windows Server 2003 的 Windows XP:SetInformationJobObject 函数可用于为与作业对象关联的所有进程设置安全限制。 从 Windows Vista 开始,必须为与作业对象关联的每个进程单独设置安全限制。
可通过调用
QueryInformationJobObject
获取当前有哪些限制。
内存限制示例
限制进程最大分配100MB内存:
// 设置 Job 对象限制
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
jobInfo.ProcessMemoryLimit = 100 * 1024 * 1024; // 100 MB
if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo))) {
std::cerr << "Failed to set Job object information" << std::endl;
CloseHandle(hJob);
return 1;
}
CPU限制示例
如下示例展示了如何将CPU使用率限制在20%:
JOBOBJECT_CPU_RATE_CONTROL_INFORMATION info;
info.CpuRate = 20 * 100;
info.ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP;
if (!::SetInformationJobObject(hJob, JobObjectCpuRateControlInformation, &info, sizeof(info))){
std::cerr << "Failed to set Job object information" << std::endl;
return 1;
}
不能将 CpuRate 设置为 0。 如果 CpuRate 为 0, 则SetInformationJobObject返回 INVALID_ARGS。
经实测, 限制CPU会有10%的上限幅度。
Linux
Linux 通过调用setrlimit对进程的CPU、内存等作出限制。 其效果与执行shell命令ulimit
一样。
其函数签名如下:
int setrlimit(int resource, const struct rlimit *rlim);
其中 resource可选参数如下:
- RLIMIT_AS: 用于设置当前进程最大的虚拟地址空间大小。单位字节。 此限制直接影响
brk
、mmap
及mremap
, 失败时其errno等于ENOMEM - RLIMIT_CORE:用于限制coredump file大小,若设置为0, 则不会生成该文件。
- RLIMIT_CPU: 设置的是进程能够使用的最大 CPU 时间总量(以秒为单位)。当进程的 CPU 时间总量达到软限制时,操作系统会向进程发送SIGXCPU信号。默认情况下,这会终止进程。而如果信号忽略, 继续占用CPU, 内核将继续每秒发送一次SIGXCPU信号, 直到达到硬限制, 最终触发SIGKILL。
- 其他限制参考 https://linux.die.net/man/2/setrlimit
struct rlimit结构如下:
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
内存限制示例
#include <sys/resource.h>
void setMemoryLimit(size_t limit_in_bytes) {
struct rlimit rl;
rl.rlim_cur = limit_in_bytes;
rl.rlim_max = limit_in_bytes;
if (setrlimit(RLIMIT_AS, &rl) == -1) {
fprintf(stderr, "Error setting memory limit:%s\n", strerror(errno));
}
}
CPU 限制示例
void setCPULimit(size_t second)
{
struct rlimit rl;
rl.rlim_cur = second;
rl.rlim_max = second * 3;
if (setrlimit(RLIMIT_CPU, &rl) == -1) {
fprintf(stderr, "Error setting memory limit:%s\n", strerror(errno));
}
}
当超过限制的时候触发的信号处理, 确保设置信号SA_RESTART标志位,避免触发一次后被重置为默认信号处理:
void sign_handler(int signno)
{
printf("Trigger the %s(%d) signal.\n","SIGXCPU", signno);
}
struct sigaction sa;
sa.sa_handler = sign_handler;
sigemptyset(&sa.sa_mask);
// 执行SIGINT时, 不允许被SIGQUIT中断
sigaddset(&sa.sa_mask, SIGQUIT);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGXCPU, &sa, NULL) == -1){
printf("Failed to set SIGXCPU signal handler.\n");
return 1;
}
以上CPU限制较为严格或带点强制, 达到约束将导致程序退出。 另一种相对较为友好的方式就是设置进程的nice
优先级, 它不会限制进程可以使用的 CPU 时间总量,而是影响调度程序的行为,使低优先级的进程在竞争 CPU 时间时处于劣势。
使用nice命令启动进程, 设置其nice值为10, nice值可设置范围为[-20,19]
:
nice -n 10 ./xxx_program
设置较低的优先级, 使其占用的CPU时间降低。
nice 只是对调度程序的建议,并不保证精确的 CPU 使用限制。它可能会影响一个进程的响应时间,但不会严格限制它的 CPU 时间。
其他方式
其他方式包括通过配置systemd
, cgroup
及docker
等方式实现对资源的限制。 从本质上说, Systemd的配置与cgroups的配置是一套机制。 systemd依赖cgroups而实现。