什么定义了命令单个参数的最大大小?

Gra*_*eme 66 kernel echo arguments

我的印象是,单个参数的最大长度在这里不是问题,而是整个参数数组的总大小加上环境的大小(仅限于ARG_MAX. 因此,我认为类似以下的事情会成功:

env_size=$(cat /proc/$$/environ | wc -c)
(( arg_size = $(getconf ARG_MAX) - $env_size - 100 ))
/bin/echo $(tr -dc [:alnum:] </dev/urandom | head -c $arg_size) >/dev/null
Run Code Online (Sandbox Code Playgroud)

这- 100足以解释 shell 和echo进程中环境大小之间的差异。相反,我收到了错误:

bash: /bin/echo: Argument list too long
Run Code Online (Sandbox Code Playgroud)

玩了一段时间后,我发现最大值小了一个完整的十六进制数量级:

/bin/echo \
  $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)/16-1))) \
  >/dev/null
Run Code Online (Sandbox Code Playgroud)

当减一被删除时,错误返回。看似单个参数的最大值实际上是放置在参数数组中字符串末尾的空字节ARG_MAX/16的-1帐户。

另一个问题是,当参数重复时,参数数组的总大小可以接近ARG_MAX,但仍然不完全:

args=( $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)/16-1))) )
for x in {1..14}; do
  args+=( ${args[0]} )
done

/bin/echo "${args[@]}" "${args[0]:6534}" >/dev/null
Run Code Online (Sandbox Code Playgroud)

"${args[0]:6533}"在这里使用会使最后一个参数长 1 个字节并给出Argument list too long错误。这种差异不太可能由环境的大小来解释:

$ cat /proc/$$/environ | wc -c
1045
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 这是正确的行为,还是某处有错误?
  2. 如果没有,这种行为是否记录在任何地方?是否有另一个参数定义单个参数的最大值?
  3. 这种行为是否仅限于 Linux(甚至此类的特定版本)?
  4. 是什么解释了参数数组的实际最大大小加上环境的近似大小与ARG_MAX?

附加信息:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
Run Code Online (Sandbox Code Playgroud)

Gra*_*eme 68

答案

  1. 绝对不是bug。
  2. 定义一个参数的最大大小的参数是MAX_ARG_STRLEN。除了 中的注释外,没有此参数的文档binfmts.h:

    /*
     * These are the maximum length and maximum number of strings passed to the
     * execve() system call.  MAX_ARG_STRLEN is essentially random but serves to
     * prevent the kernel from being unduly impacted by misaddressed pointers.
     * MAX_ARG_STRINGS is chosen to fit in a signed 32-bit integer.
     */
    #define MAX_ARG_STRLEN (PAGE_SIZE * 32)
    #define MAX_ARG_STRINGS 0x7FFFFFFF
    
    Run Code Online (Sandbox Code Playgroud)

    如图所示,Linux 对命令的参数数量也有(非常大的)限制。

  3. 对单个参数大小的限制(不同于对参数加环境的总体限制)似乎是特定于 Linux 的。此文章给出的详细比较ARG_MAX像系统和等同物在Unix。MAX_ARG_STRLEN讨论了 Linux,但没有提到任何其他系统上的任何等效项。

    上面的文章还指出,它MAX_ARG_STRLEN是在 Linux 2.6.23 中引入的,以及与命令参数最大值相关的许多其他更改(下面讨论)。可以在此处找到提交的日志/差异。

  4. 目前尚不清楚是什么导致getconf ARG_MAX了参数加环境的结果与实际最大可能大小之间的额外差异。Stephane Chazelas 的相关回答表明,部分空间是由指向每个参数/环境字符串的指针来解决的。但是,我自己的调查表明,这些指针不会在execve系统调用的早期创建,因为它可能仍会E2BIG向调用进程返回错误(尽管argv稍后肯定会创建指向每个字符串的指针)。

    此外,就我所见,这些字符串在内存中是连续的,因此这里没有由于对齐而导致的内存间隙。虽然很可能是内任何一个因素不会占用额外的内存。了解什么使用了额外空间需要更详细地了解内核如何分配内存(这是有用的知识,因此我将在稍后进行调查和更新)。

ARG_MAX 混淆

自 Linux 2.6.23(作为此提交的结果)以来,命令参数最大值的处理方式发生了变化,这使得 Linux 与其他类 Unix 系统不同。除了添加MAX_ARG_STRLEN和之外MAX_ARG_STRINGS,getconf ARG_MAXnow的结果取决于堆栈大小,并且可能与ARG_MAXin不同limits.h。

通常的结果getconf ARG_MAX将是1/4堆栈大小。在bash使用ulimit获取堆栈大小时考虑以下事项:

$ echo $(( $(ulimit -s)*1024 / 4 ))  # ulimit output in KiB
2097152
$ getconf ARG_MAX
2097152
Run Code Online (Sandbox Code Playgroud)

但是,此提交(在 Linux 2.6.25-rc4~121 中添加)稍微改变了上述行为。 ARG_MAXinlimits.h现在用作 的结果的硬下界getconf ARG_MAX。如果堆栈大小设置1/4为小于ARG_MAXin limits.h,则将使用该limits.h值:

$ grep ARG_MAX /usr/include/linux/limits.h 
#define ARG_MAX       131072    /* # bytes of args + environ for exec() */
$ ulimit -s 256
$ echo $(( $(ulimit -s)*1024 / 4 ))
65536
$ getconf ARG_MAX
131072
Run Code Online (Sandbox Code Playgroud)

另请注意,如果堆栈大小设置为低于可能的最小值ARG_MAX,则堆栈 ( RLIMIT_STACK) 的大小在E2BIG返回之前成为参数/环境大小的上限(尽管getconf ARG_MAX仍会显示 中的值limits.h)。

最后要注意的是,如果内核是在没有CONFIG_MMU(支持内存管理硬件)的情况下构建的,则ARG_MAX禁用检查,因此限制不适用。虽然MAX_ARG_STRLEN并且MAX_ARG_STRINGS仍然适用。

进一步阅读

  • 这是一个很好的答案,肯定比我的好 - 我赞成它。但是我们要求的答案并不总是我们应该得到的答案——这就是我们问的原因,因为我们不知道。它没有解决您的工作流程中的问题,该问题首先让您与此问题正面交锋。我演示了如何在我自己的答案中减轻这种情况,以及如何将长度超过 2mbs 的单个 shell 变量字符串参数传递给新执行的进程,只需几行 shell 脚本。 (2认同)