コマンドの単一引数の最大サイズを定義するものは何ですか?


48

ここでは、引数配列全体の合計サイズと環境のサイズ(これはに制限されていARG_MAXます)ほど、1つの引数の最大長は問題ではないという印象を受けました。したがって、次のようなことが成功すると思いました。

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

しばらく遊んだ後、最大値は完全な16進数の桁より小さいことがわかりました。

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

マイナス1が削除されると、エラーが返されます。一見、単一の引数の最大値は実際に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. 引数配列の実際の最大サイズと環境のおおよそのサイズの間の追加の〜5KBの不一致の原因は何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ページ(128kiB)にハードコードされています。ソースのMAX_ARG_STRLENを参照してください。
ステファンシャゼル14年


1
少なくとも私のマシンでgetconf ARG_MAXは、現在のに依存していulimit -sます。無制限に設定すると、ARG_MAXの素晴らしい4611686018427387903が得られます。
デロバート14年


なぜパス/ proc / $$ / environを使用するのですか?Linuxのprocfsはシンボリックリンク/ proc / selfをサポートしているので、/ proc / self / environを使用できます。プロセスに割り当てられたすべてのパッチは、同じプロセスがこれをチェックすると、/ proc / selfを指します。同じことがdevfsにもあります。たとえば/ dev内では、デバイスの標準出力はfd / 1へのシンボリックリンクですが、fdは/ self / fdを指します。多くのシステムはこの動作をコピーします。
ズニック

回答:


48

回答

  1. 間違いなくバグではありません。
  2. 1つの引数の最大サイズを定義するパラメーターは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. 1つの引数のサイズの制限(引数と環境の全体的な制限とは異なります)は、Linuxに固有のようです。この記事では、ARG_MAXUnixのようなシステムの詳細な比較と同等物について説明します。MAX_ARG_STRLENLinuxについて説明されていますが、他のシステムで同等のものについては言及されていません。

    上記の記事MAX_ARG_STRLENには、Linux 2.6.23で導入されたものと、コマンド引数の最大値に関連する他の多くの変更(後述)も記載されています。コミットのログ/差分はこちらにあります

  4. getconf ARG_MAX引数と環境の実際の最大サイズと結果との間の追加の不一致を説明するものはまだ明確ではありません。Stephane Chazelasの関連する回答は、スペースの一部が各引数/環境文字列へのポインターによって説明されることを示唆しています。しかし、私自身の調査では、これらのポインターはexecveシステムコールの初期段階では作成されずE2BIG、呼び出しプロセスにエラーが返される場合があります(ただし、各argv文字列へのポインターは後で作成されます)。

    また、文字列はメモリ内で連続しているので、メモリギャップによるアライメントは行われません。余分なメモリを使い果たすもの何でも内の要因である可能性が非常に高いですが。余分なスペースを使用するものを理解するには、カーネルがメモリを割り当てる方法についてのより詳細な知識が必要です(これは有用な知識ですので、後で調査して更新します)。

ARG_MAXの混乱

このcommitの結果として)Linux 2.6.23以降、コマンド引数の最大値の処理方法が変更され、Linuxが他のUnixライクなシステムと異なるようになりました。MAX_ARG_STRLENandの追加に加えてMAX_ARG_STRINGSgetconf ARG_MAXnow の結果はスタックサイズに依存し、ARG_MAXin とは異なる場合があります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_MAXlimits.hの結果のハード下限として機能しますgetconf ARG_MAX。スタックサイズは、このようなことが設定されている場合、1/4スタックサイズのより少ないARG_MAXlimits.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_MAXRLIMIT_STACKE2BIGが返される前に引数/環境サイズの上限になります(ただしgetconf ARG_MAX、値は表示されますlimits.h)。

最後に注意することは、カーネルがCONFIG_MMU(メモリ管理ハードウェアのサポートなしで)ビルドされている場合、チェックARG_MAXは無効になるため、制限は適用されないということです。しかしMAX_ARG_STRLENMAX_ARG_STRINGSまだ適用されます。

参考文献


2
これは良い答えであり、確かに私のものよりも良いです-私はそれを支持しました。しかし、私たちが求める答えは、常に私たちが得るべき答えではありません。そもそもこの問題に直接直面したワークフローの問題には対処していません。私は自分の答えでそれを軽減する方法を示し、2MB以上の単一シェル変数文字列引数をわずか数行のシェルスクリプトで新しく実行されたプロセスに渡す方法を示します。
mikeserv 14年

デフォルトのLinuxで環境変数の32 * 4KBページ= 128 KBの制限を示すPythonスクリプトを作成しました。
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を扱っています。「正しい」ARG_MAX値が返されるようにするために、getconfにパッチを適用することもできます。

編集:

少し明確にするために(短いが熱い議論の後)

ARG_MAXで定義されている定数はlimits.h、幹部と渡された1つの引数の最大長さを与えます。

このgetconf ARG_MAXコマンドは、execに渡された累積引数サイズと環境サイズの最大値を返します。


2
ARG_MAXは引数+ ENVサイズ制限のために保証された最小ある(MAX_ARG_STRLENと同じ値であることを起こるが)、それは単一の引数の最大サイズではないということ
ステファンChazelas

eglibc-2.18/NEWSスニペットの日付はありますか?これを特定のカーネルバージョンに固定するとよいでしょう。
グレアム14年

@StephaneChazelas:パーツを見つけるのが面倒ですが、argが最大値を超える場合、envサイズを計算する必要はありません。

@Graeme:getconfの値が131072を示す古いLinuxもいくつか実行しています。これはeglibc> ??のある新しいLinuxに属していると思います。のみ。おめでとうございます、あなたはバグBTWを見つけました。

2
あなたはglibcコードを見ていますが、それはここでは無関係です。libcは、渡す引数のサイズを気にしません。引用しているコードはsysconfについてのものです。sysconfは、execve(2)に渡されるargv + envの最大サイズ(意味は何でも)をユーザーに知らせるAPIです。execve()システムコールで渡されるargおよびenvリストを受け入れるかどうかは、カーネルです。これgetconf ARG_MAXはarg + envの累積サイズ(最近のLinuxの変数です。ulimit -sリンクしている他の質問をご覧ください)であり、sysconf / getconfクエリがない単一の引数の最大長ではありません。
ステファンシャゼル14年

-1

@StephaneChazelasが以下のコメントを正しく修正してくれます。シェル自体は、システムで許可される最大引数サイズを決定するのではなく、カーネルによって設定されます。

他のいくつかの人がすでに言っているように、カーネルは最初の実行時に他のプロセスから新しいプロセスに渡すことができる最大引数サイズを128kbに制限しているようです。この問題は、特定の$(command substitution)場所で実行し、出力全体を次から次へ渡す必要がある多くのネストされたサブシェルが原因で発生します。

これは一種の突飛な推測ですが、〜5kbの不一致は標準システムのページサイズに非常に近いように思われるため、最終的に出力を配信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

おそらくあなたはこれを2倍にして、ストリームでそれをやり直せばもう一度やり直すことができます-私はそれを見つけるのに十分な病的状態ではありません-しかしそれをストリーミングすれば間違いなく動作します。

printf2行目のジェネレーター部分を次のように変更してみました。

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
いいえ、シェルとは何の関係もありません。1つの引数が128kiBを超えたときにE2BIGを返すexecve(2)システムコールです。
ステファンシャゼル14年

シェルのビルトインに制限がないことも考慮してください- echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/null正常に動作します。問題があるのは、外部コマンドを使用する場合のみです。
Graeme 14年

@Graemeまあ、私は猫でもこれをやりました-問題ありません。変数は、最後にヒアドキュメントで評価されます。私の最後の編集を参照してください。毎回最後の値を追加するため、合計カウントを33に削減しました。そして、ゼロパディング...
mikeserv

@StephaneChazelas-それで、ヒアドキュメントストリームの引数を評価することでそれを回避できますか?それともbash何らかの形で圧縮していますか?
mikeserv 14年

1
@mikeserv、大きな引数リストを使用してコマンドを実行しているインスタンスは、コードのどこにも表示されません。printfは組み込みなので、実行されcatません。
ステファンシャゼル14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.