Що визначає максимальний розмір для одного аргументу команди?


48

У мене було враження, що максимальна довжина одного аргументу тут не є проблемою настільки, як загальний розмір загального масиву аргументів плюс розмір середовища, який обмежений ARG_MAX. Таким чином, я думав, що щось подібне вдасться:

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

Маючи це - 100більше, ніж достатньо, щоб врахувати різницю між розмірами середовища в оболонці та echoпроцесом. Натомість я отримав помилку:

bash: /bin/echo: Argument list too long

Погравши деякий час, я виявив, що максимум - це повна шістнадцятковий порядок менше:

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

Коли мінус один видаляється, помилка повертається. Мабуть, максимум для одного аргументу є насправді, ARG_MAX/16а -1рахунки для нульового байту розміщуються в кінці рядка в масиві аргументів.

Інша проблема полягає в тому, що коли аргумент повторюється, загальний розмір масиву аргументів може бути ближчим ARG_MAX, але все ще не зовсім таким:

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

Використання "${args[0]:6533}"тут робить останній аргумент на 1 байт довшим і видає Argument list too longпомилку. Цю різницю навряд чи можна врахувати за розміром даного середовища:

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

Запитання:

  1. Це правильна поведінка, чи є помилка десь?
  2. Якщо ні, то чи є така поведінка десь задокументована? Чи є інший параметр, який визначає максимум для одного аргументу?
  3. Чи обмежується така поведінка лише Linux (або навіть певними версіями такої версії)?
  4. Що пояснює додаткову ~ 5 КБ розбіжність між фактичним максимальним розміром масиву аргументу плюс приблизним розміром середовища та ARG_MAX?

Додаткова інформація:

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

5
В Linux важко закодовано на 32 сторінки (128 КБ). Дивіться MAX_ARG_STRLEN у джерелі.
Стефан Шазелас

1
Більшість інформації, яку ви шукаєте, є у цій відповіді на CP: Максимальні аргументи
числових

1
Принаймні від моєї машини, getconf ARG_MAXзалежить від струму ulimit -s. Встановіть його необмежену кількість і отримайте дивовижні 4611686018427387903 для ARG_MAX.
дероберт


чому ви використовуєте path / proc / $$ / environment? procfs в Linux підтримує symlink / proc / self, тоді ви можете використовувати / proc / self / Environment. всі патчі, призначені для обробки, коли той самий процес перевіряє це, вказує на / proc / self. Теж саме з devfs, наприклад, всередині / dev, stdout пристрою є символьним посиланням на fd / 1, але fd вказує на / self / fd. багато систем копіюють таку поведінку.
Znik

Відповіді:


48

Відповіді

  1. Однозначно не помилка.
  2. Параметр, який визначає максимальний розмір для одного аргументу, є MAX_ARG_STRLEN. Для цього параметра немає жодної документації, окрім коментарів у 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
    

    Як показано, у Linux також є (дуже великий) обмеження кількості аргументів команди.

  3. Обмеження розміру одного аргументу (який відрізняється від загального обмеження на аргументи плюс оточення), схоже, є специфічним для Linux. У цій статті подано детальне порівняння ARG_MAXта їх еквіваленти для Unix-подібних систем. MAX_ARG_STRLENобговорюється для Linux, але жодного еквівалента в жодних інших системах немає.

    У цій статті також зазначено, що вона MAX_ARG_STRLENбула введена в Linux 2.6.23, а також ряд інших змін, що стосуються максимумів аргументів команди (обговорюються нижче). Журнал / різниця для комісії можна знайти тут .

  4. Досі не зрозуміло, що пояснює додаткову невідповідність між результатом getconf ARG_MAXта фактичним максимально можливим розміром аргументів плюс середовищем. Відповідна відповідь Стефана Шазеласа передбачає, що частина простору припадає на покажчики до кожного з аргументів / середовищних рядків. Однак моє власне дослідження дозволяє стверджувати, що ці покажчики не створюються на початку execveсистемного виклику, коли це все ще може повернути E2BIGпомилку в процесі виклику (хоча покажчики на кожну argvрядок, безумовно, створюються пізніше).

    Крім того, наскільки я бачу, рядки є суміжними в пам'яті, тому жодні прогалини в пам'яті не вирівнюються. Хоча це, швидше за все, є фактором у межах, що не потребує додаткової пам'яті. Розуміння того, що використовує додатковий простір, вимагає більш детальних знань про те, як ядро ​​виділяє пам'ять (що корисні знання мати, тому я буду досліджувати та оновлювати пізніше).

Плутанина ARG_MAX

З моменту Linux 2.6.23 (в результаті цього зобов'язання ) відбулися зміни в способі обробки максимумів аргументів команд, що робить Linux відмінним від інших Unix-подібних систем. На додаток до додавання MAX_ARG_STRLENі MAX_ARG_STRINGS, результат getconf ARG_MAXтепер залежить від розміру стека і може відрізнятися від ARG_MAXв limits.h.

Зазвичай результат getconf ARG_MAXбуде 1/4розміром стека. Розглянемо наступне в bashвикористанні , ulimitщоб отримати розмір стека:

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

Однак дане поведінка була трохи змінено цим Комміт (додано в Linux 2.6.25-rc4 ~ 121). ARG_MAXв limits.hданий час служить жорсткою нижньою межею на результат getconf ARG_MAX. Якщо розмір стека встановлений таким, що 1/4розмір стека менше, ніж ARG_MAXу limits.h, тоді limits.hбуде використано значення:

$ 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

Зауважте також, що якщо розмір стека встановлений нижчим за мінімально можливий ARG_MAX, то розмір стека ( RLIMIT_STACK) стає верхньою межею розміру аргументу / середовища перед E2BIGповерненням (хоча getconf ARG_MAXвсе ще буде показувати значення в limits.h).

Останнє, що слід зазначити, що якщо ядро ​​побудовано без CONFIG_MMU(підтримка апаратури управління пам’яттю), то перевірка ARG_MAXвідключена, тому обмеження не застосовується. Хоча MAX_ARG_STRLENі MAX_ARG_STRINGSдосі застосовуються.

Подальше читання


2
Це хороша відповідь, безумовно, краща за мою - я її схвалив. Але відповідь, яку ми просимо, не завжди відповідь, яку ми повинні отримати - ось чому ми просимо, тому що ми не знаємо. Це не вирішує проблему з вашим робочим потоком, який привів вас в першу чергу з цим питанням. Я демонструю, як це може бути пом’якшене в моїй власній відповіді, і як аргументи рядкової строки змінної оболонки довжиною більше 2 Мб можуть передаватися новоспеченим процесам за допомогою лише декількох рядків сценарію оболонки.
mikeserv

Я створив сценарій Python, який демонструє обмеження змінних оточуючих середовищ 32 * 4 КБ = 128 КБ у Linux за замовчуванням.
nh2

0

В eglibc-2.18/NEWS

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

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

В linux/include/uapi/linux/limits.h

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

І 131072це ваш $(getconf ARG_MAX)/16-1, можливо, ви повинні почати з 0.

Ви маєте справу з glibc та Linux. Було б добре також виправити getconf, щоб повернути "правильне" ARG_MAXзначення.

Редагувати:

Щоб трохи очистити (після короткої, але гарячої дискусії)

ARG_MAXКонстанта , яка визначена в limits.h, дає максимальну довжину одного аргументу , переданого з Exec.

getconf ARG_MAXКоманда повертає максимальне значення кумулятивного розміру аргументів і оточення розміру переданого Exec.


2
Що ARG_MAX є мінімально гарантованим для обмеження розміру arg + env, це не максимальний розмір одного аргументу (хоча це трапляється з тим самим значенням, що й MAX_ARG_STRLEN)
Stéphane Chazelas

У вас є дата для вашого eglibc-2.18/NEWSфрагмента? Було б добре зафіксувати це до певної версії ядра.
Graeme

@StephaneChazelas: Я просто лінивий, щоб знайти частину, але якщо арґ перевищує максимальне значення, не потрібно з'ясовувати розмір env.

@Graeme: У мене також є кілька старих Linux, де значення getconf показує 131072. Я думаю, що це належить до новіших Linux з eglibc> ?? тільки. З повагою, ви знайшли помилку до речі.

2
Ви дивитеся на код glibc, це не має значення. Libc не хвилює, який розмір аргументів ви передаєте. Код, який ви цитуєте, стосується sysconf, API, який дає користувачам уявлення про максимальний розмір (що б це не означало) аргументу + env, переданого в execve (2). Це ядро, яке приймає чи не список аргументів та env, переданих по системному виклику execve (). getconf ARG_MAXЙде про сукупної величини АГД + окр (змінної останнім часом Linux см ulimit -sі інше питання , який я пов'язаний), це не про максимальну довжину одного арг , для якого немає ніякого sysconf / getconf запит.
Стефан Шазелас

-1

Тож @StephaneChazelas справедливо виправляє мене в коментарях нижче - сама оболонка ні в якому разі не диктує максимальний розмір аргументу, дозволений вашою системою, а навпаки, встановлює ваше ядро.

Як уже говорили декілька інших, схоже, ядро ​​обмежує максимальний розмір аргументу до 128 кбіт, який ви можете передати новому процесу від будь-якого іншого при першому його виконанні. Ця проблема виникає конкретно через багато вкладених $(command substitution)підшаровок, які повинні виконуватись на місці та передавати всю свою продукцію від одного до іншого.

І це дика здогадка, але оскільки ~ 5 кб розбіжність здається настільки близьким до стандартного розміру системної сторінки, я підозрюю, що вона призначена для сторінки, яка bashвикористовується для обробки підрозділу, який $(command substitution)потрібно для остаточного отримання результату та / або функціональний стек, який він використовує для асоціювання array tableданих із вашими даними. Я можу лише припустити, що жоден не виходить безкоштовно.

Я демонструю нижче, що, хоча це може бути трохи хитро, можливо, виклик дуже великих змінних значень оболонки до нових процесів при виклику, доки ви зможете впорядкувати його.

Для цього я в першу чергу використовував труби. Але я також оцінив масив оболонок, here-documentвказавши на cat's stdin. Результати нижче.

Але остання примітка - якщо у вас немає особливої ​​потреби в портативному коді, мені здається, що це mapfileможе трохи спростити роботу оболонки.

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

Можливо, ви могли б подвоїти це, а потім зробити це ще раз, якби це зробили в потоках - я не досить хворобливий, щоб це з'ясувати, - але, безумовно, це працює, якщо ви передаєте його.

Я намагався змінити printfчастину генератора у другому рядку на:

printf \ b%.0b

Він також працює:

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

Тож, можливо, я трохи хворобливий. Я використовую zero padding hereі додаю попереднє "$arg"значення до поточного "$arg"значення. Я виходжу понад 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

І якщо я зміню catрядок, щоб виглядати так:

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

Я можу отримати кількість байтів із " wc.Пам'ятати" - це розміри кожного ключа в argsмасиві. Загальний розмір масиву - це сума всіх цих значень.

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
Ні, нічого спільного з оболонкою, це системний виклик execve (2), який повертає E2BIG, коли один аргумент перевищує 128 Кб.
Стефан Шазелас

Враховуйте також, що обмежень на оболонки вбудованих не echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullбуде - буде добре. Виникає проблема лише при використанні зовнішньої команди.
Graeme

@Graeme Ну, я це робив і з котом - немає проблем. Змінна оцінюється в гередоці в кінці. Дивіться мою останню редакцію. Я скоротив загальну кількість до 33, тому що я додаю останнє значення кожного разу. І нульова
накладка

@StephaneChazelas - тож я можу це обійти, оцінюючи аргумент у потоці гередок? Або bashстискає це якось?
mikeserv

1
@mikeserv, я не бачу ніде у вашому коді будь-який примірник виконання команди з великим списком аргументів. printfє вбудованим, тому не виконується , і AFAICT, вашій catаргументі не надано жодного аргументу.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.