Apa yang mendefinisikan ukuran maksimum untuk satu argumen perintah?

49

Saya mendapat kesan bahwa panjang maksimum satu argumen tidak menjadi masalah di sini sebanyak ukuran total array argumen keseluruhan plus ukuran lingkungan, yang terbatas pada ARG_MAX. Jadi saya pikir sesuatu seperti yang berikut ini akan berhasil:

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

Dengan - 100menjadi lebih dari cukup untuk menjelaskan perbedaan antara ukuran lingkungan di shell dan echoprosesnya. Sebaliknya saya mendapat kesalahan:

bash: /bin/echo: Argument list too long

Setelah bermain-main sebentar, saya menemukan bahwa maksimum adalah urutan hex besarnya lebih kecil:

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

Ketika yang minus dihapus, kesalahan kembali. Tampaknya maksimum untuk argumen tunggal sebenarnya ARG_MAX/16dan -1akun untuk byte nol ditempatkan di akhir string dalam array argumen.

Masalah lain adalah ketika argumen diulangi, ukuran total array argumen bisa lebih dekat ARG_MAX, tetapi masih belum cukup:

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

Menggunakan di "${args[0]:6533}"sini membuat argumen terakhir 1 byte lebih lama dan memberikan Argument list too longkesalahan. Perbedaan ini tidak mungkin diperhitungkan oleh ukuran lingkungan yang diberikan:

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

Pertanyaan:

  1. Apakah ini perilaku yang benar, atau ada bug di suatu tempat?
  2. Jika tidak, apakah perilaku ini didokumentasikan di mana saja? Apakah ada parameter lain yang menentukan maksimum untuk satu argumen?
  3. Apakah perilaku ini terbatas pada Linux (atau bahkan versi tertentu)?
  4. Apa yang menyebabkan perbedaan tambahan ~ 5KB antara ukuran maksimum sebenarnya dari argumen array ditambah ukuran perkiraan lingkungan dan ARG_MAX?

Informasi tambahan:

uname -a
Linux graeme-rock 3.13-1-amd64 #1 SMP Debian 3.13.5-1 (2014-03-04) x86_64 GNU/Linux
Graeme
sumber
5
Di Linux, sulit dikodekan ke 32 halaman (128kiB). Lihat MAX_ARG_STRLEN di sumbernya.
Stéphane Chazelas
1
Sebagian besar informasi yang Anda cari ada di jawaban ini untuk CP: argumen jumlah file sumber maks untuk utilitas salin
Stéphane Chazelas
1
Setidaknya pada mesin saya, getconf ARG_MAXtergantung pada saat ini ulimit -s. Setel ke tak terbatas, dan dapatkan 4611686018427387903 yang luar biasa untuk ARG_MAX.
derobert
mengapa Anda menggunakan path / proc / $$ / environment? procfs di linux mendukung symlink / proc / self, maka Anda dapat menggunakan / proc / self / environment. semua tambalan yang ditugaskan untuk memproses, ketika proses yang sama memeriksa ini, menunjuk ke / proc / self. Hal yang sama dengan devfs, misalnya di dalam / dev, stdout perangkat adalah symlink ke fd / 1, tetapi fd menunjuk ke / self / fd. banyak sistem menyalin perilaku ini.
Znik

Jawaban:

50

Jawaban

  1. Jelas bukan bug.
  2. Parameter yang menentukan ukuran maksimum untuk satu argumen adalah MAX_ARG_STRLEN. Tidak ada dokumentasi untuk parameter ini selain dari komentar di 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
    

    Seperti yang ditunjukkan, Linux juga memiliki batas (sangat besar) pada jumlah argumen untuk suatu perintah.

  3. Batas ukuran argumen tunggal (yang berbeda dari batas keseluruhan argumen plus lingkungan) tampaknya spesifik untuk Linux. Artikel ini memberikan perbandingan terperinci ARG_MAXdan setara pada sistem mirip Unix. MAX_ARG_STRLENdibahas untuk Linux, tetapi tidak ada yang menyebutkan setara pada sistem lain.

    Artikel di atas juga menyatakan bahwa MAX_ARG_STRLENdiperkenalkan di Linux 2.6.23, bersama dengan sejumlah perubahan lain yang berkaitan dengan maksimum argumen perintah (dibahas di bawah). Log / diff untuk komit dapat ditemukan di sini .

  4. Masih belum jelas apa yang menyebabkan perbedaan tambahan antara hasil getconf ARG_MAXdan ukuran maksimum yang dimungkinkan dari argumen plus lingkungan. Jawaban terkait Stephane Chazelas , menunjukkan bahwa bagian dari ruang dicatat oleh pointer ke masing-masing string argumen / lingkungan. Namun, penyelidikan saya sendiri menunjukkan bahwa pointer ini tidak dibuat di awal execvepanggilan sistem ketika masih dapat mengembalikan E2BIGkesalahan ke proses pemanggilan (meskipun pointer ke setiap argvstring pasti dibuat nanti).

    Juga, string berdekatan dalam memori sejauh yang saya bisa lihat, jadi tidak ada celah memori karena melakukan penyelarasan di sini. Meskipun sangat mungkin menjadi faktor dalam apa pun yang tidak menggunakan sampai memori tambahan. Memahami apa yang menggunakan ruang ekstra membutuhkan pengetahuan yang lebih rinci tentang bagaimana kernel mengalokasikan memori (yang merupakan pengetahuan berguna untuk dimiliki, jadi saya akan menyelidiki dan memperbarui nanti).

ARG_MAX Kebingungan

Sejak Linux 2.6.23 (sebagai hasil dari komit ini ), telah ada perubahan pada cara maksimum argumen perintah ditangani yang membuat Linux berbeda dari sistem mirip Unix lainnya. Selain menambahkan MAX_ARG_STRLENdan MAX_ARG_STRINGS, hasil getconf ARG_MAXsekarang tergantung pada ukuran tumpukan dan mungkin berbeda dari ARG_MAXpada limits.h.

Biasanya hasil dari getconf ARG_MAXakan menjadi 1/4ukuran tumpukan. Pertimbangkan hal berikut dalam bashmenggunakan ulimituntuk mendapatkan ukuran tumpukan:

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

Namun, perilaku di atas sedikit diubah oleh komit ini (ditambahkan di Linux 2.6.25-rc4 ~ 121). ARG_MAXdi limits.hsekarang menjabat sebagai hard batas bawah pada hasil getconf ARG_MAX. Jika ukuran tumpukan diatur sedemikian rupa sehingga 1/4ukuran tumpukan kurang dari ARG_MAXdalam limits.h, maka limits.hnilainya akan digunakan:

$ 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

Perhatikan juga bahwa jika ukuran tumpukan diatur lebih rendah dari minimum yang mungkin ARG_MAX, maka ukuran tumpukan ( RLIMIT_STACK) menjadi batas atas ukuran argumen / lingkungan sebelum E2BIGdikembalikan (meskipun getconf ARG_MAXmasih akan menunjukkan nilai dalam limits.h).

Hal terakhir yang perlu diperhatikan adalah bahwa jika kernel dibangun tanpa CONFIG_MMU(mendukung perangkat keras manajemen memori), maka pemeriksaan ARG_MAXdinonaktifkan, sehingga batas tidak berlaku. Meskipun MAX_ARG_STRLENdan MAX_ARG_STRINGSmasih berlaku.

Bacaan lebih lanjut

Graeme
sumber
2
Ini adalah jawaban yang bagus, tentu lebih baik daripada jawaban saya - saya membatalkannya. Tetapi jawaban yang kita minta bukanlah selalu jawaban yang harus kita dapatkan - itu sebabnya kita bertanya, karena kita tidak tahu. Itu tidak mengatasi masalah dengan alur kerja Anda yang membawa Anda head to head dengan masalah ini di tempat pertama. Saya mendemonstrasikan bagaimana hal itu dapat dimitigasi dalam jawaban saya sendiri, dan bagaimana argumen string variabel shell panjang lebih dari 2mbs dapat diteruskan ke proses yang baru saja dilakukan hanya dengan beberapa baris skrip shell.
mikeserv
Saya telah membuat skrip Python yang menunjukkan 32 * 4KB halaman = 128 KB batas variabel lingkungan di Linux default.
nh2
0

Di eglibc-2.18/NEWS

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

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

Di linux/include/uapi/linux/limits.h

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

Dan 131072apakah Anda $(getconf ARG_MAX)/16-1, mungkin Anda harus mulai dari 0.

Anda berurusan dengan glibc, dan Linux. Akan lebih baik untuk menambal getconf juga untuk mendapatkan nilai "benar" yang ARG_MAXdikembalikan.

Sunting:

Untuk memperjelas sedikit (setelah diskusi singkat tapi panas)

The ARG_MAXkonstan yang didefinisikan dalam limits.h, memberikan panjang max dari satu argumen lulus dengan exec.

The getconf ARG_MAXperintah mengembalikan nilai maks cumulated ukuran argumen dan lingkungan ukuran diteruskan ke exec.


sumber
2
ARG_MAX itu adalah minimum yang dijamin untuk batas ukuran arg + env, itu bukan ukuran maksimal dari argumen tunggal (meskipun itu sama dengan nilai yang sama dengan MAX_ARG_STRLEN)
Stéphane Chazelas
Apakah Anda memiliki tanggal untuk eglibc-2.18/NEWScuplikan Anda ? Akan lebih baik untuk menyematkan ini ke versi kernel tertentu.
Graeme
@StephaneChazelas: Saya terlalu malas untuk menemukan bagiannya, tetapi jika arg melebihi nilai maks, tidak perlu mencari tahu ukuran env.
@Graeme: Saya juga memiliki beberapa linux lama yang menjalankan di mana nilai getconf menunjukkan 131072. Saya pikir ini milik linux yang lebih baru dengan eglibc> ?? hanya. Selamat, Anda menemukan bug BTW.
2
Anda sedang melihat kode glibc, itu tidak relevan di sini. Libc tidak peduli seberapa besar argumen yang Anda sampaikan. Kode yang Anda kutip adalah tentang sysconf, sebuah API untuk memberi pengguna gambaran tentang ukuran maksimum (apa pun artinya) dari argv + env yang diteruskan ke execve (2). Adalah kernel yang menerima atau tidak daftar arg dan env yang diteruskan panggilan sistem execve (). The getconf ARG_MAXadalah tentang ukuran kumulatif arg + env (variabel di Linux baru-baru ini, lihat ulimit -sdan pertanyaan lain saya terhubung), itu bukan tentang panjang max dari arg tunggal yang ada sysconf / getconf query.
Stéphane Chazelas
-1

Jadi @StephaneChazelas benar mengoreksi saya di komentar di bawah ini - shell itu sendiri tidak menentukan ukuran argumen maksimum yang diizinkan oleh sistem Anda, melainkan diatur oleh kernel Anda.

Seperti yang telah dikatakan beberapa orang lain, tampaknya kernel membatasi hingga 128kb ukuran argumen maksimum yang dapat Anda berikan ke proses baru dari yang lain saat pertama kali mengeksekusi. Anda mengalami masalah ini secara khusus karena banyak $(command substitution)subshell bersarang yang harus dijalankan di tempat dan menyerahkan keseluruhan output mereka dari satu ke yang berikutnya.

Dan ini adalah tebakan liar, tetapi karena perbedaan ~ 5kb tampaknya begitu dekat dengan ukuran halaman sistem standar, kecurigaan saya adalah bahwa ia didedikasikan untuk penggunaan halaman bashuntuk menangani subkulit yang Anda perlukan $(command substitution)untuk akhirnya memberikan output dan / atau fungsi stack yang digunakannya dalam mengasosiasikan Anda array tabledengan data Anda. Saya hanya bisa berasumsi tidak ada yang gratis.

Saya menunjukkan di bawah ini bahwa, meskipun mungkin sedikit rumit, adalah mungkin untuk melewatkan nilai variabel shell yang sangat besar ke proses baru saat pemanggilan, selama Anda dapat mengatur untuk mengalirkannya.

Untuk melakukannya, saya terutama menggunakan pipa. Tetapi saya juga mengevaluasi susunan shell dengan here-documentmenunjuk pada cat's stdin. Hasil di bawah ini.

Tapi satu catatan terakhir - jika Anda tidak membutuhkan kode portabel, saya pikir itu mapfilemungkin menyederhanakan pekerjaan shell Anda sedikit.

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

Mungkin Anda bisa menggandakan ini dan kemudian melakukannya lagi jika Anda melakukannya di stream - Saya tidak cukup bodoh untuk mengetahuinya - tapi jelas itu berfungsi jika Anda mengalirkannya.

Saya memang mencoba mengubah bagian printfgenerator di baris dua menjadi:

printf \ b%.0b

Ini juga berfungsi:

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

Jadi mungkin saya agak tidak sehat. Saya menggunakan zero padding heredan menambahkan nilai sebelumnya "$arg"ke nilai saat ini "$arg". Saya mendapatkan jauh melampaui 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

Dan jika saya mengubah catbaris agar terlihat seperti ini:

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

Saya bisa mendapatkan jumlah byte dari wc.Ingat ini adalah ukuran setiap kunci dalam argsarray. Ukuran total array adalah jumlah dari semua nilai ini.

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
sumber
2
Tidak, tidak ada hubungannya dengan shell, itu adalah (2) panggilan sistem execve E2BIG ketika satu argumen lebih dari 128kiB.
Stéphane Chazelas
Pertimbangkan juga bahwa tidak ada batasan pada builtin shell - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullakan berjalan dengan baik. Hanya ketika Anda menggunakan perintah eksternal yang ada masalah.
Graeme
@ Geem Yah, saya melakukan ini dengan kucing juga - tidak masalah. Variabel dievaluasi dalam heredoc pada akhirnya. Lihat hasil edit terakhir saya. Saya memang mengurangi jumlah total menjadi 33 karena saya menambahkan nilai terakhir setiap kali. Dan bantalan nol ...
mikeserv
@StephaneChazelas - jadi apakah saya mengatasinya dengan mengevaluasi argumen dalam aliran heredoc? Atau bashmenekannya entah bagaimana?
mikeserv
1
@ mikeserv, saya tidak bisa melihat di mana pun di kode Anda setiap contoh Anda menjalankan perintah dengan daftar arg besar. printfadalah builtin jadi tidak dieksekusi , dan AFAICT, Anda cattidak diberi argumen.
Stéphane Chazelas