2 - Hand on system programming with Linux - 资源限制

Resource Limits 资源限制

我的博客

在本章中,我们会查看单个进程的资源限制,这些限制是什么,为什么需要这些限制。

资源限制

网络中一种常见的攻击手段是分布式拒绝服务攻击 DDos: Distributed denial of service attack,攻击者企图消耗目标系统的资源,令目标系统崩溃,或者至少令目标系统不再能够正常响应。

在一个未经优化的系统上,执行这类攻击是十分简单的,比如,假设我们可以通过普通用户身份通过 shell 访问一台服务器,我们可以使用 dd 命令耗费它的硬盘空间,dd 命令的一个用法是创建任意长度的文件。

$ dd if=/dev/urandom of=tst count=1024 bs=1M
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 15.2602 s, 70.4 MB/s
$ ls -lh tst
-rw-rw-r-- 1 kai kai 1.0G Jan 4 12:19 tst

如果我们将块大小修改为 1G,比如:

dd if=/dev/urandom of=tst count=1024 bs=1G

dd 将会尝试创建 1024 GB 大小的文件,如果我们以循环的方式执行这个命令呢!

为了控制资源使用,类 UNIX 系统都具有资源限制,一个由操作系统提供的由使用者设置的资源限制。在进行介绍之前,需要声明,这些资源限制是对每一个进程的,并不针对系统全局。在进行更细致的介绍之前,我们回到吃掉系统盘空间的攻击手段,只是这次系统设置了资源限制。

查看并设置资源限制可以由 shell 命令 ulimit 实现,使用 -f 选项询问最大文件限制:

$ ulimit -f
unlimited

这里显式无限制,当然实际并非如此,这里只是说明操作系统并没有做出对应的限制,但是实际的物理可用盘空间限制了实际的应用。

让我们设置一个文件大小限制,只需要向 -f 选项传递一个参数,可以查看 ulimit 的 man 帮助页面查看具体的使用方式。当我们设置 ulimit -f 2 时,实际设置的文件大小为 2048 字节的限制,我们再次使用 dd 测试:

$ ulimit -f
unlimited
$ ulimit -f 2
$ ulimit -f
2
$ dd if=/dev/urandom of=tst count=2048 bs=1
2048+0 records in
2048+0 records out
2048 bytes (2.0 KB, 2.0 KiB) copied, 0.00688134 s, 298 kB/s
$ dd if=/dev/urandom of=tst count=2049 bs=1
File size limit exceeded (core dumped)

资源限制范围

在前面的 dd 例子中,我们可以设置最大文件大小,那么问题是,这个资源限制的范围是什么,这个限制是否是整个系统的限制?答案是简单的:不是,它并不是全系统有效的,而只是进程级别的。考虑有两个 shell,它们是两个 bash 进程,shell A 与 shell B。我们在 shell A 中进行了文件大小的资源限制配置,但是没有在 shell B 中执行对应的资源限制命令。我们按照上一章中的命令执行,会发现在 shell A 中会有资源限制的失败提示,但在 shell B 中只要资源够用,没有这样的失败提示。

资源类型

可用资源限制

资源限制 ulimit 选项 默认值 单位
最大核心文件大小 -c 无限制 KB
最大数据段大小 -d 无限制 KB
最大调度优先级(nice) -e 0
最大文件大小 -f 无限制 KB
最大待处理信号 -i 可变
最大锁定内存 -l 可变 KB
最大内存大小 -m 无限制 KB
最多打开文件 -n 1024
最大管道大小 -p 8 512 字节
最大 POSIX 消息队列 -q 可变
最大实时调度优先级 -r 0
最大栈段大小 -s 8192 KB
最大 CPU 时间 -t 无限制
最多用户进程 -u 可变
地址空间限制/最大虚拟内存 -v 无限制 KB
最多文件锁定持有 -x 无限制

上面表格中的第三列信息中的可变,表示实际的值是基于系统资源的,比如 RAM 大小等。

使用 ulimit -a 可以列出所有的限制项。需要注意的是,这些限制都是限制在本 bash 进程的。

软/硬限制

类 UNIX 系统做了更深度的区分,资源限制有两类:

  • 硬限制值
  • 软限制值

硬限制是真实的最大值,使用时不能超过这个值,如果进程企图超过这个限制,将会被操作系统杀死。而软限制,处理方式则不同,当发生超限情况时,内核会向进程发送一个信号,我们可以将这个信号视作是一种警告,比如当进程企图超过软限制的文件大小,操作系统将会向它发送 SIGXFSZ: SIGnal: eXceeding FileSize 信号,如果进程企图超过 CPU 资源软限制,那么它将会收到 SIGXCPU 信号,当操作系统向进程发送多次 SIGXCPU 后,操作系统将会向它发送最后通牒 SIGKILL 杀死这个进程。

我们可以将硬限制视作是装着软限制的小盒子,软限制的范围就是 (0 - 应限制)。如果希望同时查看 shell 进程的软限制与硬限制,可以使用 -S 选项查看软限制以及 -H 选项查看硬限制,下面是我的 Ubuntu 的软资源限制:

$ ulimit -aS
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 126981
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 126981
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

在典型 Linux 系统上,硬限制与软限制只有两处区别:最多打开的文件数量,最大栈大小。

询问并修改资源限制值

我们现在理解了,是操作系统内核设置了每一个进程的资源限制,并追踪进程的资源使用情况,并在必要时杀死进程。

任何人都可以询问进程拥有的硬软限制。但是在设置时,对于硬限制,一旦设定该会话下不能被增加,而软限制则只能设置在 0 到硬限制之间,当使用 ulimit 设置资源限制时,系统内部会同时设定硬限制与软限制。

设置资源限制的权限给定如下:

  • 具有特权的进程(比如 superuser、root、sysadmin 或者其他具有 CAP_SYS_RESOURCE 位的用户),可以同时修改硬限制与软限制

  • 普通用户

    • 软限制可以在 0 到硬限制之间调整
    • 硬限制则只能降低,但是一旦降低就不能再增加

    但凡事都有特例,一名普通用户可以修改 core file 的资源限制,这通常被开发者用来生成 core dump。

警告

设置资源限制到较高的值,需要特权。比如我们希望修改打开文件的限制,将其从 1000 修改到 2000,我们可能会想使用 sudo 来完成这个工作,但是执行 sudo ulimit -n 2000 并不能完成我们的任务,这是因为 sudo 期望 ulimit 是一个二进制可执行文件,因此会在 PATH 中搜索这个工具,但是 ulimit 实际上是 shell 内建命令,因此并不能被搜索到,需要按照下面这种方式执行:

$ ulimit -n
1024
$ sudo bash -c "ulimit -n 2000 && exec ulimit -n"
[sudo] password for arv:
2000

需要注意的是,我们不能使用 ulimit 修改 pipe 资源限制,这个资源限制是在 /proc/sys/fs/pipe-max-size 中规定的,如果希望修改这一限制,需要使用 fcntl(2) 系统调用,通过 F_GETPIPE_SZF_SETPIPE_SZ 参数修改。

prlimit 工具

除了使用 ulimit,另一个查询展示资源限制的工具是 prlimitprlimitulimit 主要有下面几点不同:

  • 它是更新的工具,支持 2.6.36 以上 Linux 内核版本
  • 它可以按需修改资源限制,并载入另一个程序将会继承这个限制
  • 这是一个二进制可执行工具,而不是像 ulimi 那样是 shell 内建命令

如果不给参数,prilimit 将会展示它自己的所有资源限制,用户可以传递 <name = value> 参数对来设置查询的资源,给定进程的 PID 来指定要查询的进程资源限制,或给定一个要加载的命令,设置新的资源限制:

prlimit [options] [--resource[=limits]] [--pid PID]
prlimit [options] [--resource[=limits]] command [argument...]
prlimit 用例

用例 1,查询限制:

$ prlimit
$ prlimit --pid=2917

用例 2,设置进程最大文件与栈大小:

$ prlimit --pid=2917 --fsize=2048000 --stack=12582912

用例 3,以给定的资源限制执行一个程序,比如 rlimit_primes (程序源码),程序用来生成质数,我们可以让它在规定 CPU 时间内生成大的质数:

$ ./rlimit_primes
Usage: ./rlimit_primes limit-to-generate-primes-upto CPU-time-
limit
arg1 : max is 10000000
arg2 : CPU-time-limit:
-2 = don't set
-1 = unlimited
0 = 1s
$ prlimit --cpu=2 ./rlimit_primes 8000000 -2
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131,
[...]
18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427,
18433, 18439,
18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517,
18521, 18523,
18539, 18541, 18553, 18583, 18587, 18593,
Killed

可以看到,当它消耗的资源超过了给它设定的 CPU 时间资源限制,它被内核杀死。上面 Killed 打印表示进程已经被操作系统杀死。

在 2.6.24 以上的内核版本,我们查看 /proc//limits 中给出的资源限制信息。

API 接口

查询、设置资源限制的编程,可以通过如下系统调用接口实现:

  • getrlimit
  • setrlimit
  • prlimit

这里我们仅关注 prlimit(2) 接口,为了令 prlimit(2) 能够正常工作,使用的 Linux 版本需要高于 2.6.36。可以使用 uname 命令查看自己的内核版本。

我们可以查看下 prlimit 系统调用接口:

#include <sys/resource.h>
int prlimit(pid_t pid, int resource,
           const struct rlimit *new_limit, struct rlimit *old_limit);

prlimit 系统调用可以用来查询、设置资源限制,每次调用只能够修改一个进程的一个资源限制。它接收四个参数,第一个参数为进程的 PID,如果参数为 0,那么这次调用是针对本进程的;第二个参数是我们要查询或设置的限制资源的名称;第三个参数与第四个参数都是 struct rlimit 指针,第三个参数如果非空,是我们要设置的新值,第四个参数如果非空,是用来接收当前限制的空间。

rlimit 结构体包含两个成员,分别表示软硬限制:

struct rlimit {
    rlimit_t rlim_cur; /* Soft limit */
    rlimit_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};

prlimit 的第二个参数,可以用下面的枚举表表示:

资源限制 枚举名称 默认值 单位
最大核心文件大小 RLIMIT_CORE unlimited KB
最大数据段大小 RLIMIT_DATA unlimited KB
最大可调度优先级 RLIMIT_NICE 0
最大文件大小 RLIMIT_FSIZE unlimited KB
最大代处理信号 RLIMIT_SIGPENDING 可变
最大锁定内存 RLIMIT_MEMLOCK 可变 KB
最多打开文件 RLIMIT_NOFILE 1024
最大 POSIX 消息队列 RLIMIT_MSGQUEUE 可变
最大实时调度优先级 RLIMIT_RTTIME 0 微妙
最大栈段大小 RLIMIT_STACK 8192 KB
最大 CPU 时间 RLIMIT_CPU unlimited
最大用户进程 RLIMIT_NPROC 可变
地址空间限制或最大虚拟内存 RLIMIT_AS unlimited KB
最多文件锁定持有 RLIMIT_LOCKS unlimited
posted @ 2023-05-13 17:26  ArvinDu  阅读(73)  评论(0编辑  收藏  举报