Cosa definisce la dimensione massima per un singolo argomento di comando?


49

Avevo l'impressione che la lunghezza massima di un singolo argomento non fosse il problema qui tanto quanto la dimensione totale dell'array di argomenti generale più la dimensione dell'ambiente, che è limitata a ARG_MAX. Quindi ho pensato che qualcosa di simile al seguente avrebbe avuto successo:

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

Con l' - 100essere più che sufficiente per tenere conto della differenza tra la dimensione dell'ambiente nella shell e il echoprocesso. Invece ho ricevuto l'errore:

bash: /bin/echo: Argument list too long

Dopo aver giocato per un po ', ho scoperto che il massimo era un ordine esadecimale di grandezza più piccolo:

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

Quando viene rimosso quello meno, l'errore ritorna. Apparentemente il massimo per un singolo argomento è in realtà ARG_MAX/16e gli -1account per il byte null posto alla fine della stringa nella matrice di argomenti.

Un altro problema è che quando l'argomento viene ripetuto, la dimensione totale dell'array degli argomenti può essere più vicina ARG_MAX, ma ancora non del tutto:

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

L'uso "${args[0]:6533}"qui allunga l'ultimo argomento di 1 byte e genera l' Argument list too longerrore. È improbabile che questa differenza sia spiegata dalla dimensione dell'ambiente dato:

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

Domande:

  1. È un comportamento corretto o c'è un bug da qualche parte?
  2. In caso contrario, questo comportamento è documentato ovunque? Esiste un altro parametro che definisce il massimo per un singolo argomento?
  3. Questo comportamento è limitato a Linux (o anche a versioni particolari di questo)?
  4. Cosa spiega la discrepanza aggiuntiva di ~ 5 KB tra la dimensione massima effettiva dell'array di argomenti più la dimensione approssimativa dell'ambiente e ARG_MAX?

Informazioni addizionali:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux

5
Su Linux, è hard coded a 32 pagine (128kiB). Vedi MAX_ARG_STRLEN nella fonte.
Stéphane Chazelas,

1
La maggior parte delle informazioni che stai cercando si trova in questa risposta a CP: numero massimo di file di origine argomenti per l'utilità di copia
Stéphane Chazelas

1
Almeno dalla mia macchina, getconf ARG_MAXdipende dalla corrente ulimit -s. Impostalo su illimitato e ottieni un fantastico 4611686018427387903 per ARG_MAX.
derobert,


perché usi path / proc / $$ / environment? procfs in linux supporta symlink / proc / self, quindi puoi usare / proc / self / environment. tutte le patch assegnate al processo, quando lo stesso processo controlla questo, punta a / proc / self. Lo stesso vale per devfs, ad esempio dentro / dev, device stdout è un link simbolico a fd / 1, ma fd punta a / self / fd. molti sistemi copiano questo comportamento.
Znik,

Risposte:


50

risposte

  1. Sicuramente non un bug.
  2. Il parametro che definisce la dimensione massima per un argomento è MAX_ARG_STRLEN. Non esiste documentazione per questo parametro oltre ai commenti in 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
    

    Come mostrato, Linux ha anche un limite (molto grande) al numero di argomenti per un comando.

  3. Un limite sulla dimensione di un singolo argomento (che differisce dal limite complessivo di argomenti e ambiente) sembra essere specifico per Linux. Questo articolo fornisce un confronto dettagliato ARG_MAXe equivalenti su sistemi simili a Unix. MAX_ARG_STRLENè discusso per Linux, ma non si parla di equivalenti su altri sistemi.

    L'articolo sopra afferma anche che è MAX_ARG_STRLENstato introdotto in Linux 2.6.23, insieme a una serie di altre modifiche relative ai massimi argomenti di comando (discussi di seguito). Il log / diff per il commit è disponibile qui .

  4. Non è ancora chiaro cosa spieghi la discrepanza aggiuntiva tra il risultato getconf ARG_MAXe la dimensione massima effettiva possibile degli argomenti più l'ambiente. La risposta correlata di Stephane Chazelas , suggerisce che parte dello spazio è spiegata dai puntatori a ciascuna delle stringhe argomento / ambiente. Tuttavia, la mia indagine suggerisce che questi puntatori non vengono creati all'inizio della execvechiamata di sistema quando potrebbe comunque restituire un E2BIGerrore al processo di chiamata (sebbene i puntatori a ciascuna argvstringa vengano sicuramente creati in seguito).

    Inoltre, per quanto posso vedere, le stringhe sono contigue nella memoria, quindi non ci sono lacune di memoria dovute all'allineamento qui. Anche se è molto probabile che sia un fattore all'interno di qualunque cosa usi la memoria aggiuntiva. Comprendere ciò che utilizza lo spazio aggiuntivo richiede una conoscenza più dettagliata di come il kernel alloca la memoria (che è una conoscenza utile da avere, quindi esaminerò e aggiornerò più avanti).

ARG_MAX Confusione

Dalla versione 2.6.23 di Linux (come risultato di questo commit ), sono state apportate modifiche al modo in cui vengono gestiti i valori massimi degli argomenti dei comandi che rendono Linux diverso dagli altri sistemi simili a Unix. Oltre ad aggiungere MAX_ARG_STRLENe MAX_ARG_STRINGS, il risultato di getconf ARG_MAXora dipende dalle dimensioni dello stack e potrebbe essere diverso da ARG_MAXin limits.h.

Normalmente il risultato di getconf ARG_MAXsarà 1/4della dimensione dello stack. Considerare quanto segue bashnell'uso ulimitper ottenere le dimensioni dello stack:

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

Tuttavia, questo comportamento è stato leggermente modificato da questo commit (aggiunto in Linux 2.6.25-rc4 ~ 121). ARG_MAXin limits.hora funge da limite inferiore rigido al risultato di getconf ARG_MAX. Se la dimensione dello stack è impostata in modo tale che 1/4la dimensione dello stack sia inferiore a ARG_MAXin limits.h, limits.hverrà utilizzato il valore:

$ 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

Si noti inoltre che se la dimensione dello stack impostata è inferiore al minimo possibile ARG_MAX, la dimensione dello stack ( RLIMIT_STACK) diventa il limite superiore della dimensione argomento / ambiente prima che E2BIGvenga restituito (sebbene getconf ARG_MAXmostrerà comunque il valore in limits.h).

Un'ultima cosa da notare è che se il kernel è compilato senza CONFIG_MMU(supporto per l'hardware di gestione della memoria), il controllo di ARG_MAXè disabilitato, quindi il limite non si applica. Sebbene MAX_ARG_STRLENe MAX_ARG_STRINGSancora applicare.

Ulteriori letture


2
Questa è una buona risposta, sicuramente migliore della mia - l'ho votata. Ma la risposta che chiediamo non è sempre la risposta che dovremmo ottenere - ecco perché lo stiamo chiedendo, perché non lo sappiamo. Non affronta il problema con il tuo flusso di lavoro che ti ha portato in testa a questo problema in primo luogo. Dimostro come ciò possa essere mitigato nella mia risposta e come argomenti a stringa variabile a shell singola di lunghezza superiore a 2 Mb possono essere passati a processi appena eseguiti con solo un paio di righe di script di shell.
Mikeserv,

Ho realizzato uno script Python che dimostra il limite di 32 * pagine 4KB = 128 KB delle variabili di ambiente su Linux predefinito.
nh2

0

Nel eglibc-2.18/NEWS

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

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

Nel linux/include/uapi/linux/limits.h

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

Ed 131072è tuo $(getconf ARG_MAX)/16-1, forse dovresti iniziare da 0.

Hai a che fare con glibc e Linux. Sarebbe bene patchare getconf anche al fine di ottenere il ARG_MAXvalore "giusto" restituito.

Modificare:

Per chiarire un po '(dopo una breve ma accesa discussione)

La ARG_MAXcostante definita in limits.h, fornisce la lunghezza massima di un argomento passato con exec.

Il getconf ARG_MAXcomando restituisce il valore massimo della dimensione degli argomenti cumulati e la dimensione dell'ambiente passata a exec.


2
Che ARG_MAX è il minimo garantito per il limite di dimensioni arg + env, non è la dimensione massima di un singolo argomento (anche se capita di avere lo stesso valore di MAX_ARG_STRLEN)
Stéphane Chazelas

Hai una data per il tuo eglibc-2.18/NEWSframmento? Sarebbe bene aggiungere questo ad una particolare versione del kernel.
Graeme,

@StephaneChazelas: Sono troppo pigro per trovare la parte, ma se arg supera il valore massimo non è necessario capire la dimensione del env.

@Graeme: ho anche alcuni vecchi Linux in esecuzione in cui il valore getconf mostra 131072. Penso che questo appartenga ai Linux più recenti con eglibc> ?? solo. Complimenti, hai trovato un bug BTW.

2
Stai guardando il codice glibc, questo è irrilevante qui. Alla libc non importa quale dimensione degli argomenti passi. Il codice che stai citando riguarda sysconf, un'API per dare agli utenti un'idea della dimensione massima (qualunque cosa significhi) di argv + env trasmessa a execve (2). È il kernel che accetta o meno la lista arg ed env trasmessa lungo una chiamata di sistema execve (). Si getconf ARG_MAXtratta della dimensione cumulativa di arg + env (variabile in Linux recente, vedi ulimit -se l'altra domanda che ho collegato), non si tratta della lunghezza massima di un singolo arg per il quale non esiste una query sysconf / getconf.
Stéphane Chazelas

-1

Quindi @StephaneChazelas mi corregge giustamente nei commenti qui sotto: la shell stessa non determina in alcun modo la dimensione massima dell'argomento consentita dal tuo sistema, ma piuttosto è impostata dal tuo kernel.

Come molti altri hanno già detto, sembra che il kernel limiti a 128kb la dimensione massima dell'argomento che puoi passare a un nuovo processo da qualsiasi altro quando lo esegui per la prima volta. Si riscontra questo problema in particolare a causa dei numerosi $(command substitution)subshells nidificati che devono essere eseguiti sul posto e consegnare l'intero output da uno all'altro.

E questa è una specie di ipotesi selvaggia, ma poiché la discrepanza di ~ 5kb sembra così vicina alla dimensione standard della pagina del sistema, il mio sospetto è che sia dedicato bashall'utilizzo della pagina per gestire la subshell $(command substitution)richiesta in definitiva per fornire il suo output e / o lo stack di funzioni che impiega nell'associare i array tabletuoi dati. Posso solo supporre che nessuno dei due sia gratuito.

Dimostro di seguito che, sebbene possa essere un po 'complicato, è possibile trasferire valori di variabili shell molto grandi su nuovi processi durante l'invocazione, purché si riesca a trasmetterlo in streaming.

Per fare ciò, ho usato principalmente tubi. Ma ho anche valutato l'array di shell here-documentindicando i cat's stdin. risultati di seguito.

Ma un'ultima nota: se non hai particolare bisogno di codice portatile, mi colpisce che mapfilepotrebbe semplificare un po 'i tuoi lavori di 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

Forse potresti raddoppiarlo e poi farlo di nuovo se lo facessi in streaming - non sono abbastanza morboso da scoprirlo - ma sicuramente funziona se lo streaming.

Ho provato a cambiare la printfparte del generatore nella riga due in:

printf \ b%.0b

Funziona anche:

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

Quindi forse sono un po 'morboso. Uso zero padding heree aggiungo il "$arg"valore precedente al "$arg"valore corrente . Vado ben oltre il 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 cambio la catlinea in questo modo:

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

Posso ottenere il conteggio dei byte da wc.Ricorda che queste sono le dimensioni di ogni chiave argsdell'array. La dimensione totale dell'array è la somma di tutti questi valori.

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
No, niente a che fare con la shell, è la chiamata di sistema execve (2) che restituisce E2BIG quando un singolo argomento supera i 128 kB.
Stéphane Chazelas,

Considera anche che non c'è limite ai builtin della shell - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullfunzionerà bene. È solo quando si utilizza un comando esterno che si verifica un problema.
Graeme,

@Graeme Beh, l'ho fatto anche con il gatto - nessun problema. La variabile viene valutata in eredità alla fine. Vedi la mia ultima modifica. Ho ridotto il conteggio totale a 33 perché aggiungo ogni volta l'ultimo valore. E lo zero padding ...
mikeserv

@StephaneChazelas - quindi mi sto aggirando valutando l'argomento in un flusso ereditario? O la sta bashcomprimendo in qualche modo?
Mikeserv,

1
@mikeserv, non riesco a vedere da nessuna parte nel tuo codice qualsiasi istanza di te che esegui un comando con un grande elenco arg. printfè un builtin quindi non viene eseguito , e AFAICT, al tuo catnon viene dato alcun argomento.
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.