O que define o tamanho máximo para um argumento único de comando?

42

Eu estava com a impressão de que o tamanho máximo de um único argumento não era o problema aqui, mas sim o tamanho total do array de argumentos geral mais o tamanho do ambiente, que é limitado a ARG_MAX . Assim, pensei que algo como o seguinte teria sucesso:

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

Com o - 100 sendo mais que suficiente para explicar a diferença entre o tamanho do ambiente no shell e o processo echo . Em vez disso, recebi o erro:

bash: /bin/echo: Argument list too long

Depois de brincar um pouco, descobri que o máximo era uma ordem hexadecimal completa de magnitude menor:

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

Quando o menos um é removido, o erro é retornado. Aparentemente, o máximo para um único argumento é, na verdade, ARG_MAX/16 e o -1 é responsável pelo byte nulo colocado no final da string na matriz de argumentos.

Outra questão é que, quando o argumento é repetido, o tamanho total da matriz de argumentos pode estar mais próximo de ARG_MAX , mas ainda não está lá:

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

Usar "${args[0]:6533}" aqui torna o último argumento de 1 byte mais longo e fornece o erro Argument list too long . É improvável que essa diferença seja explicada pelo tamanho do ambiente dado:

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

Perguntas:

  1. Esse comportamento é correto ou há algum bug em algum lugar?
  2. Se não, esse comportamento é documentado em algum lugar? Existe outro parâmetro que define o máximo para um único argumento?
  3. Este comportamento é limitado ao Linux (ou mesmo versões particulares de tal)?
  4. O que explica a discrepância adicional de ~ 5 KB entre o tamanho máximo real da matriz de argumentos mais o tamanho aproximado do ambiente e ARG_MAX ?

Informações adicionais:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
    
por Graeme 20.03.2014 / 19:32

3 respostas

43

Respostas

  1. Definitivamente não é um bug.
  2. O parâmetro que define o tamanho máximo para um argumento é MAX_ARG_STRLEN . Não há documentação para esse parâmetro além dos comentários em 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
    

    Como mostrado, o Linux também tem um limite (muito grande) no número de argumentos para um comando.

  3. Um limite no tamanho de um único argumento (que difere do limite geral de argumentos mais ambiente) parece ser específico para o Linux. Este artigo oferece uma comparação detalhada de ARG_MAX e equivalentes em sistemas semelhantes a Unix. MAX_ARG_STRLEN é discutido para o Linux, mas não há menção de nenhum equivalente em nenhum outro sistema.

    O artigo acima também afirma que MAX_ARG_STRLEN foi introduzido no Linux 2.6.23, junto com várias outras mudanças relacionadas ao comando maximum dos argumentos (discutido abaixo). O log / diff para o commit pode ser encontrado aqui .

  4. Ainda não está claro o que explica a discrepância adicional entre o resultado de getconf ARG_MAX e o tamanho real máximo possível dos argumentos mais o ambiente. A resposta relacionada de Stephane Chazelas , sugere que parte do espaço é explicada por ponteiros para cada um dos argumentos / ambiente cordas. No entanto, minha própria investigação sugere que esses ponteiros não são criados no início da chamada do sistema execve quando ainda pode retornar um erro E2BIG para o processo de chamada (embora os ponteiros para cada string argv sejam certamente criados posteriormente). / p>

    Além disso, as seqüências de caracteres são contíguas na memória, tanto quanto eu posso ver, por isso não há intervalos de memória devido ao alinhamento aqui. Embora seja muito provável que seja um fator dentro de qualquer que use a memória extra. Entender o que usa o espaço extra requer um conhecimento mais detalhado de como o kernel aloca memória (que é um conhecimento útil para se ter, então eu investigarei e atualizarei mais tarde).

ARG_MAX Confusão

Desde o Linux 2.6.23 (como resultado de este commit ), houve mudanças no modo como os valores máximos do argumento do comando são manipulados, o que faz o Linux diferir de outros sistemas do tipo Unix. Além de adicionar MAX_ARG_STRLEN e MAX_ARG_STRINGS , o resultado de getconf ARG_MAX agora depende do tamanho da pilha e pode ser diferente de ARG_MAX em limits.h .

Normalmente, o resultado de getconf ARG_MAX será 1/4 do tamanho da pilha. Considere o seguinte em bash usando ulimit para obter o tamanho da pilha:

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

No entanto, o comportamento acima foi alterado ligeiramente por este commit (adicionado no Linux 2.6.25-rc4 ~ 121).   ARG_MAX in limits.h agora serve como um limite inferior rígido no resultado de getconf ARG_MAX . Se o tamanho da pilha estiver definido de forma que 1/4 do tamanho da pilha seja menor que ARG_MAX em limits.h , o valor limits.h será usado:

$ 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

Observe também que, se o tamanho da pilha for menor que o mínimo possível ARG_MAX , o tamanho da pilha ( RLIMIT_STACK ) se tornará o limite superior de argumento / ambiente antes que E2BIG seja retornado (embora getconf ARG_MAX ainda mostrará o valor em limits.h ).

Uma última observação a ser feita é que, se o kernel for construído sem CONFIG_MMU (suporte para hardware de gerenciamento de memória), a verificação de ARG_MAX será desativada, portanto, o limite não se aplica. Embora MAX_ARG_STRLEN e MAX_ARG_STRINGS ainda se apliquem.

Leitura adicional

  • Resposta relacionada de Stephane Chazelas - link
  • Na página detalhada que abrange a maioria dos itens acima. Inclui uma tabela de valores ARG_MAX (e equivalentes) em outros sistemas semelhantes a Unix - link
  • Aparentemente, a introdução de MAX_ARG_STRLEN causou um erro no Automake, que estava embutindo scripts shell em Makefiles usando sh -c - link
por 21.03.2014 / 19:38
0

Em eglibc-2.18/NEWS

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

Em 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;

Em linux/include/uapi/linux/limits.h

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

E 131072 é seu $(getconf ARG_MAX)/16-1 , talvez você deva começar com 0.

Você está lidando com o glibc e o Linux. Seria bom remendar o getconf também para obter o valor "%"ARG_MAX "correto" retornado.

Editar:

Para clarificar um pouco (depois de uma discussão curta, mas quente)

A constante ARG_MAX , que é definida em limits.h , fornece o tamanho máximo de um argumento passado com exec.

O comando getconf ARG_MAX retorna o valor máximo do tamanho dos argumentos acumulados e o tamanho do ambiente passado para exec.

    
por 20.03.2014 / 21:40
-1

Então @StephaneChazelas me corrige corretamente nos comentários abaixo - o shell em si não dita de qualquer forma o tamanho máximo de argumento permitido pelo seu sistema, mas é definido pelo seu kernel.

Como vários outros já disseram, parece que o kernel limita a 128kb o tamanho máximo do argumento que você pode entregar para um novo processo de qualquer outro quando executá-lo pela primeira vez. Você enfrenta esse problema especificamente devido a muitas subpastas $(command substitution) aninhadas que devem ser executadas no local e entregam a totalidade de sua saída de uma para a próxima.

E este é um tipo de palpite, mas como a discrepância de ~ 5kb parece tão próxima do tamanho padrão da página do sistema, minha suspeita é que ele é dedicado à página bash manipule o subshell que seu $(command substitution) exige para entregar sua saída e / ou a pilha de funções que ele emprega ao associar seu array table aos seus dados. Eu só posso assumir que nem vem de graça.

Demonstro abaixo que, embora possa ser um pouco complicado, é possível passar valores de variáveis de shell muito grandes para novos processos na chamada, contanto que você consiga gerenciá-lo.

Para fazer isso, eu usei principalmente pipes. Mas também avaliei o array de shell em um here-document apontado em cat's stdin. Resultados abaixo.

Mas uma última observação - se você não precisa de código portátil, parece que mapfile pode simplificar um pouco seus trabalhos com o 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

Possivelmente você poderia dobrar isto e então fazê-lo novamente se você o fizesse em streams - eu não sou mórbido o suficiente para descobrir - mas definitivamente funciona se você o transmitir.

Eu tentei alterar a parte do gerador printf na linha dois para:

printf \ b%.0b

Também funciona:

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

Então talvez eu seja um pouco mórbida. Eu uso zero padding here e adiciono o valor anterior "$arg" ao valor atual "$arg" . Eu tenho muito além de 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

E se eu alterar a linha cat para ficar assim:

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

Eu posso obter contagens de bytes de wc. Lembre-se de que esses são os tamanhos de cada chave na matriz args . O tamanho total da matriz é a soma de todos esses valores.

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
    
por 21.03.2014 / 04:10