不同平台下对进程资源进行限制(CPU与内存)

不同平台下对进程资源进行限制(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, 支持的限制选项有:

使用 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: 用于设置当前进程最大的虚拟地址空间大小。单位字节。 此限制直接影响brkmmapmremap, 失败时其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, cgroupdocker等方式实现对资源的限制。 从本质上说, Systemd的配置与cgroups的配置是一套机制。 systemd依赖cgroups而实现。

posted @ 2024-08-22 15:21  汗牛充栋  阅读(44)  评论(0编辑  收藏  举报