กำหนดขนาดสูงสุดสำหรับอาร์กิวเมนต์คำสั่งเดียวคืออะไร


47

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. บัญชีอะไรเพิ่มเติมความแตกต่างระหว่าง ~ 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 ในแหล่งที่มา
Stéphane Chazelas


1
อย่างน้อยในเครื่องของฉันขึ้นอยู่กับปัจจุบันgetconf ARG_MAX ulimit -sกำหนดเป็นไม่ จำกัด และรับ 4611686018427387903 สำหรับ ARG_MAX ที่น่าทึ่ง
Derobert


ทำไมคุณใช้ path / proc / $$ / environ procfs ใน linux รองรับ symlink / proc / self จากนั้นคุณสามารถใช้ / proc / self / environ แพทช์ทั้งหมดที่กำหนดให้กับกระบวนการเมื่อกระบวนการเดียวกันตรวจสอบสิ่งนี้ชี้ไปที่ / proc / self เช่นเดียวกับ devfs เช่นภายใน / dev อุปกรณ์ stdout คือ symlink ไปยัง fd / 1 แต่ fd ชี้ไปที่ / self / fd หลายระบบคัดลอกพฤติกรรมนี้
Znik

คำตอบ:


47

คำตอบ

  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 พร้อมด้วยการเปลี่ยนแปลงอื่น ๆ ที่เกี่ยวข้องกับจำนวนอาร์กิวเมนต์สูงสุดคำสั่ง (อธิบายด้านล่าง) เข้าสู่ระบบ / diff สำหรับการกระทำที่สามารถพบได้ที่นี่

  4. ยังไม่ชัดเจนว่าบัญชีใดสำหรับความคลาดเคลื่อนเพิ่มเติมระหว่างผลลัพธ์getconf ARG_MAXและขนาดที่เป็นไปได้สูงสุดที่แท้จริงของอาร์กิวเมนต์และสภาพแวดล้อม คำตอบที่เกี่ยวข้องของ Stephane Chazelasชี้ให้เห็นว่าส่วนหนึ่งของพื้นที่นั้นถูกพอยน์เตอร์ชี้ไปยังสตริงอาร์กิวเมนต์ / สภาพแวดล้อมแต่ละตัว อย่างไรก็ตามการตรวจสอบของฉันเองชี้ให้เห็นว่าตัวชี้เหล่านี้ไม่ได้ถูกสร้างขึ้นในช่วงต้นของการexecveเรียกระบบเมื่อมันยังอาจส่งคืนE2BIGข้อผิดพลาดในกระบวนการเรียก (แม้ว่าตัวชี้ไปยังแต่ละargvสตริงจะถูกสร้างขึ้นในภายหลัง)

    นอกจากนี้สตริงยังต่อเนื่องกันในหน่วยความจำเท่าที่ฉันเห็นดังนั้นจึงไม่มีช่องว่างหน่วยความจำเนื่องจากการจัดตำแหน่งที่นี่ แม้ว่าเป็นอย่างมากมีแนวโน้มที่จะเป็นปัจจัยภายในสิ่งที่ไม่ใช้หน่วยความจำเสริม การทำความเข้าใจกับสิ่งที่ใช้พื้นที่พิเศษนั้นต้องการความรู้ที่ละเอียดยิ่งขึ้นว่าเคอร์เนลจัดสรรหน่วยความจำอย่างไร

ความสับสน ARG_MAX

ตั้งแต่ Linux 2.6.23 (เป็นผลมาจากการกระทำนี้ ) มีการเปลี่ยนแปลงวิธีการจัดการอาร์กิวเมนต์คำสั่งสูงสุดซึ่งทำให้ Linux แตกต่างจากระบบ Unix อื่น ๆ นอกเหนือจากการเพิ่มMAX_ARG_STRLENและMAX_ARG_STRINGSผลของgetconf ARG_MAXตอนนี้ขึ้นอยู่กับขนาดสแต็คและอาจจะแตกต่างจากในARG_MAXlimits.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ยังคงใช้

อ่านเพิ่มเติม

  • คำตอบที่เกี่ยวข้องโดย Stephane Chazelas - https://unix.stackexchange.com/a/110301/48083
  • ในหน้ารายละเอียดที่ครอบคลุมส่วนใหญ่ข้างต้น รวมถึงตารางของค่าARG_MAX(และเทียบเท่า) ในระบบที่คล้าย Unix - http://www.in-ulm.de/~mascheck/various/argmax/
  • ดูเหมือนว่าMAX_ARG_STRLENมีข้อผิดพลาดเกิดขึ้นกับ Automake ซึ่งฝังเชลล์สคริปต์ไว้ใน Makefiles โดยใช้sh -c- http://www.mail-archive.com/bug-make@gnu.org/msg05522.html

2
นี่เป็นคำตอบที่ดีดีกว่าของฉันอย่างแน่นอน - ฉันยกมันขึ้นมา แต่คำตอบที่เราถามไม่ใช่คำตอบที่เราควรได้รับเสมอนั่นคือสาเหตุที่เราถามเพราะเราไม่รู้ มันไม่ได้แก้ไขปัญหาเกี่ยวกับขั้นตอนการทำงานของคุณซึ่งนำคุณมาพบกับปัญหานี้ตั้งแต่แรก ฉันแสดงให้เห็นว่าสิ่งนั้นอาจถูกลดทอนลงในคำตอบของฉันเองได้อย่างไรและอาร์กิวเมนต์สตริงตัวแปรเชลล์เดี่ยวที่มีความยาวเกิน 2mbs สามารถส่งผ่านไปยังกระบวนการที่เพิ่งดำเนินการด้วยเชลล์สคริปต์เพียงไม่กี่บรรทัด
mikeserv

ฉันสร้างสคริปต์ Pythonที่แสดงหน้า 32 * 4KB = 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ข้อมูลโค้ดของคุณหรือไม่ มันเป็นการดีที่จะปักหมุดลงไปเป็นเคอร์เนลเวอร์ชันใดรุ่นหนึ่งโดยเฉพาะ
แกรม

@StephaneChazelas: ฉันแค่ขี้เกียจที่จะหาส่วน แต่ถ้าหาเรื่องเกินค่าสูงสุดก็ไม่จำเป็นต้องคิดขนาด env

@ Graeme: ฉันยังมี linuxes รุ่นเก่าบางตัวที่ทำงานที่ค่า getconf แสดง 131072 ฉันคิดว่านี่เป็น linuxes รุ่นใหม่ที่มี eglibc> ?? เท่านั้น ยินดีด้วยคุณพบข้อผิดพลาด BTW

2
คุณกำลังดูรหัส glibc ซึ่งไม่เกี่ยวข้องกับที่นี่ libc ไม่สนใจขนาดของการขัดแย้งที่คุณกำลังผ่าน โค้ดที่คุณอ้างถึงนั้นเกี่ยวกับ sysconf ซึ่งเป็น API สำหรับให้ผู้ใช้ทราบถึงขนาดสูงสุด (ไม่ว่ามันจะมีความหมายอะไร) ของ argv + env ที่ส่งผ่านไปยัง execve (2) เป็นเคอร์เนลที่รับหรือไม่รายการ arg และ env ที่ส่งผ่านการเรียกระบบ execve () getconf ARG_MAXมีขนาดประมาณสะสมของหาเรื่อง + env (ตัวแปรในลินุกซ์ที่ผ่านมาเห็นulimit -sและคำถามอื่น ๆ ฉันเชื่อมโยง) มันไม่ได้เกี่ยวกับความยาวสูงสุดของหาเรื่องเดียวที่ไม่มี sysconf / getconf แบบสอบถาม
Stéphane Chazelas

-1

ดังนั้น @StephaneChazelas ถูกต้องฉันถูกต้องในความคิดเห็นด้านล่าง - เชลล์ตัวเองไม่ได้กำหนดขนาดอาร์กิวเมนต์สูงสุดที่ระบบของคุณอนุญาต แต่อย่างใด แต่มันถูกกำหนดโดยเคอร์เนลของคุณ

ดังที่คนอื่น ๆ หลายคนบอกไปแล้วดูเหมือนว่าเคอร์เนลจะ จำกัด ขนาดอาร์กิวเมนต์สูงสุดที่ 128kb ที่คุณสามารถมอบให้กับกระบวนการใหม่จากที่อื่นเมื่อทำการประมวลผลครั้งแรก คุณประสบปัญหานี้โดยเฉพาะเนื่องจากมี$(command substitution)subshells หลายระดับที่ต้องดำเนินการและส่งมอบผลลัพธ์ทั้งหมดจากหนึ่งไปยังอีก

และสิ่งนี้เป็นการคาดเดาที่ยากลำบาก แต่เนื่องจากความคลาดเคลื่อน ~ 5kb ดูเหมือนว่าใกล้เคียงกับขนาดหน้ามาตรฐานของระบบความสงสัยของฉันคือว่ามันทุ่มเทให้กับหน้าเว็บที่bashใช้จัดการกับ subshell ที่คุณ$(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 เมื่ออาร์กิวเมนต์เดียวมีค่ามากกว่า 128kiB
Stéphane Chazelas

พิจารณาด้วยว่าไม่มีข้อ จำกัด ในตัวบิวด์เชลล์ - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullจะทำงานได้ดี มันก็ต่อเมื่อคุณใช้คำสั่งภายนอกว่ามีปัญหา
แกรม

@ Graeme ดีฉันทำกับแมวได้ดี - ไม่มีปัญหา ตัวแปรจะถูกประเมินใน heredoc ในตอนท้าย ดูการแก้ไขล่าสุดของฉัน ฉันลดจำนวนทั้งหมดลงเหลือ 33 เพราะฉันเพิ่มมูลค่าสุดท้ายทุกครั้ง และช่องว่างภายในเป็นศูนย์ ...
mikeserv

@StephaneChazelas - ดังนั้นฉันจะได้รับรอบที่โดยการประเมินข้อโต้แย้งในกระแส heredoc? หรือbashบีบอัดมันอย่างใด?
mikeserv

1
@ ไมค์เซอร์ฉันไม่เห็นว่าคุณอยู่ที่ไหนในโค้ดของคุณที่กำลังรันคำสั่งด้วยรายการหาเรื่องขนาดใหญ่ printfเป็น builtin ดังนั้นจึงไม่ถูกเรียกใช้งานและ AFAICT คุณcatไม่ได้รับข้อโต้แย้งใด ๆ
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.