<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>限制 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E9%99%90%E5%88%B6/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E9%99%90%E5%88%B6/index.xml" rel="self" type="application/rss+xml"/><item><title>在真正启动工作负载的进程中设置文件上限</title><link>https://www.xbcatm.com/zh/posts/linux-ulimit-file-descriptors/</link><pubDate>Mon, 19 Jul 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/linux-ulimit-file-descriptors/</guid><description>验证软硬值继承、子进程局部修改、被拒绝的调高，以及 Bash 同时改动两种限制的赋值方式。</description><content:encoded>&lt;p&gt;shell 内建的 &lt;code&gt;ulimit&lt;/code&gt; 改变的是该 shell 及其随后启动进程的资源限制。对于文件描述符，&lt;code&gt;RLIMIT_NOFILE&lt;/code&gt; 包含当前生效的软值与硬性上界。当终端显示很宽松的数字，独立受管服务却仍然无法打开文件或接受连接时，关键往往在于这些值从哪里继承。只知道某个命令可以调大数字，并不足以证明改到了真正执行工作负载的进程。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;软值通常约束新的描述符分配。普通进程可以在现有硬上限内提高软值，也可以降低硬值；提高硬值则需要相应权限，而且仍然受内核约束。因此，启动时继承的硬值太低，可能使应用无法设置自己打算使用的软值。不能只检查应用配置写了多少，还要核对它是否真正有能力应用那个数字，以及失败有没有被正确处理。&lt;/p&gt;
&lt;p&gt;子进程修改自身限制，不会改变启动它的父进程。同样，在新终端里执行命令，也不会追溯配置已经由 systemd 启动的守护进程。服务实际父进程和启动配置决定其初始值；shell 配置文件只有在真实启动路径读取并应用时才有效。把交互终端的状态当成机器上所有进程的共同状态，会让排障从一开始就观察错对象。&lt;/p&gt;
&lt;p&gt;Bash 的选项选择也需要注意。读取 &lt;code&gt;ulimit -n&lt;/code&gt; 通常显示软值，但赋值时如果没有选择 &lt;code&gt;-S&lt;/code&gt; 或 &lt;code&gt;-H&lt;/code&gt;，会同时修改软硬两项。只想暂时降低软限制的操作者，可能顺带降低硬上限，随后无法再以普通权限恢复。应依据具体 shell 的文档理解语义，不能把几条长得相似的命令当成完全等价。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;本轮环境没有管理员切换数字身份的权限，因此未重新运行该启动流程；执行记录仍来自此前环境。&lt;/p&gt;
&lt;p&gt;在装有 Bash 和 Python 的一次性 Linux 容器中，以管理员身份运行下面的脚本。它通过一个普通数字身份执行实验，管理员控制进程的限制保持不变。实测环境为 Linux 6.12.76、Bash 5.2.15、Python 3.11.2，没有修改任何既有账号配置或服务单元：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getrlimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLIMIT_NOFILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;set -eu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;ulimit -S -n 32
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;ulimit -H -n 64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;python3 - &amp;lt;&amp;lt;&amp;#39;CHILD&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;import resource
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;resource.setrlimit(resource.RLIMIT_NOFILE,(48,64))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert resource.getrlimit(resource.RLIMIT_NOFILE)==(48,64)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;print(&amp;#39;first child inherits 32/64 and raises its own soft limit to 48&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;CHILD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;python3 - &amp;lt;&amp;lt;&amp;#39;CHILD&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;import resource
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;print(&amp;#39;second child still inherits shell values 32/64&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;CHILD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;if ulimit -S -n 65 2&amp;gt;/dev/null; then exit 11; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;if ulimit -H -n 128 2&amp;gt;/dev/null; then exit 12; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ulimit -n 16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; python3 - &amp;lt;&amp;lt;&amp;#39;CHILD&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;import resource
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert resource.getrlimit(resource.RLIMIT_NOFILE)==(16,16)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;print(&amp;#39;Bash assignment without -S/-H changed both limits in this subshell&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;CHILD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;python3 - &amp;lt;&amp;lt;&amp;#39;CHILD&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;import resource
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;assert resource.getrlimit(resource.RLIMIT_NOFILE)==(32,64)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;print(&amp;#39;parent shell limits survived child changes and rejected invalid increases&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;CHILD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run the experiment under an unprivileged numeric identity in this disposable container.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--noprofile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--norc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;61001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;61001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;extra_groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/usr/bin:/bin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LC_ALL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getrlimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLIMIT_NOFILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;administrator controller limits remain unchanged&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;测试 shell 先明确设置软值三十二、硬值六十四。它启动的第一个 Python 子进程继承这组值，然后在硬值保持六十四的情况下，成功把自己的软值提高到四十八。这展示的是现有上界内允许的调整，不需要子进程拥有管理员权限，也不等于它可以绕过硬限制任意扩大资源范围。&lt;/p&gt;
&lt;p&gt;同一个 shell 随后启动第二个 Python 子进程，后者仍然得到三十二和六十四。第一次修改只留在第一个子进程，没有反向传播到 shell，也没有影响 shell 后来启动的其他子进程。两次观察都读取真实资源限制接口，不是根据启动脚本文本推测，因此能清楚区分配置意图和当前进程实际继承的值。&lt;/p&gt;
&lt;p&gt;普通测试 shell 尝试把软值设为超过硬值的六十五，以及把硬值提高到一百二十八，均被拒绝。脚本检查了命令的失败结果，不会隐藏错误后再假装新值已经应用，也没有借助 root 权限让本应失败的操作意外成功。权限上下文是这项实验的一部分，否则很容易测到与生产普通进程不同的行为。&lt;/p&gt;
&lt;p&gt;在子 shell 中执行没有软硬选择参数的赋值，会把两项都变成十六。该子 shell 结束后，原测试 shell 的另一个子进程仍然看到三十二和六十四。这里分别验证了两条常被混淆的规则：一次赋值可以修改两种限制，但它仍然只作用在当前进程范围内，不会因为两项都变了，就拥有反向改写父进程的能力。&lt;/p&gt;
&lt;p&gt;最后，管理员控制进程比较运行前后的限制，确认完全相同。所有降低和失败的调高，都发生在可丢弃的子进程中。实验验证继承和权限规则，并没有替生产服务器决定描述符预算，也没有声称每一种 shell 都具有完全相同的命令细节。实际部署仍应检查正在使用的解释器与启动方式。&lt;/p&gt;
&lt;h2 id="配置并检查真实启动链"&gt;配置并检查真实启动链&lt;/h2&gt;
&lt;p&gt;对于 systemd 服务，应在最终生效的单元设置中配置合适的 &lt;code&gt;LimitNOFILE&lt;/code&gt;，启动策略改变后让替代进程按新策略运行。对于 shell 启动的应用，应在执行程序前通过实际入口设置需要的值。一次性终端命令不是持久配置，除非后续每次启动都有明确机制再次执行它；配置文件存在也不证明这条路径真的被经过。&lt;/p&gt;
&lt;p&gt;应检查目标进程的限制，例如读取对应 &lt;code&gt;/proc/PID/limits&lt;/code&gt;，并确认观察的是正确运行实例。读取控制 shell 的值回答的是另一个问题。容器运行时、用户管理器和中间包装程序都可能影响继承；当实际值与预期不同，应沿真实父子链定位，而不是反复在无关终端里调同一个数字。&lt;/p&gt;
&lt;p&gt;预算应包含套接字、普通文件、管道、事件描述符和必要运维余量。提高上界本身不会创建描述符，但允许更多工作同时存活，可能增加内存和下游负载。因此除了数字，还应验证应用事件机制与资源所有权管理。只有工作负载能够正确使用更高额度时，调整限制才有实际价值，而不是把问题推迟到下一个更大的上限。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man2/getrlimit.2.html"&gt;getrlimit(2)&lt;/a&gt;，资源软硬限制、权限检查与继承，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html"&gt;GNU Bash builtins&lt;/a&gt;，ulimit 的软硬值选择与赋值行为，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/252/systemd.exec.html"&gt;systemd 252 execution environment&lt;/a&gt;，LimitNOFILE 与服务管理器执行限制，已核验 2026-09-12。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>