Was definiert die maximale Größe für ein einzelnes Befehlsargument?


47

Ich hatte den Eindruck, dass die maximale Länge eines einzelnen Arguments hier nicht das Problem war, sondern vielmehr die Gesamtgröße des gesamten Argument-Arrays plus die Größe der Umgebung, die auf beschränkt ist ARG_MAX. So dachte ich, dass so etwas gelingen würde:

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

Mit dem - 100Sein mehr als genug, um den Unterschied zwischen der Größe der Umgebung in der Shell und dem echoProzess zu berücksichtigen. Stattdessen habe ich den Fehler bekommen:

bash: /bin/echo: Argument list too long

Nachdem ich eine Weile herumgespielt hatte, stellte ich fest, dass das Maximum eine ganze Hex-Größenordnung kleiner war:

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

Wenn das Minuszeichen entfernt wird, wird der Fehler zurückgegeben. Scheinbar ist das Maximum für ein einzelnes Argument tatsächlich ARG_MAX/16und die -1Konten für das Nullbyte, das am Ende der Zeichenfolge im Argumentarray platziert wird.

Ein weiteres Problem ist, dass bei Wiederholung des Arguments die Gesamtgröße des Argument-Arrays näher sein kann ARG_MAX, aber immer noch nicht ganz da ist:

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

Die Verwendung von "${args[0]:6533}"here verlängert das letzte Argument um 1 Byte und gibt den Argument list too longFehler aus. Es ist unwahrscheinlich, dass dieser Unterschied auf die Größe der gegebenen Umgebung zurückzuführen ist:

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

Fragen:

  1. Ist das korrekt oder gibt es irgendwo einen Fehler?
  2. Wenn nicht, ist dieses Verhalten irgendwo dokumentiert? Gibt es einen anderen Parameter, der das Maximum für ein einzelnes Argument definiert?
  3. Ist dieses Verhalten auf Linux (oder sogar bestimmte Versionen davon) beschränkt?
  4. Was erklärt die zusätzliche Diskrepanz von ~ 5 KB zwischen der tatsächlichen Maximalgröße des Argument-Arrays plus der ungefähren Größe der Umgebung und ARG_MAX?

Zusätzliche Information:

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

5
Unter Linux ist es auf 32 Seiten (128 KB) fest programmiert. Siehe MAX_ARG_STRLEN in der Quelle.
Stéphane Chazelas


1
Zumindest an meiner Maschine, getconf ARG_MAXhängt von der Stromstärke ab ulimit -s. Setzen Sie es auf unbegrenzt und erhalten Sie eine erstaunliche 4611686018427387903 für ARG_MAX.
Derobert


warum benutzt du path / proc / $$ / environ? procfs in linux unterstützt symlink / proc / self, dann kannst du / proc / self / environ verwenden. Alle Patches, die dem Prozess zugewiesen sind, zeigen auf / proc / self, wenn derselbe Prozess dies überprüft. Dasselbe gilt für devfs, z. B. inside / dev. Device stdout ist symlink zu fd / 1, aber fd zeigt auf / self / fd. Viele Systeme kopieren dieses Verhalten.
Znik

Antworten:


47

Antworten

  1. Auf keinen Fall ein Bug.
  2. Der Parameter, der die maximale Größe für ein Argument definiert, ist MAX_ARG_STRLEN. Es gibt keine Dokumentation für diesen Parameter außer den Kommentaren 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
    

    Wie gezeigt, ist die Anzahl der Argumente für einen Befehl in Linux (sehr groß) begrenzt.

  3. Eine Begrenzung der Größe eines einzelnen Arguments (die sich von der Gesamtbegrenzung für Argumente plus Umgebung unterscheidet) scheint für Linux spezifisch zu sein. Dieser Artikel enthält einen detaillierten Vergleich ARG_MAXund Entsprechungen zu Unix-ähnlichen Systemen. MAX_ARG_STRLENwird für Linux erörtert, auf anderen Systemen wird jedoch kein Äquivalent erwähnt.

    Der obige Artikel besagt auch, dass MAX_ARG_STRLENer in Linux 2.6.23 eingeführt wurde, zusammen mit einer Reihe anderer Änderungen in Bezug auf Befehlsargumentmaxima (weiter unten beschrieben). Das Protokoll / Diff für das Commit finden Sie hier .

  4. Es ist immer noch nicht klar, was für die zusätzliche Diskrepanz zwischen dem Ergebnis getconf ARG_MAXund der tatsächlich maximal möglichen Größe von Argumenten plus Umgebung verantwortlich ist. Stephane Chazelas 'Antwort legt nahe, dass ein Teil des Raums durch Zeiger auf jede der Argument- / Umgebungszeichenfolgen erklärt wird. Meine eigene Untersuchung legt jedoch nahe, dass diese Zeiger nicht zu Beginn des execveSystemaufrufs erstellt E2BIGwerden, obwohl sie möglicherweise immer noch einen Fehler an den aufrufenden Prozess zurückgeben (obwohl Zeiger auf jede argvZeichenfolge sicherlich später erstellt werden).

    Soweit ich sehen kann, sind die Zeichenfolgen auch zusammenhängend im Speicher, sodass hier keine Speicherlücken entstehen. Obwohl sehr wahrscheinlich ein Faktor in was auch immer sein wird den zusätzlichen Speicher aufbrauchen. Um zu verstehen, wie der zusätzliche Speicherplatz genutzt wird, ist eine genauere Kenntnis der Speicherzuweisung durch den Kernel erforderlich (eine nützliche Kenntnis, die ich später untersuchen und aktualisieren werde).

ARG_MAX Verwirrung

Seit Linux 2.6.23 (als Ergebnis dieses Commits ) wurden Änderungen an der Art und Weise vorgenommen, in der Befehlsargumentmaxima behandelt werden, wodurch sich Linux von anderen Unix-ähnlichen Systemen unterscheidet. Zusätzlich zum Hinzufügen von MAX_ARG_STRLENund hängt MAX_ARG_STRINGSdas Ergebnis von getconf ARG_MAXder Stapelgröße ab und kann von ARG_MAXin abweichen limits.h.

Normalerweise das Ergebnis getconf ARG_MAXwird 1/4die Stack - Größe. Betrachten Sie die folgende in bashmit ulimitder Stack - Größe zu bekommen:

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

Das obige Verhalten wurde jedoch durch dieses Commit geringfügig geändert (hinzugefügt in Linux 2.6.25-rc4 ~ 121). ARG_MAXin limits.hdient nun als harte Untergrenze für das Ergebnis von getconf ARG_MAX. Wenn die Stapelgröße so eingestellt ist, dass 1/4die Stapelgröße kleiner als ARG_MAXin ist limits.h, wird der limits.hWert verwendet:

$ 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

Beachten Sie außerdem, dass ARG_MAXdie Größe des Stapels ( RLIMIT_STACK) die Obergrenze der Argument- / Umgebungsgröße darstellt, bevor sie E2BIGzurückgegeben getconf ARG_MAXwird , wenn die Stapelgröße auf einen niedrigeren Wert als den minimal möglichen Wert festgelegt wird (obwohl der Wert weiterhin in angezeigt wird limits.h).

Als letztes ist zu beachten, dass, wenn der Kernel ohne CONFIG_MMU(Unterstützung für Speicherverwaltungshardware) erstellt wird, die Überprüfung ARG_MAXdeaktiviert ist, sodass das Limit nicht gilt. Obwohl MAX_ARG_STRLENund MAX_ARG_STRINGSimmer noch gelten.

Weitere Lektüre


2
Dies ist eine gute Antwort, sicherlich besser als meine - ich habe sie positiv bewertet. Aber die Antwort, nach der wir fragen, ist nicht immer die Antwort, die wir bekommen sollten - deshalb fragen wir, weil wir es nicht wissen. Das Problem in Ihrem Arbeitsablauf, das Sie mit diesem Problem konfrontiert hat, wird hier nicht angesprochen. Ich demonstriere, wie dies in meiner eigenen Antwort gemildert werden kann und wie einzelne Shell-Variablen-String-Argumente mit einer Länge von mehr als 2 MB mit nur wenigen Zeilen Shell-Skript an neu ausgeführte Prozesse übergeben werden können.
mikeserv

Ich habe ein Python-Skript erstellt , das die Begrenzung der Umgebungsvariablen auf 32 * 4 KB Seiten = 128 KB unter Standard-Linux demonstriert.
nh2

0

Im eglibc-2.18/NEWS

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

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

Im linux/include/uapi/linux/limits.h

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

Und 131072ist dein $(getconf ARG_MAX)/16-1, vielleicht solltest du bei 0 anfangen.

Sie haben es mit glibc und Linux zu tun. Es wäre gut, getconf auch zu patchen, um den "richtigen" ARG_MAXWert zurückzuerhalten.

Bearbeiten:

Zur Klärung ein wenig (nach einer kurzen aber heißen Diskussion)

Die ARG_MAXin definierte Konstante limits.hgibt die maximale Länge eines mit exec übergebenen Arguments an.

Der getconf ARG_MAXBefehl gibt den Maximalwert der Größe der kumulierten Argumente und der Umgebungsgröße zurück, die an exec übergeben wurden.


2
Dass ARG_MAX das garantierte Minimum für die Größenbeschränkung von arg + env ist, ist nicht die maximale Größe eines einzelnen Arguments (obwohl es zufällig der gleiche Wert wie MAX_ARG_STRLEN ist)
Stéphane Chazelas

Hast du ein Date für dein eglibc-2.18/NEWSSnippet? Es wäre gut, dies auf eine bestimmte Kernel-Version festzulegen.
Graeme

@StephaneChazelas: Ich bin einfach zu faul, um das Teil zu finden, aber wenn arg den Maximalwert überschreitet, ist es nicht notwendig, die Umgebungsgröße herauszufinden.

@Graeme: Ich habe auch einige ältere Linuxe, auf denen der getconf-Wert 131072 anzeigt. Ich denke, das gehört zu neueren Linuxen mit eglibc> ?? nur. Herzlichen Glückwunsch, Sie haben übrigens einen Fehler gefunden.

2
Sie suchen nach Glibc-Code, das ist hier irrelevant. Der libc ist es egal, wie viele Argumente Sie übergeben. Der Code, den Sie zitieren, handelt von sysconf, einer API, mit der Benutzer eine Vorstellung von der maximalen Größe (was auch immer das bedeutet) von argv + env erhalten, die an execve (2) übergeben wird. Es ist der Kernel, der die bei einem Systemaufruf von execve () übergebene arg- und env-Liste akzeptiert oder nicht. Es getconf ARG_MAXgeht um die kumulative Größe von arg + env (Variable in neuerem Linux, siehe ulimit -sund die andere Frage, die ich verlinkt habe), es geht nicht um die maximale Länge eines einzelnen Arguments, für das es keine sysconf / getconf-Abfrage gibt.
Stéphane Chazelas

-1

@StephaneChazelas korrigiert mich daher zu Recht in den Kommentaren unten - die Shell selbst bestimmt in keiner Weise die von Ihrem System zulässige maximale Argumentgröße, sondern sie wird von Ihrem Kernel festgelegt.

Wie einige andere bereits gesagt haben, scheint der Kernel die maximale Argumentgröße, die Sie einem neuen Prozess von jedem anderen übergeben können, wenn Sie ihn zum ersten Mal ausführen, auf 128 KB zu begrenzen. Dieses Problem tritt speziell aufgrund der vielen verschachtelten $(command substitution)Subshells auf, die ausgeführt und die gesamte Ausgabe von einer zur nächsten übergeben werden müssen.

Und dies ist eine wilde Vermutung, aber da die Diskrepanz von ~ 5 KB so nahe an der Seitengröße des Standardsystems zu liegen scheint, ist mein Verdacht, dass sie der Seite gewidmet ist, die für die bashVerarbeitung der von Ihnen $(command substitution)benötigten Subshell verwendet wird, um letztendlich die Ausgabe und / oder das Ergebnis zu liefern den Funktionsstapel, array tablemit dem Sie Ihre Daten verknüpfen . Ich kann nur davon ausgehen, weder kommt frei.

Im Folgenden wird gezeigt, dass es zwar etwas schwierig ist, beim Aufruf jedoch sehr große Shell-Variablenwerte an neue Prozesse weiterzugeben, sofern Sie es schaffen, sie zu streamen.

Zu diesem Zweck habe ich hauptsächlich Rohre verwendet. Aber ich habe auch das Shell-Array in einem here-documentPunkt auf cat's stdin. Ergebnisse unten ausgewertet .

Aber noch eine letzte Anmerkung: Wenn Sie keinen besonderen Bedarf an portablem Code haben, fällt mir auf, dass dies mapfileIhre Shell-Jobs ein wenig vereinfachen könnte.

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

Möglicherweise könnten Sie dies verdoppeln und es dann erneut tun, wenn Sie es in Streams getan haben - ich bin nicht krankhaft genug, um es herauszufinden - aber auf jeden Fall funktioniert es, wenn Sie es streamen.

Ich habe versucht, das printfGeneratorteil in Zeile zwei zu ändern :

printf \ b%.0b

Es funktioniert auch:

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

Vielleicht bin ich ein bisschen krankhaft. Ich benutze zero padding hereund addiere den vorherigen "$arg"Wert zum aktuellen "$arg"Wert. Ich komme weit über 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

Und wenn ich die catZeile so ändere :

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

Ich kann die Byteanzahl von erhalten. wc.Denken Sie daran, dies sind die Größen der einzelnen Schlüssel im argsArray. Die Gesamtgröße des Arrays ist die Summe aller dieser Werte.

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
Nein, nichts mit der Shell zu tun, es ist der Systemaufruf execve (2), der E2BIG zurückgibt, wenn ein einzelnes Argument größer als 128 KB ist.
Stéphane Chazelas

Bedenken Sie auch, dass es keine Beschränkung für Shell-Buildins gibt - funktioniert einwandfrei echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/null. Nur wenn Sie einen externen Befehl verwenden, liegt ein Problem vor.
Graeme

@Graeme Nun, ich habe das auch mit Katze gemacht - kein Problem. Die Variable wird am Ende in einem Heredoc ausgewertet. Siehe meine letzte Bearbeitung. Ich habe die Gesamtzahl auf 33 reduziert, weil ich jedes Mal den letzten Wert addiere. Und die Null-Polsterung ...
mikeserv

@StephaneChazelas - kann ich das umgehen, indem ich das Argument in einem Heredoc-Stream auswerte? Oder bashkomprimiert es irgendwie?
mikeserv

1
@mikeserv, ich kann nirgendwo in Ihrem Code sehen, dass eine Instanz von Ihnen einen Befehl mit einer großen Argumentliste ausführt. printfIst ein Builtin so wird es nicht ausgeführt , und AFAICT, Ihr catbekommt kein Argument.
Stéphane Chazelas
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.