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


47

我的印象是,这里单个参数的最大长度不是问题,而是整个参数数组的总大小加上环境的大小(最大为)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

这样- 100就足以解决shell中环境大小与echo进程之间的差异。相反,我得到了错误:

bash: /bin/echo: Argument list too long

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

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

减一时,错误返回。似乎单个参数的最大值实际上是,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

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

$ cat /proc/$$/environ | wc -c
1045

问题:

  1. 这是正确的行为,还是某个地方有错误?
  2. 如果不是,是否在任何地方记录了此行为?是否有另一个参数定义单个参数的最大值?
  3. 这种行为是否仅限于Linux(甚至是特定版本)?
  4. 是什么导致参数数组的实际最大大小加上环境的近似大小与之间的〜5KB额外差异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

5
在Linux上,它被硬编码为32页(128kiB)。请参见源代码中的MAX_ARG_STRLEN。
斯特凡Chazelas


1
至少在我的机器上,getconf ARG_MAX取决于电流ulimit -s。将其设置为无限制,并获得惊人的4611686018427387903 ARG_MAX。
derobert 2014年


为什么使用路径/ proc / $$ / environ?linux中的procfs支持symlink / proc / self,那么您可以使用/ proc / self / environ。当同一进程检查此进程时,分配给进程的所有修补程序均指向/ proc / self。devfs也是如此,例如在/ dev内部,设备stdout是符号链接到fd / 1,但是fd指向/ self / fd。许多系统复制此行为。
Znik

Answers:


47

答案

  1. 绝对不是错误。
  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
    

    如图所示,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(此commit的结果)以来,对命令自变量最大值的处理方式进行了更改,这使Linux与其他类似Unix的系统有所不同。除了添加MAX_ARG_STRLEN和之外MAX_ARG_STRINGSgetconf 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

但是,此提交对此行为略有更改(在Linux 2.6.25-rc4〜121中已添加)。 ARG_MAXlimits.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

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

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

进一步阅读


2
这是一个很好的答案,肯定比我的要好-我赞成。但是,我们要求的答案并不总是我们应该获得的答案-这就是我们要求的原因,因为我们不知道。它并没有解决您的工作流程中的问题,而该问题首先使您与该问题并驾齐驱。我将演示如何在我自己的答案中减轻这种情况,以及如何仅用几行shell脚本将长度超过2mbs的单个shell变量字符串参数传递给新执行的进程。
mikeserv 2014年

编写了一个Python脚本,演示了默认Linux上32 * 4KB页面= 128 KB环境变量的限制。
nh2

0

eglibc-2.18/NEWS

* ARG_MAX is not anymore constant on Linux.  Use sysconf(_SC_ARG_MAX).
Implemented by Ulrich Drepper.

eglibc-2.18/debian/patches/kfreebsd/local-sysdeps.diff

+      case _SC_ARG_MAX:
+   request[0] = CTL_KERN;
+   request[1] = KERN_ARGMAX;
+   if (__sysctl(request, 2, &value, &len, NULL, 0) == -1)
+       return ARG_MAX;
+   return (long)value;

linux/include/uapi/linux/limits.h

#define ARG_MAX       131072    /* # bytes of args + environ for exec() */

并且131072是您的$(getconf ARG_MAX)/16-1,也许您应该从0开始。

您正在使用glibc和Linux。也最好修补getconf以便获得ARG_MAX返回的“正确” 值。

编辑:

澄清一点(经过简短但热烈的讨论之后)

ARG_MAX中定义的常数limits.h给出了exec传递的一个参数的最大长度。

getconf ARG_MAX命令返回传递给exec的累积参数大小和环境大小的最大值。


2
这是ARG_MAX保证在ARG + ENV大小限制最小,这不是一个参数的最大尺寸(虽然它恰好是相同的值,MAX_ARG_STRLEN)
斯特凡Chazelas

您的eglibc-2.18/NEWS片段有约会吗?最好将其固定为特定的内核版本。
Graeme 2014年

@StephaneChazelas:我太懒了,找不到零件,但是如果arg超过最大值,就没有必要弄清楚env的大小。

@Graeme:我也有一些较旧的linux在运行,其中getconf值显示131072。我认为这属于带有eglibc> ??的较新的linux。只要。恭喜,您发现了一个BTW错误。

2
您正在查看glibc代码,这与此处无关。libc不在乎您要传递的参数大小。您引用的代码是关于sysconf的,该API使用户可以了解传递给execve(2)的argv + env的最大大小(无论是什么意思)。它是内核是否接受通过execve()系统调用传递的arg和env列表。在getconf ARG_MAX约ARG + ENV的累积大小(变量在最近的Linux,看到ulimit -s和我联系的其他问题),它不是一个单一的ARG对此有没有的sysconf /使用getconf查询的最大长度。
斯特凡Chazelas

-1

因此,@ StephaneChazelas在下面的注释中正确地纠正了我-shell本身不以任何方式决定系统允许的最大参数大小,而是由内核设置。

正如其他几个人已经说过的那样,似乎内核将最大参数大小限制为128kb,您可以在首次执行该最大参数大小时将其交给任何其他进程。您会遇到此问题,特别是由于$(command substitution)必须执行许多嵌套子外壳并将它们的全部输出从一个移到另一个。

这有点疯狂,但是由于〜5kb差异似乎非常接近标准系统页面大小,因此我怀疑它专用bash于处理您$(command substitution)最终需要提供其输出和/或它array table用于将数据与您的数据关联的函数堆栈。我只能假设两者都不免费。

我在下面演示,虽然这可能有些棘手,但可以在调用时将非常大的Shell变量值传递给新进程,只要您能够流式传输它即可。

为此,我主要使用管道。但我还评估在所述壳阵列here-document尖在cat's stdin. 下面结果。

但是最后一点-如果您对可移植代码没有特别的需求,那对我来说mapfile可能会简化您的Shell工作。

time bash <<-\CMD
    ( for arg in `seq 1 6533` ; do
        printf 'args+=(' ; printf b%.0b `seq 1 6533` ; echo ')'
    done ;
    for arg in `seq 1 6533` ; do
        printf %s\\n printf\ '%s\\n'\ \""\${args[$arg]}"\" ;
    done ) | . /dev/stdin >&2
CMD
bash <<<''  66.19s user 3.75s system 84% cpu 1:22.65 total

可能您可以将其加倍,如果您在流中进行了此操作,则可以再次执行此操作-我的病态还不足以找到它-但如果您对其进行流传输,则肯定可以。

我确实尝试将第二printf行中的生成器部分更改为:

printf \ b%.0b

它也可以工作:

bash <<<''  123.78s user 5.42s system 91% cpu 2:20.53 total

所以也许我有点病态。我使用zero padding here并将先前的"$arg"值添加到当前"$arg"值。我超越6500 ...

time bash <<-\CMD
    ( for arg in `seq 1 33` ; do
        echo $arg >&2
        printf 'args+=('"${args[$((a=arg-1))]}$(printf "%0${arg}0d" \
            `seq 1 6533` ; printf $((arg-1)))"')\n'
    done ;
    for arg in `seq 1 33` ; do
        printf '/usr/bin/cat <<HERE\n%s\nHERE\n' "\${args[$arg]}"
    done ) | . /dev/stdin >&2
CMD

bash <<<''  14.08s user 2.45s system 94% cpu 17.492 total

如果我将cat行更改为如下所示:

printf '/usr/bin/cat <<HERE | { printf '$arg'\  ; wc -c ;}
    %s\nHERE\n' "\${args[$arg]}"

我可以从字节数中获取字节数。wc.记住这些是args数组中每个键的大小。数组的总大小是所有这些值的总和。

1 130662
2 195992
3 261322
4 326652
5 391982
6 457312
7 522642
8 587972
9 653302
10 718633
11 783963
12 849293
13 914623
14 979953
15 1045283
16 1110613
17 1175943
18 1241273
19 1306603
20 1371933
21 1437263
22 1502593
23 1567923
24 1633253
25 1698583
26 1763913
27 1829243
28 1894573
29 1959903
30 2025233
31 2090563
32 2155893
33 2221223

2
不,与外壳无关,这是execve(2)系统调用,当单个参数超过128kiB时返回E2BIG。
斯特凡Chazelas

还请考虑对shell内置程序没有限制- echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/null可以正常运行。仅当您使用外部命令时,才有问题。
Graeme 2014年

@Graeme好吧,我也是和猫一起做的-没问题。该变量最后在heredoc中求值。看到我的最后编辑。我确实将总数减少到33,因为每次都添加最后一个值。零填充...
mikeserv

@StephaneChazelas-我可以通过在heredoc流中评估参数来解决这个问题吗?还是bash以某种方式压缩它?
mikeserv

1
@mikeserv,在您执行任何带有大量arg列表的命令的实例的代码中,我都看不到任何地方。printf是内置的,因此不会执行,并且AFAICT,您cat没有任何参数。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.