Qu'est-ce qui définit la taille maximale d'un seul argument de commande?

49

J'avais l'impression que la longueur maximale d'un seul argument n'était pas le problème ici, mais plutôt la taille totale du tableau d'arguments global plus la taille de l'environnement, qui est limité à ARG_MAX. Ainsi, j'ai pensé que quelque chose comme ce qui suit pourrait réussir:

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

En - 100plus d’être suffisant pour prendre en compte la différence entre la taille de l’environnement dans le shell et le echoprocessus. Au lieu de cela, j'ai eu l'erreur:

bash: /bin/echo: Argument list too long

Après avoir joué pendant un moment, j'ai trouvé que le maximum était d'un ordre de grandeur hexagonal plus petit:

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

Lorsque le moins un est supprimé, l'erreur revient. Apparemment, le maximum pour un seul argument est en fait ARG_MAX/16et les -1comptes de l'octet nul placés à la fin de la chaîne dans le tableau d'arguments.

Un autre problème est que lorsque l'argument est répété, la taille totale du tableau d'arguments peut être plus proche de ARG_MAX, mais pas encore tout à fait 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

Utiliser "${args[0]:6533}"ici rallonge le dernier argument d'un octet et donne l' Argument list too longerreur. Il est peu probable que cette différence soit prise en compte par la taille de l'environnement:

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

Des questions:

  1. Ce comportement est-il correct ou existe-t-il un bogue quelque part?
  2. Si non, ce comportement est-il documenté quelque part? Existe-t-il un autre paramètre définissant le maximum pour un seul argument?
  3. Ce comportement est-il limité à Linux (ou même à des versions particulières de ce type)?
  4. Qu'est-ce qui explique l'écart de ~ 5 Ko supplémentaire entre la taille maximale réelle du tableau d'arguments plus la taille approximative de l'environnement et ARG_MAX?

Information additionnelle:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
Graeme
la source
5
Sous Linux, il est codé en dur sur 32 pages (128 Ko). Voir MAX_ARG_STRLEN dans le source.
Stéphane Chazelas
1
La plupart des informations que vous recherchez se trouvent dans cette réponse au CP: nombre maximal de fichiers source arguments pour la copie utilitaire
Stéphane Chazelas
1
Au moins sur ma machine, getconf ARG_MAXdépend du courant ulimit -s. Réglez-le à illimité et obtenez un incroyable 4611686018427387903 pour ARG_MAX.
Derobert
pourquoi utilisez-vous chemin / proc / $$ / environ? procfs in linux supporte symlink / proc / self, vous pouvez alors utiliser / proc / self / environ. tous les correctifs affectés au processus, lorsque le même processus le vérifie, pointe vers / proc / self. Il en va de même avec devfs, par exemple dans / dev, device stdout est un lien symbolique vers fd / 1, mais fd pointe sur / self / fd. de nombreux systèmes copient ce comportement.
Znik

Réponses:

50

Réponses

  1. Certainement pas un bug.
  2. Le paramètre qui définit la taille maximale d'un argument est MAX_ARG_STRLEN. Il n'y a pas de documentation pour ce paramètre autre que les commentaires dans 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
    

    Comme on le voit, Linux a également une limite (très grande) sur le nombre d'arguments d'une commande.

  3. Une limite sur la taille d'un seul argument (qui diffère de la limite globale sur les arguments plus l'environnement) semble être spécifique à Linux. Cet article donne une comparaison détaillée des ARG_MAXéquivalents sous Unix. MAX_ARG_STRLENest discuté pour Linux, mais aucun équivalent n’est mentionné sur d’autres systèmes.

    L'article ci-dessus indique également qu'il a MAX_ARG_STRLENété introduit dans Linux 2.6.23, ainsi qu'un certain nombre d'autres modifications relatives aux maximums d'argument de commande (décrits ci-dessous). Le journal / diff pour le commit peut être trouvé ici .

  4. On ne sait toujours pas ce qui explique l'écart supplémentaire entre le résultat de getconf ARG_MAXet la taille maximale possible réelle des arguments plus l'environnement. La réponse associée de Stéphane Chazelas suggère qu'une partie de l'espace est expliquée par des pointeurs vers chacune des chaînes argument / environment. Cependant, ma propre enquête suggère que ces pointeurs ne sont pas créés au début de l' execveappel système, car il peut toujours renvoyer une E2BIGerreur au processus appelant (bien que les pointeurs vers chaque argvchaîne soient certainement créés plus tard).

    En outre, les chaînes sont contiguës en mémoire aussi loin que je peux voir, donc aucun espace mémoire nécessaire pour l'alignement ici. Bien que très probablement être un facteur au sein de quoi que ce soit, utilise la mémoire supplémentaire. Comprendre ce qui utilise cet espace supplémentaire nécessite une connaissance plus détaillée de la façon dont le noyau alloue de la mémoire (une connaissance utile à posséder, je vais donc étudier et mettre à jour ultérieurement).

ARG_MAX Confusion

Depuis Linux 2.6.23 (à la suite de cette validation ), des modifications ont été apportées à la façon dont les maximums d'argument de commande sont gérés, ce qui différencie Linux des autres systèmes de type Unix. En plus d'ajouter MAX_ARG_STRLENet MAX_ARG_STRINGS, le résultat de getconf ARG_MAXmaintenant dépend de la taille de la pile et peut être différent de celui de ARG_MAXin limits.h.

Normalement, le résultat de getconf ARG_MAXsera 1/4de la taille de la pile. Considérez ce qui suit en bashutilisant ulimitpour obtenir la taille de la pile:

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

Cependant, le comportement ci-dessus a été légèrement modifié par ce commit (ajouté dans Linux 2.6.25-rc4 ~ 121). ARG_MAXin limits.hsert maintenant de limite inférieure dure sur le résultat de getconf ARG_MAX. Si la taille de la pile est définie de telle sorte que 1/4sa taille soit inférieure ARG_MAXà limits.hla limits.hvaleur in , la valeur sera utilisée:

$ 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

Notez également que si la taille de la pile définie est inférieure au minimum possible ARG_MAX, la taille de la pile ( RLIMIT_STACK) devient la limite supérieure de la taille de l’argument / de l’environnement avant qu’elle ne E2BIGsoit renvoyée (bien que getconf ARG_MAXla valeur soit toujours affichée limits.h).

Une dernière chose à noter est que si le noyau est construit sans CONFIG_MMU(prise en charge du matériel de gestion de la mémoire), la vérification de ARG_MAXest désactivée et la limite ne s'applique donc pas. Bien MAX_ARG_STRLENet MAX_ARG_STRINGStoujours appliquer.

Lectures complémentaires

Graeme
la source
2
C'est une bonne réponse, certainement meilleure que la mienne - je l'ai votée. Mais la réponse que nous demandons n’est pas toujours la réponse que nous devrions obtenir - c’est pourquoi nous demandons, car nous ne savons pas. Cela ne résout pas le problème de votre flux de travail qui vous a mis en tête avec ce problème en premier lieu. Je montre comment cela pourrait être atténué dans ma propre réponse et comment des arguments de chaîne de variable d'environnement d'une longueur supérieure à 2 Mo peuvent être transmis aux processus nouvellement exécutés avec seulement quelques lignes de script shell.
mikeserv
J'ai créé un script Python qui illustre les pages 32 * 4 Ko = 128 Ko de variables d'environnement sur Linux par défaut.
nh2
0

Dans eglibc-2.18/NEWS

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

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

Dans linux/include/uapi/linux/limits.h

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

Et 131072est votre $(getconf ARG_MAX)/16-1, peut - être vous devriez commencer à 0.

Vous avez affaire à la glibc et à Linux. Il serait bon de mettre à jour getconf afin de récupérer la "bonne" ARG_MAXvaleur.

Modifier:

Pour clarifier un peu (après une discussion brève mais chaude)

La ARG_MAXconstante définie dans limits.hdonne la longueur maximale d'un argument passé avec exec.

La getconf ARG_MAXcommande renvoie la valeur maximale de la taille des arguments cumulés et de la taille de l'environnement transmise à exec.


la source
2
ARG_MAX est le minimum garanti pour la limite de taille arg + env, il ne s'agit pas de la taille maximale d'un seul argument (bien qu'il s'agisse de la même valeur que MAX_ARG_STRLEN)
Stéphane Chazelas
Avez-vous une date pour votre eglibc-2.18/NEWSextrait? Il serait bon d’attacher cela à une version particulière du noyau.
Graeme
@StephaneChazelas: Je suis trop paresseux pour trouver la pièce, mais si arg dépasse la valeur maximale, il n'est pas nécessaire de déterminer la taille de l'env.
@Graeme: J'ai aussi d'anciens linux en cours d'exécution où la valeur de getconf indique 131072. Je pense que cela appartient aux linux les plus récents avec eglibc> ?? seulement. Félicitations, vous avez trouvé un bug BTW.
2
Vous regardez le code glibc, ce n'est pas pertinent ici. La bibliothèque ne se soucie pas de la taille des arguments que vous transmettez. Le code que vous citez concerne sysconf, une API qui donne aux utilisateurs une idée de la taille maximale (peu importe ce que cela signifie) de argv + env transmise à un execve (2). C'est le noyau qui accepte ou non les listes arg et env transmises par un appel système execve (). Il getconf ARG_MAXs'agit de la taille cumulée de arg + env (variable dans Linux récent, voir ulimit -set l'autre question que j'ai liée), il ne s'agit pas de la longueur maximale d'un argument pour lequel il n'y a pas de requête sysconf / getconf.
Stéphane Chazelas
-1

Donc, @StephaneChazelas me corrige à juste titre dans les commentaires ci-dessous - le shell lui-même ne dicte en aucune manière la taille maximale des arguments autorisée par votre système, mais elle est plutôt définie par votre noyau.

Comme plusieurs autres l'ont déjà dit, il semble que le noyau limite à 128 Ko la taille d'argument maximale que vous pouvez donner à un nouveau processus depuis n'importe quel autre lors de sa première exécution. Vous rencontrez ce problème spécifiquement en raison des nombreux $(command substitution)sous-shell imbriqués qui doivent s'exécuter en place et transmettre l'intégralité de leur sortie de l'un à l'autre.

Et celle-ci est plutôt aléatoire, mais comme l’écart de ~ 5kb semble si proche de la taille de page système standard, j’ai le sentiment qu’elle est dédiée à la page bashutilisée pour gérer le sous-shell dont vous avez $(command substitution)besoin pour délivrer sa sortie et / ou la pile de fonctions qu’elle utilise pour associer array tablevos données à vos données. Je ne peux que supposer que ni l'un ni l'autre n'est gratuit.

Je démontre ci-dessous que, bien que cela puisse être un peu délicat, il est possible de transmettre de très grandes valeurs de variables shell à de nouveaux processus lors de leur invocation, tant que vous pouvez gérer leur diffusion en continu.

Pour ce faire, j'ai principalement utilisé des pipes. Mais j'ai aussi évalué le tableau de shell dans Résultats here-documentpointés cat's stdin.ci-dessous.

Mais une dernière remarque - si vous n'avez pas particulièrement besoin de code portable, cela me mapfilesimplifiera peut - être un peu vos tâches 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

Vous pourriez peut-être doubler ce nombre et le refaire si vous le faisiez dans des flux - je ne suis pas assez morbide pour le savoir - mais définitivement cela fonctionne si vous le diffusez en continu.

J'ai essayé de changer la printfpartie générateur de la ligne deux en:

printf \ b%.0b

Cela fonctionne aussi:

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

Alors peut-être que je suis un peu morbide. J'utilise zero padding hereet ajoute la "$arg"valeur précédente à la "$arg"valeur actuelle . Je vais bien au-delà 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

Et si je change la catligne pour ressembler à ceci:

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

Je peux obtenir le nombre d'octets à partir de wc.Rappelez-vous qu'il s'agit de la taille de chaque clé du argstableau. La taille totale du tableau est la somme de toutes ces valeurs.

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
la source
2
Non, rien à voir avec le shell, c'est l'appel système execve (2) qui renvoie E2BIG lorsqu'un seul argument dépasse 128kiB.
Stéphane Chazelas
Pensez également qu’il n’ya pas de limite au nombre d’intégrations dans le shell - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullcela fonctionnera bien. Ce n'est que lorsque vous utilisez une commande externe qu'il y a un problème.
Graeme
@Graeme Eh bien, j'ai aussi fait cela avec le chat - pas de problème. La variable est évaluée dans un heredoc à la fin. Voir ma dernière édition. J'ai réduit le nombre total à 33 parce que j'ajoute la dernière valeur à chaque fois. Et le zéro padding ...
mikeserv
@StephaneChazelas - Comment puis-je contourner cela en évaluant l'argument dans un courant hérédoc? Ou est- bashil en train de le compresser?
mikeserv
1
@mikeserv, je ne vois nulle part dans votre code une instance quelconque exécutant une commande avec une liste d'arguments volumineuse. printfest une commande interne, elle n'est donc pas exécutée et AFAICT, cataucun argument n'est fourni à votre .
Stéphane Chazelas