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

48

Fiquei com a impressão de que o tamanho máximo de um único argumento não era o problema aqui, tanto quanto o tamanho total da matriz geral de argumentos mais o tamanho do ambiente, ao qual está limitado ARG_MAX. Assim, pensei que algo como o seguinte seria bem-sucedido:

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

- 100Sendo o suficiente para explicar a diferença entre o tamanho do ambiente no shell e o echoprocesso. 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 retorna. Aparentemente, o máximo para um único argumento é realmente ARG_MAX/16e as -1contas para o byte nulo são colocadas no final da cadeia de caracteres na matriz de argumentos.

Outra questão é que, quando o argumento é repetido, o tamanho total da matriz de argumentos pode estar mais próximo 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

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

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

Questões:

  1. Esse comportamento é correto ou existe algum bug em algum lugar?
  2. Caso contrário, esse comportamento está documentado em algum lugar? Existe outro parâmetro que define o máximo para um único argumento?
  3. Esse comportamento é limitado ao Linux (ou mesmo a versões específicas)?
  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ção adicional:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
Graeme
fonte
5
No Linux, é codificado para 32 páginas (128kiB). Veja MAX_ARG_STRLEN na fonte.
Stéphane Chazelas
1
A maioria das informações que você está procurando em esta resposta a CP: fonte max arquivos argumentos numéricos para utilitário de cópia
Stéphane Chazelas
1
Pelo menos na minha máquina, getconf ARG_MAXdepende da corrente ulimit -s. Defina-o como ilimitado e obtenha um incrível 4611686018427387903 para ARG_MAX.
derobert
por que você usa path / proc / $$ / environ? procfs no linux suporta symlink / proc / self, então você pode usar / proc / self / environ. todos os patches atribuídos ao processo, quando o mesmo processo verifica isso, apontam para / proc / self. O mesmo ocorre com o devfs, por exemplo, dentro de / dev, stdout do dispositivo é o link simbólico para fd / 1, mas fd aponta para / self / fd. muitos sistemas copiam esse comportamento.
Znik 6/0318

Respostas:

48

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 fornece uma comparação detalhada ARG_MAXe equivalentes em sistemas similares ao Unix. MAX_ARG_STRLENé discutido para Linux, mas não há menção de equivalente em nenhum outro sistema.

    O artigo acima também afirma que MAX_ARG_STRLENfoi introduzido no Linux 2.6.23, juntamente com várias outras alterações relacionadas aos máximos dos argumentos de comando (discutidos 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 getconf ARG_MAXe o tamanho máximo possível real dos argumentos mais o ambiente. A resposta relacionada a Stephane Chazelas sugere que parte do espaço é explicada por ponteiros para cada uma das seqüências de argumento / ambiente. No entanto, minha própria investigação sugere que esses ponteiros não são criados no início da execvechamada do sistema, quando ainda pode retornar um E2BIGerro ao processo de chamada (embora os ponteiros para cada argvcadeia de caracteres certamente sejam criados posteriormente).

    Além disso, as seqüências de caracteres são contíguas na memória, tanto quanto eu posso ver, portanto, não há lacunas de memória devido ao alinhamento aqui. Embora seja muito provável que seja um fator no que quer que consuma memória extra. Para entender o que usa o espaço extra, é necessário um conhecimento mais detalhado de como o kernel aloca memória (o que é um conhecimento útil, por isso vou investigar e atualizar mais tarde).

Confusão ARG_MAX

Desde o Linux 2.6.23 (como resultado dessa confirmação ), houve mudanças na maneira como os máximos dos argumentos de comando são tratados, o que faz o Linux diferir de outros sistemas do tipo Unix. Além de adicionar MAX_ARG_STRLENe MAX_ARG_STRINGS, o resultado de getconf ARG_MAXagora depende do tamanho da pilha e pode ser diferente da ARG_MAXde limits.h.

Normalmente o resultado de getconf ARG_MAXserá 1/4do tamanho da pilha. Considere o seguinte em bashusar ulimitpara 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 ligeiramente alterado por esse commit (adicionado no Linux 2.6.25-rc4 ~ 121). ARG_MAXem limits.hagora serve como uma inferior com força ligado no resultado de getconf ARG_MAX. Se o tamanho da pilha for definido de forma que 1/4o tamanho da pilha seja menor que ARG_MAXem limits.h, o limits.hvalor 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 definido for menor que o mínimo possível ARG_MAX, o tamanho da pilha ( RLIMIT_STACK) se tornará o limite superior do tamanho do argumento / ambiente antes de E2BIGser retornado (embora getconf ARG_MAXainda mostre o valor em limits.h).

Uma coisa final a ser observada é que, se o kernel for construído sem CONFIG_MMU(suporte para hardware de gerenciamento de memória), a verificação de ARG_MAXserá desativada, para que o limite não se aplique. Embora MAX_ARG_STRLENe MAX_ARG_STRINGSainda se aplique.

Leitura adicional

Graeme
fonte
2
Esta é uma boa resposta, certamente melhor que a minha - eu a votei. Mas a resposta que pedimos nem sempre é a resposta que devemos obter - é por isso que estamos perguntando, porque não sabemos. Ele não resolve o problema do seu fluxo de trabalho que o levou a enfrentar esse problema em primeiro lugar. Demonstro como isso pode ser atenuado em minha própria resposta e como argumentos de cadeia de caracteres variáveis ​​de shell único com mais de 2 MB de comprimento podem ser passados ​​para processos recém-executados com apenas algumas linhas de script de shell.
mikeserv
Eu criei um script Python que demonstra as páginas de 32 * 4KB = limite de 128 KB de variáveis ​​de ambiente no Linux padrão.
nh2 03/03
0

No eglibc-2.18/NEWS

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

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

No 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 em 0.

Você está lidando com glibc e Linux. Seria bom corrigir o getconf também para obter o ARG_MAXvalor "correto" retornado.

Editar:

Para esclarecer um pouco (após uma discussão curta, mas quente)

A ARG_MAXconstante definida em limits.hfornece o comprimento máximo de um argumento passado com exec.

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


fonte
2
Isso ARG_MAX é o mínimo garantido para o arg + env limite de tamanho, não é o tamanho máximo de um único argumento (embora ele passa a ser o mesmo valor que MAX_ARG_STRLEN)
Stéphane Chazelas
Você tem uma data para o seu eglibc-2.18/NEWSsnippet? Seria bom atribuir isso a uma versão específica do kernel.
Graeme
@ StephanieChazelas: Estou com preguiça de encontrar a peça, mas se arg exceder o valor máximo, não é necessário descobrir o tamanho do ambiente.
@ Graeme: Eu também tenho alguns linuxes mais antigos em execução, onde o valor getconf mostra 131072. Acho que isso pertence aos linuxes mais recentes com eglibc> ?? só. Parabéns, você encontrou um bug BTW.
2
Você está vendo o código glibc, que é irrelevante aqui. A libc não se importa com o tamanho dos argumentos que você está passando. O código que você está citando é sobre sysconf, uma API que fornece aos usuários uma idéia do tamanho máximo (o que quer que isso signifique) de argv + env passado para um execve (2). É o kernel que aceita ou não a lista arg e env passada ao longo de uma chamada de sistema execve (). O getconf ARG_MAXé sobre o tamanho cumulativo de arg + env (variável no Linux recente, veja ulimit -se a outra pergunta que eu vinculei), não é sobre o comprimento máximo de um único argumento para o qual não há consulta sysconf / getconf.
Stéphane Chazelas
-1

Portanto, o @StephaneChazelas me corrige corretamente nos comentários abaixo - o próprio shell não determina de forma alguma o tamanho máximo do 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 de argumento que você pode entregar a um novo processo de qualquer outro quando o executar pela primeira vez. Você enfrenta esse problema especificamente devido aos muitos $(command substitution)subshells aninhados que devem ser executados no local e entregar a totalidade de sua saída de um para o outro.

E essa é uma espécie 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 ela seja dedicada ao uso da página bashpara lidar com o subshell $(command substitution)necessário para finalmente fornecer sua saída e / ou a pilha de funções que ele emprega ao associar os array tableseus dados. 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, desde que você consiga transmiti-lo.

Para fazer isso, usei principalmente tubos. Mas também avaliei o array de shell em Resultados here-documentapontados cat's stdin.abaixo.

Mas uma última observação - se você não precisa de código portátil, parece-me que isso mapfilepode simplificar um pouco seus trabalhos de 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ê pode dobrar isso e fazê-lo novamente se fizer isso em fluxos - não sou mórbido o suficiente para descobrir - mas definitivamente funciona se você transmitir.

Tentei alterar a printfparte do gerador 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 esteja um pouco mórbida. Eu uso zero padding heree adiciono o "$arg"valor anterior ao "$arg"valor atual . Eu vou 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 mudar a catlinha para ficar assim:

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

Posso obter contagens de bytes em wc.Lembre-se de que esses são os tamanhos de cada chave na argsmatriz. 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
mikeserv
fonte
2
Não, nada a ver com o shell, é a chamada de sistema execve (2) retornando E2BIG quando um único argumento ultrapassa 128 kB.
Stéphane Chazelas
Considere também que não há limite para os componentes internos do shell - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullfuncionará bem. Somente quando você usa um comando externo é que existe um problema.
Graeme
@ Graeme Bem, eu fiz isso com gato também - não há problema. A variável é avaliada em um heredoc no final. Veja minha última edição. Reduzi a contagem total para 33 porque estou adicionando o último valor a cada vez. Eo preenchimento de zero ...
mikeserv
@StephaneChazelas - então eu estou contornando isso avaliando o argumento em um fluxo heredoc? Ou está bashcomprimindo de alguma forma?
mikeserv
1
@ MikeServ, eu não posso ver em qualquer lugar no seu código qualquer instância de você executar um comando com uma grande lista de argumentos. printfé um builtin, portanto não é executado e, AFAICT, seu catargumento não é fornecido.
Stéphane Chazelas