如何绕过 Linux "Too Many Arguments" 限制

How to get around the Linux "Too Many Arguments" limit

我必须将 256Kb 的文本作为参数传递给 "aws sqs" 命令,但 运行 在命令行中限制在 140Kb 左右。 it been solved in the Linux kernel as of 2.6.23 kernel.

很多地方都讨论过这个问题

但是无法让它工作。我正在使用 3.14.48-33.39.amzn1.x86_64

这里有一个简单的例子来测试:

#!/bin/bash

SIZE=1000
while [ $SIZE -lt 300000 ]
do
   echo "$SIZE"
   VAR="`head -c $SIZE < /dev/zero | tr '[=12=]' 'a'`"
   ./foo "$VAR"
   let SIZE="( $SIZE * 20 ) / 19"
done

foo 脚本只是:

#!/bin/bash
echo -n "" | wc -c

我的输出是:

117037
123196
123196
129680
129680
136505
./testCL: line 11: ./foo: Argument list too long
143689
./testCL: line 11: ./foo: Argument list too long
151251
./testCL: line 11: ./foo: Argument list too long
159211

所以,我该如何修改 testCL 脚本的问题是它可以传递 256Kb 的数据?顺便说一句,我已经尝试将 ulimit -s 65536 添加到脚本中,但没有帮助。

如果这根本不可能,我可以解决这个问题,但是你能解释一下我上面 link 中的这句话吗

"While Linux is not Plan 9, in 2.6.23 Linux is adding variable argument length. Theoretically you shouldn't hit frequently "argument list too long" errors again, but this patch also limits the maximum argument length to 25% of the maximum stack limit (ulimit -s)."

只需将参数放入某个文件,然后修改您的程序以从文件中接受 "arguments"。一个常见的约定(特别被 GCC 和其他几个 GNU 程序使用)是像 @/tmp/arglist.txt 这样的参数要求你的程序从文件 /tmp/arglist.txt 中读取参数,通常每个参数一行

您可能会通过长环境变量传递一些数据,但它们也是有限的(内核限制的实际上是main的大小的初始堆栈,包含程序参数和环境)

或者,将您的程序修改为可通过一些配置文件进行配置,该配置文件将包含您希望通过参数传递的信息。

(如果您可以重新编译您的内核,您可能会尝试将 - 增加到比可用 RAM 小得多的 2 的更大幂,例如增加到 2097152- ARG_MAX#define-d in linux-4.*/include/uapi/linux/limits.h 重新编译你的内核之前)

在其他方面,没有办法规避该限制(请参阅 execve(2) man page and its Limits on size of arguments and environment section) - once you have raised your stack limit (using setrlimit(2)RLIMIT_STACK,通常在父 shell 中内置 ulimit。否则你需要处理它。


编辑:

我终于能够将 <= 256 KB 作为单个命令行参数传递(参见底部的 edit (4))。但是,请仔细阅读我是如何做到的,然后自己决定这是否是您想要的方式。至少你应该能够理解为什么你是 'stuck' 否则从我发现的。


随着 ARG_MAXulim -s / 4 的耦合,引入了 MAX_ARG_STRLEN 作为最大值。参数长度:

/*
 *  linux/fs/exec.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

...

#ifdef CONFIG_MMU
/*
 * The nascent bprm->mm is not visible until exec_mmap() but it can
 * use a lot of memory, account these pages in current->mm temporary
 * for oom_badness()->get_mm_rss(). Once exec succeeds or fails, we
 * change the counter back via acct_arg_size(0).
 */

...

static bool valid_arg_len(struct linux_binprm *bprm, long len)
{
 return len <= MAX_ARG_STRLEN;
}

...

#else

...

static bool valid_arg_len(struct linux_binprm *bprm, long len)
{
  return len <= bprm->p;
}

#endif /* CONFIG_MMU */

...

static int copy_strings(int argc, struct user_arg_ptr argv,
      struct linux_binprm *bprm)
{

...

    str = get_user_arg_ptr(argv, argc);

...

    len = strnlen_user(str, MAX_ARG_STRLEN);
    if (!len)
      goto out;

    ret = -E2BIG;
    if (!valid_arg_len(bprm, len))
      goto out;

...

}

...

MAX_ARG_STRLEN定义为linux/include/uapi/linux/binfmts.h中页面大小的32倍:

...

/*
 * 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

...

默认页面大小为 4 KB,因此您不能传递超过 128 KB 的参数。

我现在无法尝试,但如果可能,在您的系统上切换到大页面模式(页面大小 4 MB)可能会解决此问题。

有关更多详细信息和参考,请参阅 this answer to a similar question on Unix & Linux SE


编辑:

(1) 根据 this answer,可以通过在内核中启用 CONFIG_TRANSPARENT_HUGEPAGE 并将 CONFIG_TRANSPARENT_HUGEPAGE_MADVISE 设置为 nx86_64 Linux 的页面大小更改为 1 MB配置

(2) 使用上述配置更改重新编译我的内核后 getconf PAGESIZE 仍然是 returns 4096。 根据 this answer,还需要 CONFIG_HUGETLB_PAGE,我可以通过 CONFIG_HUGETLBFS 引入。我现在正在重新编译,将再次测试。

(3) 我在启用 CONFIG_HUGETLBFS 的情况下重新编译了我的内核,现在 /proc/meminfo 包含 the corresponding section of the kernel documentation 中提到的相应 HugePages_* 条目。 但是,根据 getconf PAGESIZE 的页面大小仍然没有改变。因此,虽然我现在应该能够通过 mmap 调用请求大页面,但内核的默认页面大小决定 MAX_ARG_STRLEN 仍然固定为 4 KB。

(4) 我将 linux/include/uapi/linux/binfmts.h 修改为 #define MAX_ARG_STRLEN (PAGE_SIZE * 64),重新编译我的内核,现在您的代码生成:

...

117037
123196
123196
129680
129680
136505
143689
151251
159211

...

227982
227982
239981
239981
252611
252611
265906
./testCL: line 11: ./foo: Argument list too long
279901
./testCL: line 11: ./foo: Argument list too long
294632
./testCL: line 11: ./foo: Argument list too long

所以现在限制从 128 KB 变为预期的 256 KB。 我不知道潜在的副作用。 据我所知,我的系统似乎 运行 很好。