¿Qué define el tamaño máximo para un argumento de comando único?


49

Tenía la impresión de que la longitud máxima de un solo argumento no era el problema aquí, sino el tamaño total de la matriz de argumentos general más el tamaño del entorno, que se limita a ARG_MAX. Por lo tanto, pensé que algo como lo siguiente tendría éxito:

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 el - 100ser más que suficiente para explicar la diferencia entre el tamaño del entorno en el shell y el echoproceso. En cambio, recibí el error:

bash: /bin/echo: Argument list too long

Después de jugar un rato, descubrí que el máximo era un orden hexadecimal completo de menor magnitud:

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

Cuando se elimina el menos uno, el error regresa. Aparentemente, el máximo para un solo argumento es en realidad ARG_MAX/16y las -1cuentas para el byte nulo colocado al final de la cadena en la matriz de argumentos.

Otro problema es que cuando se repite el argumento, el tamaño total de la matriz de argumentos puede estar más cerca ARG_MAX, pero aún no del todo:

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}"aquí hace que el último argumento 1 byte sea más largo y da el Argument list too longerror. Es improbable que esta diferencia se deba al tamaño del entorno dado:

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

Preguntas:

  1. ¿Es este comportamiento correcto o hay algún error en alguna parte?
  2. Si no, ¿se documenta este comportamiento en alguna parte? ¿Hay otro parámetro que defina el máximo para un solo argumento?
  3. ¿Se limita este comportamiento a Linux (o incluso a versiones particulares de este)?
  4. ¿Qué explica la discrepancia adicional de ~ 5 KB entre el tamaño máximo real de la matriz de argumentos más el tamaño aproximado del entorno y ARG_MAX?

Información adicional:

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

55
En Linux, está codificado en 32 páginas (128 kB). Ver MAX_ARG_STRLEN en la fuente.
Stéphane Chazelas


1
Al menos en mi máquina, getconf ARG_MAXdepende de la corriente ulimit -s. Configúrelo como ilimitado y obtenga un increíble 4611686018427387903 para ARG_MAX.
derobert


¿por qué usas path / proc / $$ / environmental? procfs en linux admite el enlace simbólico / proc / self, luego puede usar / proc / self / environmental. todos los parches asignados al proceso, cuando el mismo proceso verifica esto, apunta a / proc / self. Lo mismo ocurre con devfs, por ejemplo, dentro de / dev, stdout del dispositivo es enlace simbólico a fd / 1, pero fd apunta a / self / fd. Muchos sistemas copian este comportamiento.
Znik

Respuestas:


50

Respuestas

  1. Definitivamente no es un error.
  2. El parámetro que define el tamaño máximo para un argumento es MAX_ARG_STRLEN. No hay documentación para este parámetro aparte de los comentarios en 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 se muestra, Linux también tiene un límite (muy grande) en el número de argumentos para un comando.

  3. Un límite en el tamaño de un argumento único (que difiere del límite general en argumentos más el entorno) parece ser específico de Linux. Este artículo ofrece una comparación detallada ARG_MAXy equivalentes en sistemas similares a Unix. MAX_ARG_STRLENse discute para Linux, pero no se menciona ningún equivalente en ningún otro sistema.

    El artículo anterior también establece que MAX_ARG_STRLENse introdujo en Linux 2.6.23, junto con una serie de otros cambios relacionados con los máximos de argumentos de comando (que se analizan a continuación). El log / diff para el commit se puede encontrar aquí .

  4. Todavía no está claro qué explica la discrepancia adicional entre el resultado getconf ARG_MAXy el tamaño real máximo posible de argumentos más el entorno. La respuesta relacionada de Stephane Chazelas sugiere que parte del espacio se explica por punteros a cada una de las cadenas de argumento / entorno. Sin embargo, mi propia investigación sugiere que estos punteros no se crean temprano en la execvellamada del sistema cuando aún puede devolver un E2BIGerror al proceso de llamada (aunque los punteros a cada argvcadena ciertamente se crean más adelante).

    Además, las cadenas son contiguas en la memoria hasta donde puedo ver, por lo que no hay huecos de memoria debido a la alineación aquí. Aunque es muy probable que sea un factor dentro de lo que sea que use la memoria extra. Comprender qué utiliza el espacio extra requiere un conocimiento más detallado de cómo el núcleo asigna la memoria (que es un conocimiento útil, por lo que investigaré y actualizaré más adelante).

ARG_MAX Confusión

Desde Linux 2.6.23 (como resultado de esta confirmación ), ha habido cambios en la forma en que se manejan los máximos de argumentos de comando, lo que hace que Linux difiera de otros sistemas similares a Unix. Además de agregar MAX_ARG_STRLENy MAX_ARG_STRINGS, el resultado de getconf ARG_MAXahora depende del tamaño de la pila y puede ser diferente de ARG_MAXin limits.h.

Normalmente el resultado de getconf ARG_MAXserá 1/4del tamaño de la pila. Considere lo siguiente al bashusar ulimitpara obtener el tamaño de la pila:

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

Sin embargo, el comportamiento anterior fue cambiado ligeramente por este commit (agregado en Linux 2.6.25-rc4 ~ 121). ARG_MAXen limits.hahora sirve como un límite inferior duro en el resultado de getconf ARG_MAX. Si el tamaño de la pila se establece de manera que 1/4el tamaño de la pila sea menor que ARG_MAXen limits.h, entonces se limits.husará el valor:

$ 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

Tenga en cuenta también que si el tamaño de la pila se establece por debajo del mínimo posible ARG_MAX, entonces el tamaño de la pila ( RLIMIT_STACK) se convierte en el límite superior del tamaño del argumento / entorno antes de que E2BIGse devuelva (aunque getconf ARG_MAXtodavía mostrará el valor en limits.h).

Una última cosa a tener en cuenta es que si el núcleo se construye sin CONFIG_MMU(soporte para hardware de administración de memoria), entonces la comprobación de ARG_MAXestá desactivada, por lo que el límite no se aplica. Aunque MAX_ARG_STRLENy MAX_ARG_STRINGStodavía se aplican.

Otras lecturas


2
Esta es una buena respuesta, ciertamente mejor que la mía. Lo voté. Pero la respuesta que pedimos no siempre es la respuesta que debemos obtener, es por eso que estamos preguntando, porque no lo sabemos. No trata el problema con su flujo de trabajo que lo enfrentó con este problema en primer lugar. Demuestro cómo se puede mitigar eso en mi propia respuesta, y cómo los argumentos de cadena variable de shell único de más de 2 mb de longitud se pueden pasar a procesos recién ejecutados con solo un par de líneas de script de shell.
mikeserv

Creé un script de Python que muestra las páginas de 32 * 4KB = límite de 128 KB de variables de entorno en Linux predeterminado.
nh2

0

En eglibc-2.18/NEWS

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

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

En linux/include/uapi/linux/limits.h

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

Y 131072es tu $(getconf ARG_MAX)/16-1, quizás deberías comenzar en 0.

Estás lidiando con glibc y Linux. Sería bueno parchear getconf también para obtener el ARG_MAXvalor "correcto" devuelto.

Editar:

Para aclarar un poco (después de una discusión corta pero caliente)

La ARG_MAXconstante que se define en limits.h, da la longitud máxima de un argumento pasado con exec.

El getconf ARG_MAXcomando devuelve el valor máximo del tamaño de argumentos acumulados y el tamaño del entorno pasado a exec.


2
Que ARG_MAX es el mínimo garantizado para el límite de tamaño arg + env, no es el tamaño máximo de un solo argumento (aunque resulta ser el mismo valor que MAX_ARG_STRLEN)
Stéphane Chazelas

¿Tienes una fecha para tu eglibc-2.18/NEWSfragmento? Sería bueno fijar esto en una versión particular del kernel.
Graeme

@StephaneChazelas: soy demasiado vago para encontrar la pieza, pero si arg excede el valor máximo, no es necesario determinar el tamaño del entorno.

@Graeme: También tengo algunos linuxes antiguos en ejecución donde el valor getconf muestra 131072. Creo que esto pertenece a linuxes más nuevos con eglibc> ?? solamente. Felicidades, encontraste un error BTW.

2
Estás viendo el código glibc, eso es irrelevante aquí. Al libc no le importa qué tamaño de argumentos estás pasando. El código que está citando es sobre sysconf, una API para dar a los usuarios una idea del tamaño máximo (lo que sea que eso signifique) de argv + env pasado a un execve (2). Es el núcleo que acepta o no la lista arg y env pasada a lo largo de una llamada al sistema execve (). Se getconf ARG_MAXtrata del tamaño acumulativo de arg + env (variable en Linux reciente, vea ulimit -sy la otra pregunta que vinculé), no se trata de la longitud máxima de un solo argumento para el que no hay una consulta sysconf / getconf.
Stéphane Chazelas

-1

Entonces @StephaneChazelas me corrige correctamente en los comentarios a continuación: el shell en sí no dicta de ninguna manera el tamaño máximo de argumento permitido por su sistema, sino que lo establece su núcleo.

Como ya han dicho varios otros, parece que el núcleo limita a 128 kb el tamaño máximo de argumento que puede pasar a un nuevo proceso desde cualquier otro cuando lo ejecute por primera vez. Experimenta este problema específicamente debido a las muchas $(command substitution)subcapas anidadas que deben ejecutarse en su lugar y entregar la totalidad de su salida de una a la siguiente.

Y esta es una especie de suposición descabellada, pero como la discrepancia de ~ 5kb parece tan cercana al tamaño de página estándar del sistema, sospecho que está dedicada a los bashusos de la página para manejar la subshell que $(command substitution)necesita para finalmente entregar su salida y / o la pila de funciones que emplea para asociar tu array tablecon tus datos. Solo puedo suponer que ninguno viene gratis.

Demuestro a continuación que, si bien puede ser un poco complicado, es posible pasar valores de variables de shell muy grandes a nuevos procesos en la invocación, siempre que pueda lograr transmitirlos.

Para hacerlo, utilicé principalmente tuberías. Pero también evalué la matriz de shell en un here-documentapuntado en cat's stdin. Resultados a continuación.

Pero una última nota: si no tiene una necesidad particular de código portátil, me parece que mapfilepodría simplificar un poco sus trabajos 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

Posiblemente podría duplicar esto y luego volver a hacerlo si lo hizo en las transmisiones, no soy lo suficientemente mórbido como para descubrirlo, pero definitivamente funciona si lo transmite.

Intenté cambiar la printfparte del generador en la línea dos para:

printf \ b%.0b

También funciona:

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

Entonces quizás soy un poco morbosa. Yo uso zero padding herey agrego el "$arg"valor anterior al valor actual "$arg". Llego mucho más allá 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

Y si cambio la catlínea para que se vea así:

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

Puedo obtener recuentos de bytes de wc.Recuerde que estos son los tamaños de cada clave en la argsmatriz. El tamaño total de la matriz es la suma de todos estos 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

2
No, nada que ver con el shell, es la llamada al sistema execve (2) que devuelve E2BIG cuando un solo argumento supera los 128 kB.
Stéphane Chazelas

Tenga en cuenta también que no hay límite para las construcciones de conchas: echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullfuncionará bien. Es solo cuando usa un comando externo que hay un problema.
Graeme

@Graeme Bueno, también hice esto con cat, no hay problema. La variable se evalúa en un heredoc al final. Ver mi última edición. Reduje el recuento total a 33 porque agrego el último valor cada vez. Y el relleno cero ...
mikeserv

@StephaneChazelas: entonces, ¿estoy evitando eso evaluando el argumento en una secuencia heredoc? ¿O lo está bashcomprimiendo de alguna manera?
mikeserv

1
@mikeserv, no puedo ver en ninguna parte de tu código ninguna instancia de ti ejecutando un comando con una gran lista de argumentos. printfes una orden interna por lo que no se ejecuta , y AFAICT, el catno se da ningún argumento.
Stéphane Chazelas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.