Kernel

什麼定義了命令單個參數的最大大小?

  • March 21, 2014

我的印像是單個參數的最大長度在這裡不是問題,而是整個參數數組的總大小加上環境的大小,環境的大小限制為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足以說明 shell 中的環境大小和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. 是什麼導致了參數數組的實際最大大小加上環境的近似大小之間額外的約 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

答案

  1. 絕對不是bug。
  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 的。本文詳細比較了類 Unix 系統上的等價物討論了 Linux,但沒有提到任何其他系統上的任何等效項。ARG_MAX``MAX_ARG_STRLEN

上面的文章還說明了它MAX_ARG_STRLEN是在 Linux 2.6.23 中引入的,以及與命令參數最大值相關的一些其他更改(下面討論)。可以在此處找到送出的日誌/差異。 4. 目前尚不清楚是什麼導致了getconf ARG_MAX參數加環境的結果與實際最大可能大小之間的額外差異。Stephane Chazelas 的相關回答表明,部分空間是由指向每個參數/環境字元串的指針來計算的。但是,我自己的調查表明,這些指針不是在execve系統呼叫的早期創建的,因為它可能仍然E2BIG向呼叫程序返回錯誤(儘管指向每個argv字元串的指針肯定是稍後創建的)。

此外,就我所見,字元串在記憶體中是連續的,因此這裡沒有由於對齊而導致的記憶體間隙。儘管很可能是消耗額外記憶體的一個因素。了解什麼使用額外空間需要更詳細地了解核心如何分配記憶體(這是有用的知識,因此我將在稍後進行調查和更新)。

ARG_MAX 混亂

自 Linux 2.6.23 以來(作為此送出的結果),處理命令參數最大值的方式發生了變化,這使得 Linux 與其他類 Unix 系統不同。除了MAX_ARG_STRLEN和之外MAX_ARG_STRINGS, now 的結果getconf ARG_MAX取決於堆棧大小,可能與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_MAXinlimits.h現在用作 的結果的硬下限getconf ARG_MAX。如果設置堆棧大小使得1/4堆棧大小小於ARG_MAXin 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

另請注意,如果堆棧大小設置為低於 minimum possible ARG_MAX,則堆棧 ( RLIMIT_STACK) 的大小將成為參數/環境大小的上限,然後E2BIG返回(儘管getconf ARG_MAX仍會顯示 中的值limits.h)。

最後要注意的是,如果核心是在沒有CONFIG_MMU(支持記憶體管理硬體)的情況下建構的,ARG_MAX則禁用檢查,因此限制不適用。雖然MAX_ARG_STRLEN並且MAX_ARG_STRINGS仍然適用。

延伸閱讀

引用自:https://unix.stackexchange.com/questions/120642