限制单个 Linux 进程的内存使用

Ben*_*lts 207 linux memory ulimit

我正在pdftoppm将用户提供的 PDF 转换为 300DPI 图像。这很好用,除非用户提供页面非常大的 PDF。 pdftoppm将分配足够的内存来在内存中保存该大小的 300DPI 图像,对于 100 英寸的方形页面,它是 100*300 * 100*300 * 4 字节/像素 = 3.5GB。恶意用户可以给我一个愚蠢的大 PDF 并导致各种问题。

所以我想做的是对我即将运行的子进程的内存使用设置某种硬限制——如果它试图分配超过 500MB 的内存,就让进程死掉。那可能吗?

我不认为 ulimit 可以用于此,但是否有一个等价的进程?

小智 160

另一种限制这种情况的方法是使用 Linux 的控制组。如果您想将一个进程(或一组进程)的物理内存分配与虚拟内存区分开来,这将特别有用。例如:

cgcreate -g memory:myGroup
echo 500M > /sys/fs/cgroup/memory/myGroup/memory.limit_in_bytes
echo 5G > /sys/fs/cgroup/memory/myGroup/memory.memsw.limit_in_bytes
Run Code Online (Sandbox Code Playgroud)

将创建一个名为 的控制组myGroup,限制在myGroup最多 500 MB 的物理内存memory.limit_in_bytes和最多 5000 MB 的物理内存和交换内存下运行的一组进程memory.memsw.limit_in_bytes。有关这些选项的更多信息,请访问:https : //access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-memory

要在控制组下运行进程:

cgexec -g memory:myGroup pdftoppm
Run Code Online (Sandbox Code Playgroud)

请注意,在现代 Ubuntu 发行版上,此示例需要安装cgroup-bin软件包并编辑/etc/default/grub以更改GRUB_CMDLINE_LINUX_DEFAULT为:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"
Run Code Online (Sandbox Code Playgroud)

然后运行sudo update-grub并重新启动以使用新的内核启动参数启动。

  • `firejail` 程序还可以让你启动一个有内存限制的进程(使用 cgroups 和命名空间来限制更多的内存)。在我的系统上,我不必更改内核命令行即可使其正常工作! (4认同)
  • 在此答案中注意,在某些发行版(例如 Ubuntu)上, cgcreate 需要 sudo 以及后面的命令,除非授予当前用户许可,这将很有用。这将使读者不必在其他地方找到这些信息(例如 https://askubuntu.com/questions/345055)。我建议对此进行编辑,但被拒绝了。 (4认同)
  • 您是否需要修改“GRUB_CMDLINE_LINUX_DEFAULT”以使设置持久化?我找到了另一种使其持久化的方法 [here](http://www.fernandoalmeida.net/blog/how-to-limit-cpu-and-memory-usage-with-cgroups-on-debian-ubuntu/)。 (3认同)
  • 在 Ubuntu 22.04 中,“/sys/fs/cgroup/memory”目录不存在,但使用“sudo cgcreate -g memory:myGroup”创建 cgroup 会创建一个名为“/sys/fs/cgroup/myGroup”的目录。但是,该目录没有“memory.limit_in_bytes”文件,并且不可写,即使是超级用户也是如此。它包含的文件似乎是_报告_有关 cgroup 中使用的资源的统计信息,但我没有看到任何控制它们的内容。我可以在这个 cgroup 中使用“cgexec”进程(作为超级用户),并且我看到峰值内存使用量增加。这些说明是以 RedHat 为中心的吗? (3认同)
  • 另请参阅:[GRUB_CMDLINE_LINUX_DEFAULT 中的`swapaccount=1` 有什么作用?](https://unix.stackexchange.com/q/531480/4784) (2认同)
  • 似乎现代 ubuntu 需要 cgroup-tools 而不是 cgroup-bin (2认同)

小智 92

如果您的进程没有产生更多消耗最多内存的子进程,则可以使用setrlimit函数。更常见的用户界面是使用ulimitshell 命令:

$ ulimit -Sv 500000     # Set ~500 mb limit
$ pdftoppm ...
Run Code Online (Sandbox Code Playgroud)

这只会限制进程的“虚拟”内存,考虑并限制被调用的进程与其他进程共享的内存,以及映射但未保留的内存(例如,Java 的大堆)。尽管如此,虚拟内存是增长非常大的进程的最接近的近似值,使得所述错误无关紧要。

如果您的程序产生子进程,并且由它们分配内存,则它变得更加复杂,您应该编写辅助脚本来在您的控制下运行进程。我在我的博客中写道,为什么以及如何

  • 因为内核不会对所有子进程的 vm 大小求和;如果这样做了,无论如何都会得到错误的答案。限制是每个进程的,是虚拟地址空间,而不是内存使用。内存使用更难衡量。 (10认同)
  • 为什么`setr​​limit`对于更多的孩子来说更复杂?`man setrlimit` 告诉我“通过 fork(2) 创建的子进程继承其父进程的资源限制。资源限制在 execve(2) 中保留” (3认同)
  • 只是想说声谢谢 - 这种 `ulimit` 方法帮助我解决了 `firefox` 的 [bug 622816 – 加载大图像可以“冻结”firefox,或使系统崩溃](https://bugzilla.mozilla.org/show_bug .cgi?id=622816); 这在 USB 启动(从 RAM)上往往会冻结操作系统,需要硬重启;现在至少`firefox` 自己崩溃了,让操作系统还活着......干杯! (2认同)
  • 软限制和硬限制是什么? (2认同)

kvz*_*kvz 76

ulimit 有一些问题。这是有关该主题的有用阅读:限制 Linux 中程序的时间和内存消耗,这导致了超时工具,它可以让您通过时间或内存消耗来控制进程(及其分支)。

超时工具需要 Perl 5+ 和已/proc挂载的文件系统。之后,您将工具复制到例如/usr/local/bin

curl https://raw.githubusercontent.com/pshved/timeout/master/timeout | \
  sudo tee /usr/local/bin/timeout && sudo chmod 755 /usr/local/bin/timeout
Run Code Online (Sandbox Code Playgroud)

之后,您可以像在您的问题中一样通过内存消耗来“控制”您的进程,如下所示:

timeout -m 500 pdftoppm Sample.pdf
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用-t <seconds>-x <hertz>分别通过时间或 CPU 限制来限制进程。

该工具的工作方式是每秒多次检查生成的进程是否没有超额订阅其设置的边界。这意味着实际上有一个小窗口,一个进程可能会在超时通知和终止进程之前过度订阅。

因此,更正确的方法可能涉及 cgroups,但设置起来要复杂得多,即使您使用 Docker 或 runC,其中提供了对 cgroups 更用户友好的抽象。

  • 应该注意的是,这个答案并不是指同名的 linux 标准 `coreutils` 实用程序!因此,如果您系统上的任何地方都有一个脚本,期望 `timeout` 成为 linux 标准的 `coreutils` 包,那么这个答案就有潜在的危险!我不知道这个工具被打包用于诸如 debian 之类的发行版。 (19认同)

Hi-*_*gel 64

在任何基于 systemd 的发行版上,您还可以通过 systemd-run 间接使用 cgroup。例如,对于限制pdftoppm为 500M RAM 的情况,请使用:

systemd-run --scope -p MemoryMax=500M pdftoppm
Run Code Online (Sandbox Code Playgroud)

注意:这会要求您输入密码,但应用程序会以您的用户身份启动。不要让这让您误以为命令需要sudo,因为这会导致命令在 root 下运行,这不是您的本意。

如果您不想输入密码(实际上,为什么需要密码来限制您已经拥有的内存),您可以使用--user选项,但是要使其工作,您将需要启用 cgroupsv2 支持,现在需要启动与systemd.unified_cgroup_hierarchy内核参数

  • 另一个注意事项:这是在幕后使用 `cgroups`,我认为使用此命令执行此操作更有意义,而不是像其他答案中建议的那样手动摆弄 `cgroups`。这应该有更多的赞成票。 (6认同)
  • 谢谢你,让我开心 (3认同)
  • 值得注意的是:如果你使用了 `--user` 选项但没有启用 cgroupsv2(这在许多发行版中仍然是默认设置),systemd-run 将无法设置内存限制,但不会抛出任何错误。[Github 问题](https://github.com/systemd/systemd/issues/13347) (2认同)
  • 我刚刚重新测试,它似乎确实有效,C hello world 被 `systemd-run --scope -p MemoryMax=1 --user ./hello.out` 或 `1K` 杀死,但运行良好`systemd-run --scope -p MemoryMax=1M --user ./hello.out`。和`sudo dmesg | less` 显示 CLI:`命令行:BOOT_IMAGE=/BOOT/ubuntu_uvs1fq@/vmlinuz-5.15.0-27-generic root=ZFS=rpool/ROOT/ubuntu_uvs1fq ro`。顺便说一句,我也在这里收到了通知:-) (2认同)

Jan*_*nis 12

我正在使用以下脚本,效果很好。它通过cgmanager. 更新:它现在使用来自cgroup-tools. 命名这个脚本limitmem并将它放在你的 $PATH 中,你可以像limitmem 100M bash. 这将限制内存和交换使用。为了仅限制内存,请删除带有memory.memsw.limit_in_bytes.

编辑:在默认的 Linux 安装中,这只会限制内存使用,而不是交换使用。要启用交换使用限制,您需要在 Linux 系统上启用交换记帐。通过设置/添加swapaccount=1/etc/default/grub做到这一点,它看起来像

GRUB_CMDLINE_LINUX="swapaccount=1"
Run Code Online (Sandbox Code Playgroud)

然后运行sudo update-grub并重新启动。

免责声明:如果cgroup-tools将来也会中断,我不会感到惊讶。正确的解决方案是使用 systemd api 进行 cgroup 管理,但该 atm 没有命令行工具

GRUB_CMDLINE_LINUX="swapaccount=1"
Run Code Online (Sandbox Code Playgroud)


oz1*_*123 7

除了daemontoolsMark Johnson 建议的来自 的工具,您还可以考虑chpstrunit. Runit 本身捆绑在 中busybox,因此您可能已经安装了它。

手册页chpst显示了该选项:

-m 字节限制内存。将每个进程的数据段、堆栈段、锁定的物理页和所有段的总数限制为每个字节字节。


Cir*_*郝海东 5

cgroupsv2 更新 (Ubuntu 22.04)

事情发生了一些变化。与https://unix.stackexchange.com/a/125024/32558相比,现在您需要:

sudo cgcreate -a $USER:$USER -g memory:myGroup -t $USER:$USER
sudo cgset -r memory.max=500M myGroup
sudo cgset -r memory.swap.max=0 myGroup
sudo chmod o+w /sys/fs/cgroup/cgroup.procs
cgexec -g memory:myGroup mycmd arg0 arg1
Run Code Online (Sandbox Code Playgroud)

该行:

sudo chmod o+w /sys/fs/cgroup/cgroup.procs
Run Code Online (Sandbox Code Playgroud)

它需要在没有以下情况下工作sudohttps://askubuntu.com/questions/1406329/how-to-run-cgexec-without-sudo-as-current-user-on-ubuntu-22-04-with-cgroups- v2/1450845#1450845否则会失败并显示:

cgroup change of group failed
Run Code Online (Sandbox Code Playgroud)

如果您不运行该命令,您还可以使用:

sudo cgexec -g memory:myGroup mycmd arg0 arg1
Run Code Online (Sandbox Code Playgroud)

但随后运行为root,您通常不希望它这样做,这可以使用 进行测试sudo cgexec -g memory:myGroup id

与最初用于 v1 的https://unix.stackexchange.com/a/125024/32558相比:

  • /sys/fs/cgroup/memory/myGroup/memory.limit_in_bytes就是现在/sys/fs/cgroup/myGroup/memory.max
  • /sys/fs/cgroup/memory/myGroup/memory.memsw.limit_in_bytes被分割了,现在你只需单独设置交换/sys/fs/cgroup/myGroup/memory.swap.max而不是总和

然而,对于内存的具体情况,只需使用https://unix.stackexchange.com/a/536046/32558systemd-run中提到的方法即可,该方法刚刚起作用,并且是迄今为止最简单的方法。

测试一下

malloc_touch.c

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    size_t nbytes, step;
    if (argc > 1) {
        nbytes = strtoull(argv[1], NULL, 0);
    } else {
        nbytes = 0x10;
    }
    if (argc > 2) {
        step = strtoull(argv[2], NULL, 0);
    } else {
        step = 1;
    }

    char *base = malloc(nbytes);
    assert(base);
    char *i = base;
    while (i < base + nbytes) {
        *i = 13;
        i += step;
    }
    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

GitHub 上游.

首先我们发现这1M大约是 C hello world 运行的最低限度:

sudo cgset -r memory.max=1M myGroup
cgexec -g memory:myGroup ./malloc_touch.out
Run Code Online (Sandbox Code Playgroud)

然后从那里开始我们可以尝试 malloc 100k:

sudo cgset -r memory.max=1M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 100000
Run Code Online (Sandbox Code Playgroud)

好吧,还剩下一些。那么如果尝试 1M:

sudo cgset -r memory.max=1M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 1000000
Run Code Online (Sandbox Code Playgroud)

果然被杀了。将限制增加到 10M:

sudo cgset -r memory.max=10M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 1000000
Run Code Online (Sandbox Code Playgroud)

好的。Malloc 9M:

sudo cgset -r memory.max=10M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 9000000
Run Code Online (Sandbox Code Playgroud)

好的。Malloc 10M:

sudo cgset -r memory.max=10M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 10000000
Run Code Online (Sandbox Code Playgroud)

好的。嗯,不知道为什么,预计它会死。MiB 与 MB?尝试11M

sudo cgset -r memory.max=10M myGroup
cgexec -g memory:myGroup ./malloc_touch.out 11000000
Run Code Online (Sandbox Code Playgroud)

果然被杀了。

在 Ubuntu 22.10 上测试。

cgcreate 在 Ubuntu 21.10 上完全损坏

https://askubuntu.com/questions/1376093/is-cgroup-tools-using-cgroup-v1-or-v2

失败:

cgcreate: libcgroup initialization failed: Cgroup is not mounted
Run Code Online (Sandbox Code Playgroud)

显然他们将系统的一部分移至 v2,但没有移至另一部分。