Co określa maksymalny rozmiar pojedynczego argumentu polecenia?

48

Miałem wrażenie, że maksymalna długość pojedynczego argumentu nie była tutaj problemem, tyle że całkowity rozmiar ogólnej tablicy argumentów plus rozmiar środowiska, który jest ograniczony ARG_MAX. Pomyślałem więc, że coś takiego się powiedzie:

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

Ponieważ - 100jest to więcej niż wystarczające, aby uwzględnić różnicę między rozmiarem środowiska w powłoce a echoprocesem. Zamiast tego dostałem błąd:

bash: /bin/echo: Argument list too long

Po dłuższej zabawie odkryłem, że maksimum to pełny rząd heksów wielkości mniejszy:

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

Gdy jeden minus zostanie usunięty, błąd powraca. Pozornie maksimum dla pojedynczego argumentu jest w rzeczywistości, ARG_MAX/16a -1konta dla bajtu zerowego są umieszczane na końcu ciągu w tablicy argumentów.

Inną kwestią jest to, że gdy argument się powtarza, całkowity rozmiar tablicy argumentów może być bliższy ARG_MAX, ale wciąż nie do końca:

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

Użycie "${args[0]:6533}"tutaj powoduje wydłużenie ostatniego argumentu o 1 bajt i powoduje Argument list too longbłąd. Ta różnica prawdopodobnie nie zostanie uwzględniona w zależności od wielkości środowiska:

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

Pytania:

  1. Czy to jest poprawne zachowanie, czy może gdzieś jest błąd?
  2. Jeśli nie, czy takie zachowanie jest gdziekolwiek udokumentowane? Czy istnieje inny parametr, który określa maksimum dla pojedynczego argumentu?
  3. Czy to zachowanie jest ograniczone do Linuksa (czy nawet niektórych jego wersji)?
  4. Co odpowiada dodatkowej rozbieżności ~ 5 KB między faktycznym maksymalnym rozmiarem tablicy argumentów oraz przybliżonym rozmiarem środowiska i ARG_MAX?

Dodatkowe informacje:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
Graeme
źródło
5
W systemie Linux jest on zakodowany na stałe na 32 stronach (128kiB). Zobacz MAX_ARG_STRLEN w źródle.
Stéphane Chazelas
1
Większość informacji, których szukasz, znajduje się w odpowiedzi na CP: argumenty liczby plików źródłowych dla narzędzia do kopiowania
Stéphane Chazelas
1
Przynajmniej od mojej maszyny, getconf ARG_MAXzależy od prądu ulimit -s. Ustaw na nieograniczony i uzyskaj niesamowite 4611686018427387903 dla ARG_MAX.
derobert
dlaczego używasz ścieżki / proc / $$ / Environment? procfs w Linuksie obsługuje dowiązanie symboliczne / proc / self, następnie możesz użyć / proc / self / environment. wszystkie łatki przypisane do procesu, gdy ten sam proces to sprawdza, wskazuje na / proc / self. To samo dotyczy devfs, na przykład wewnątrz / dev, stdout urządzenia jest dowiązaniem symbolicznym do fd / 1, ale fd wskazuje na / self / fd. wiele systemów kopiuje to zachowanie.
Znik

Odpowiedzi:

48

Odpowiedzi

  1. Zdecydowanie nie jest to błąd.
  2. Parametrem określającym maksymalny rozmiar jednego argumentu jest MAX_ARG_STRLEN. Brak dokumentacji dla tego parametru oprócz komentarzy w 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
    

    Jak pokazano, Linux ma również (bardzo duży) limit liczby argumentów polecenia.

  3. Limit wielkości pojedynczego argumentu (który różni się od ogólnego limitu argumentów plus środowiska) wydaje się być specyficzny dla Linuksa. Ten artykuł zawiera szczegółowe porównanie ARG_MAXi odpowiedniki systemów uniksowych. MAX_ARG_STRLENjest omawiany w systemie Linux, ale nie ma wzmianki o żadnym równoważniku w innych systemach.

    Powyższy artykuł stwierdza również, że MAX_ARG_STRLENzostał wprowadzony w Linuksie 2.6.23, wraz z szeregiem innych zmian związanych z maksymalnymi argumentami poleceń (omówionymi poniżej). Log / diff dla zatwierdzenia można znaleźć tutaj .

  4. Nadal nie jest jasne, co tłumaczy dodatkową rozbieżność między wynikiem getconf ARG_MAXa rzeczywistą maksymalną możliwą wielkością argumentów plus środowisko. Powiązana odpowiedź Stephane'a Chazelasa sugeruje, że część przestrzeni jest uwzględniana przez wskaźniki do każdego z ciągów argumentów / środowiska. Jednak moje własne badanie sugeruje, że wskaźniki te nie są tworzone na początku execvewywołania systemowego, gdy może on nadal zwracać E2BIGbłąd do procesu wywoływania (chociaż wskaźniki do każdego argvciągu są z pewnością tworzone później).

    Ponadto ciągi są ciągłe w pamięci, o ile widzę, więc żadne luki w pamięci nie powodują tutaj wyrównania. Chociaż jest bardzo prawdopodobne, że będzie czynnikiem w tym, co zużywa dodatkową pamięć. Zrozumienie, co wykorzystuje dodatkową przestrzeń, wymaga bardziej szczegółowej wiedzy o tym, w jaki sposób jądro alokuje pamięć (co jest przydatną wiedzą, więc zbadam ją i zaktualizuję później).

Zamieszanie ARG_MAX

Od Linuksa 2.6.23 (w wyniku tego zatwierdzenia ) wprowadzono zmiany w sposobie obsługi maksymalnych argumentów poleceń, co odróżnia Linuksa od innych systemów uniksopodobnych. Oprócz dodawania MAX_ARG_STRLENi MAX_ARG_STRINGS, wynik getconf ARG_MAXteraz zależy od wielkości stosu i może być inny niż ARG_MAXw limits.h.

Zwykle wynikiem getconf ARG_MAXbędzie 1/4rozmiar stosu. Rozważmy następujące w bashużyciu ulimit, aby uzyskać rozmiar stosu:

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

Jednak powyższe zachowanie zostało nieznacznie zmienione przez to zatwierdzenie (dodane w Linuksie 2.6.25-rc4 ~ 121). ARG_MAXin limits.hsłuży teraz jako twarda dolna granica wyniku getconf ARG_MAX. Jeśli rozmiar stosu jest ustawiony tak, że 1/4rozmiar stosu jest mniejszy niż ARG_MAXw limits.h, wówczas limits.hzostanie użyta wartość:

$ 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

Zauważ też, że jeśli rozmiar stosu ustawiony jest poniżej minimum możliwego ARG_MAX, wówczas rozmiar stosu ( RLIMIT_STACK) staje się górną granicą wielkości argumentu / środowiska przed E2BIGzwróceniem (chociaż getconf ARG_MAXnadal będzie wyświetlać wartość w limits.h).

Ostatnią rzeczą do zapamiętania jest to, że jeśli jądro jest zbudowane bez CONFIG_MMU(obsługa sprzętu do zarządzania pamięcią), to sprawdzanie ARG_MAXjest wyłączone, więc limit nie ma zastosowania. Chociaż MAX_ARG_STRLENi MAX_ARG_STRINGSnadal mają zastosowanie.

Dalsza lektura

Graeme
źródło
2
To dobra odpowiedź, na pewno lepsza niż moja - głosowałem za nią. Ale odpowiedź, o którą prosimy, nie zawsze jest odpowiedzią, którą powinniśmy otrzymać - dlatego pytamy, ponieważ nie wiemy. Nie rozwiązuje problemu z przepływem pracy, który doprowadził Cię do tego problemu. Pokazuję, jak można to złagodzić we własnej odpowiedzi, oraz w jaki sposób argumenty łańcucha zmiennej pojedynczej powłoki o długości ponad 2 Mb mogą być przekazywane do nowo wykonywanych procesów za pomocą zaledwie kilku wierszy skryptu powłoki.
mikeserv
Stworzyłem skrypt Pythona, który pokazuje 32 * 4KB stron = limit 128 KB zmiennych środowiskowych w domyślnym Linuksie.
nh2
0

W eglibc-2.18/NEWS

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

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

W linux/include/uapi/linux/limits.h

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

I 131072jest twój $(getconf ARG_MAX)/16-1, być może powinieneś zacząć od 0.

Masz do czynienia z Glibc i Linuksem. Dobrze byłoby załatać getconf również po to, aby otrzymać „właściwą” ARG_MAXwartość.

Edytować:

Aby trochę wyjaśnić (po krótkiej, ale gorącej dyskusji)

ARG_MAXStałą, która jest określona w limits.hdaje maksymalną długość jednego argumentu podjętej Exec.

getconf ARG_MAXPolecenie zwraca maksymalną wartość skumulowanej wielkości argumenty i środowiska wielkości przekazanego Exec.


źródło
2
Że ARG_MAX jest minimalnym gwarantowanym limitem wielkości arg + env, nie jest to maksymalny rozmiar pojedynczego argumentu (chociaż okazuje się, że ma taką samą wartość jak MAX_ARG_STRLEN)
Stéphane Chazelas 20'14
Czy masz datę na swój eglibc-2.18/NEWSfragment? Dobrze byłoby przypiąć to do konkretnej wersji jądra.
Graeme
@StephaneChazelas: Jestem po prostu zbyt leniwy, aby znaleźć część, ale jeśli arg przekroczy maksymalną wartość, nie jest konieczne ustalanie rozmiaru env.
@Graeme: Mam też kilka starszych linuksów, w których wartość getconf pokazuje 131072. Myślę, że należy to do nowszych linuxów z eglibc> ?? tylko. Gratulacje, znalazłeś błąd BTW.
2
Patrzysz na kod glibc, tutaj nie ma to znaczenia. Libc nie obchodzi, jaki rozmiar argumentów przekazujesz. Cytowany kod dotyczy sysconf, interfejsu API, który daje użytkownikom pojęcie o maksymalnym rozmiarze (cokolwiek to oznacza) argumentu argv + env przekazywanego do execve (2). Jest to jądro, które akceptuje lub nie listę arg i env przekazywaną przez wywołanie systemowe execve (). Chodzi getconf ARG_MAXo skumulowany rozmiar arg + env (zmienna w najnowszym Linuksie, zobacz ulimit -si inne pytanie, które podłączyłem), nie dotyczy maksymalnej długości pojedynczego argumentu, dla którego nie ma zapytania sysconf / getconf.
Stéphane Chazelas
-1

Więc @StephaneChazelas słusznie poprawia mnie w komentarzach poniżej - sama powłoka nie określa w żaden sposób maksymalnego rozmiaru argumentu dozwolonego przez twój system, ale raczej jest ustawiona przez twoje jądro.

Jak już kilka innych powiedziało, wydaje się, że jądro ogranicza do 128 kb maksymalnego rozmiaru argumentu, który można przekazać nowemu procesowi z dowolnego innego miejsca przy pierwszym uruchomieniu. Ten problem występuje szczególnie z powodu wielu zagnieżdżonych $(command substitution)podpowłok, które muszą być wykonywane w miejscu i przekazywać całość swoich wyników od jednego do drugiego.

I ten rodzaj dzikiego zgadywania, ale ponieważ rozbieżność ~ 5kb wydaje się tak zbliżona do standardowego rozmiaru strony systemowej, podejrzewam, że jest poświęcony bashzastosowaniom strony do obsługi podpowłoki $(command substitution)wymaganej do ostatecznego dostarczenia jej wyników i / lub stos funkcji, który wykorzystuje do kojarzenia array tabletwoich danych. Mogę tylko założyć, że żadne nie jest darmowe.

Poniżej pokazuję, że chociaż może to być trochę trudne, możliwe jest przekazywanie bardzo dużych wartości zmiennych powłoki do nowych procesów podczas wywoływania, o ile można to zrobić strumieniowo.

W tym celu użyłem przede wszystkim rur. Ale oceniłem również tablicę powłok w here-documentwskazanym cat's stdin. poniżej Wyniki.

Ale ostatnia uwaga - jeśli nie potrzebujesz szczególnego kodu przenośnego, uderza mnie to, co mapfilemoże nieco uprościć twoje zadania powłoki.

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

Być może możesz to podwoić, a następnie zrobić to ponownie, jeśli zrobisz to w strumieniach - nie jestem wystarczająco chorobliwy, aby się dowiedzieć - ale na pewno działa, jeśli go przesyłasz.

Próbowałem zmienić printfczęść generatora w drugim wierszu na:

printf \ b%.0b

Działa również:

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

Więc może jestem trochę chorobliwy. Używam zero padding herei dodaje poprzednią "$arg"wartość do bieżącej "$arg"wartości. Dostaję znacznie więcej niż 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

A jeśli zmienię catlinię, aby wyglądała tak:

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

Mogę uzyskać liczbę bajtów z wc.Pamiętaj, że są to rozmiary każdego klucza w argstablicy. Całkowity rozmiar tablicy jest sumą wszystkich tych wartości.

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
źródło
2
Nie, nie ma to nic wspólnego z powłoką, to wywołanie systemowe execve (2) zwracające E2BIG, gdy pojedynczy argument przekracza 128kiB.
Stéphane Chazelas
Weź również pod uwagę, że nie ma ograniczenia na wbudowane powłoki - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullbędą działać poprawnie. Problem występuje tylko wtedy, gdy używasz zewnętrznego polecenia.
Graeme
@Graeme Cóż, zrobiłem to również z kotem - nie ma problemu. Zmienna jest oceniana w heredoc na końcu. Zobacz moją ostatnią edycję. Zmniejszyłem całkowitą liczbę do 33, ponieważ za każdym razem dodam ostatnią wartość. I wypełnienie zerowe ...
mikeserv
@StephaneChazelas - więc czy mam to obejść, oceniając argument w strumieniu heredoc? A może bashjakoś to kompresuje?
mikeserv
1
@ Mikeserv, nie widzę nigdzie w twoim kodzie żadnego wystąpienia wykonania polecenia z dużą listą argumentów. printfjest wbudowany, więc nie jest wykonywany , a AFAICT catnie podaje żadnego argumentu.
Stéphane Chazelas