在真正启动工作负载的进程中设置文件上限
shell 内建的 ulimit 改变的是该 shell 及其随后启动进程的资源限制。对于文件描述符,RLIMIT_NOFILE 包含当前生效的软值与硬性上界。当终端显示很宽松的数字,独立受管服务却仍然无法打开文件或接受连接时,关键往往在于这些值从哪里继承。只知道某个命令可以调大数字,并不足以证明改到了真正执行工作负载的进程。 坏掉的样子 软值通常约束新的描述符分配。普通进程可以在现有硬上限内提高软值,也可以降低硬值;提高硬值则需要相应权限,而且仍然受内核约束。因此,启动时继承的硬值太低,可能使应用无法设置自己打算使用的软值。不能只检查应用配置写了多少,还要核对它是否真正有能力应用那个数字,以及失败有没有被正确处理。 子进程修改自身限制,不会改变启动它的父进程。同样,在新终端里执行命令,也不会追溯配置已经由 systemd 启动的守护进程。服务实际父进程和启动配置决定其初始值;shell 配置文件只有在真实启动路径读取并应用时才有效。把交互终端的状态当成机器上所有进程的共同状态,会让排障从一开始就观察错对象。 Bash 的选项选择也需要注意。读取 ulimit -n 通常显示软值,但赋值时如果没有选择 -S 或 -H,会同时修改软硬两项。只想暂时降低软限制的操作者,可能顺带降低硬上限,随后无法再以普通权限恢复。应依据具体 shell 的文档理解语义,不能把几条长得相似的命令当成完全等价。 快速验证 在装有 Bash 和 Python 的一次性 Linux 容器中,以管理员身份运行下面的脚本。它通过一个普通数字身份执行实验,管理员控制进程的限制保持不变。实测环境为 Linux 6.12.76、Bash 5.2.15、Python 3.11.2,没有修改任何既有账号配置或服务单元: import resource,subprocess before=resource.getrlimit(resource.RLIMIT_NOFILE) assert before[0]>=64 and before[1]>=64 script=r''' set -eu ulimit -S -n 32 ulimit -H -n 64 python3 - <<'CHILD' import resource assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64) resource.setrlimit(resource.RLIMIT_NOFILE,(48,64)) assert resource.getrlimit(resource.RLIMIT_NOFILE)==(48,64) print('first child inherits 32/64 and raises its own soft limit to 48') CHILD python3 - <<'CHILD' import resource assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64) print('second child still inherits shell values 32/64') CHILD if ulimit -S -n 65 2>/dev/null; then exit 11; fi if ulimit -H -n 128 2>/dev/null; then exit 12; fi ( ulimit -n 16 python3 - <<'CHILD' import resource assert resource.getrlimit(resource.RLIMIT_NOFILE)==(16,16) print('Bash assignment without -S/-H changed both limits in this subshell') CHILD ) python3 - <<'CHILD' import resource assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64) print('parent shell limits survived child changes and rejected invalid increases') CHILD ''' # Run the experiment under an unprivileged numeric identity in this disposable container. result=subprocess.run(['bash','--noprofile','--norc','-c',script],user=61001,group=61001,extra_groups=[], env={'PATH':'/usr/bin:/bin','LC_ALL':'C'},capture_output=True,text=True,timeout=10) assert result.returncode==0,(result.returncode,result.stderr) assert resource.getrlimit(resource.RLIMIT_NOFILE)==before print(result.stdout,end='') print('administrator controller limits remain unchanged') 测试 shell 先明确设置软值三十二、硬值六十四。它启动的第一个 Python 子进程继承这组值,然后在硬值保持六十四的情况下,成功把自己的软值提高到四十八。这展示的是现有上界内允许的调整,不需要子进程拥有管理员权限,也不等于它可以绕过硬限制任意扩大资源范围。 ...