명령의 단일 매개변수의 최대 크기를 정의하는 것은 무엇입니까?

명령의 단일 매개변수의 최대 크기를 정의하는 것은 무엇입니까?

내 생각에 여기서는 단일 매개변수의 최대 길이가 문제가 아니라 전체 매개변수 배열의 총 크기와 환경 크기( 로 제한됨)가 문제인 것 같습니다 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쉘의 환경 크기와 프로세스 간의 차이점을 설명하는 것으로 충분합니다 echo. 대신 오류가 발생합니다.

bash: /bin/echo: Argument list too long

잠시 동안 사용해 본 후 최대값이 전체 16진수보다 작다는 것을 알았습니다.

/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(또는 특정 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

답변1

답변

  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에만 적용되는 것으로 보입니다. 이것기사Unix 계열 시스템에 대한 자세한 비교 ARG_MAX및 ​​이에 상응하는 내용이 제공됩니다. MAX_ARG_STRLEN토론은 Linux에 관한 것이지만 다른 시스템에 대한 언급은 없습니다.

    위 기사 MAX_ARG_STRLEN에서는 Linux 2.6.23에 도입된 내용과 명령 매개변수 최대값과 관련된 기타 여러 변경 사항도 지적합니다(아래에서 설명). 커밋 로그/차이점을 찾을 수 있습니다.여기.

  4. getconf ARG_MAX매개변수와 환경의 결과와 실제 가능한 최대 크기 사이에 추가적인 불일치가 발생하는 원인이 무엇인지는 확실하지 않습니다 .Stephane Chazelas의 관련 답변, 각 인수/환경 문자열에 대한 포인터가 공간의 일부를 차지함을 나타냅니다. 그러나 내 조사에 따르면 이러한 포인터는 시스템 호출 초기에 생성되지 않습니다 . 호출 프로세스에 오류를 반환하는 것이 execve여전히 가능하기 때문입니다 ( 각 문자열에 대한 포인터는 확실히 나중에 생성되지만).E2BIGargv

    또한, 제가 아는 한, 문자열은 메모리 내에서 연속되어 있으므로 여기서 정렬로 인한 메모리 공백은 없습니다. 요인이 될 수도 있지만하다추가 메모리가 부족합니다. 추가 공간을 사용하는 것이 무엇인지 이해하려면 커널이 메모리를 할당하는 방법에 대한 더 자세한 이해가 필요합니다(이것은 유용한 지식이므로 나중에 조사하고 업데이트하겠습니다).

ARG_MAX 카오스

Linux 2.6.23 이후(이번에 제출하세요), 명령 매개변수 최대값을 처리하는 방식이 변경되어 Linux가 다른 Unix 계열 시스템과 다릅니다. MAX_ARG_STRLEN및 를 추가하는 것 외에도 MAX_ARG_STRINGS이제 의 결과는 getconf ARG_MAX스택 크기에 따라 달라지며 의 결과와 ARG_MAX다를 수 있습니다 limits.h.

일반적으로 결과는 스택 크기 getconf ARG_MAX입니다 . 스택 크기 가져오기를 사용할 1/4때 다음 사항을 고려하십시오 .bashulimit

$ echo $(( $(ulimit -s)*1024 / 4 ))  # ulimit output in KiB
2097152
$ getconf ARG_MAX
2097152

그러나 위의 동작은 약간 변경됩니다.범죄(리눅스 2.6.25-rc4~121에 추가됨). ARG_MAX이제 는 limits.h의 결과에 대한 엄격한 하한값 역할을 합니다 getconf ARG_MAX. 스택 크기가 in 1/4보다 작게 설정된 경우 이 값이 사용됩니다.ARG_MAXlimits.hlimits.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_MAXstack()의 크기는 RLIMIT_STACK반환되기 전에 매개변수/환경 크기의 상한이 됩니다(단, 값은 계속 표시됨).E2BIGgetconf ARG_MAXlimits.h

마지막 참고 사항은 커널이 CONFIG_MMU메모리 관리 하드웨어 지원 없이 빌드된 경우 검사가 ARG_MAX비활성화되므로 이 제한이 적용되지 않는다는 것입니다. 그래도 MAX_ARG_STRLEN여전히 MAX_ARG_STRINGS적용됩니다.

추가 읽기

답변2

존재하다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.hexec에 전달되는 인수의 최대 길이를 제공합니다.

getconf ARG_MAX명령은 exec에 전달된 최대 누적 인수 크기와 환경 크기를 반환합니다.

답변3

따라서 @StephaneChazelas는 아래 설명에서 저를 올바르게 수정했습니다. 쉘 자체는 시스템에서 허용하는 최대 매개변수 크기를 어떤 식으로든 지시하지 않으며 커널에 의해 설정됩니다.

여러 사람들이 이미 말했듯이 커널은 최대 인수 크기를 128kb로 제한하는 것으로 보입니다. 이는 처음 실행될 때 다른 프로세스에서 새 프로세스로 전달할 수 있는 최대 인수 크기입니다. 특히 많은 중첩으로 인해 이 문제가 발생합니다.$(command substitution)서브셸은 제자리에서 실행되어야 하며 전체 출력을 하나에서 다음으로 전달해야 합니다.

이것은 터무니없는 추측이지만 5kb 정도의 차이가 표준 시스템 페이지 크기와 꽤 비슷해 보이기 때문에 페이지별로 발생하는 것으로 의심됩니다.bash서브쉘 처리를 위해$(command substitution)최종적으로 출력을 전달하거나 함수 스택을 연결하는 데 사용해야 합니다.array table당신의 데이터로. 나는 둘 다 무료가 아니라고 가정할 수 있습니다.

아래에서는 약간 까다로울 수 있지만 스트리밍을 관리할 수 있는 한 호출 시 매우 큰 쉘 변수 값을 새 프로세스에 전달할 수 있음을 보여줍니다.

이를 위해 나는 주로 파이프를 사용합니다. 하지만 쉘 배열도 평가했습니다.here-document방향cat's stdin. 결과는 다음과 같습니다.

하지만 마지막 참고 사항 - 이식 가능한 코드가 특별히 필요하지 않은 경우 나에게 발생합니다.mapfile쉘 작업을 약간 단순화할 수 있습니다.

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

관련 정보