Điều gì xác định kích thước tối đa cho một đối số lệnh?


48

Tôi có ấn tượng rằng độ dài tối đa của một đối số không phải là vấn đề ở đây nhiều như tổng kích thước của mảng đối số tổng thể cộng với kích thước của môi trường, được giới hạn ARG_MAX. Vì vậy, tôi nghĩ rằng một cái gì đó như sau sẽ thành công:

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

Với sự - 100tồn tại quá đủ để tính đến sự khác biệt giữa kích thước của môi trường trong vỏ và echoquy trình. Thay vào đó tôi đã nhận được lỗi:

bash: /bin/echo: Argument list too long

Sau khi chơi một lúc, tôi thấy rằng mức tối đa là một thứ tự hex có độ lớn nhỏ hơn:

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

Khi trừ đi một cái, lỗi sẽ trả về. Dường như mức tối đa cho một đối số là thực sự ARG_MAX/16và các -1tài khoản cho byte null được đặt ở cuối chuỗi trong mảng đối số.

Một vấn đề khác là khi đối số được lặp lại, tổng kích thước của mảng đối số có thể gần hơn ARG_MAX, nhưng vẫn không hoàn toàn ở đó:

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

Sử dụng "${args[0]:6533}"ở đây làm cho đối số cuối cùng dài hơn 1 byte và đưa ra Argument list too longlỗi. Sự khác biệt này khó có thể được tính bằng kích thước của môi trường được đưa ra:

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

Câu hỏi:

  1. Đây có phải là hành vi chính xác, hoặc có một lỗi ở đâu đó?
  2. Nếu không, hành vi này được ghi nhận ở bất cứ đâu? Có một tham số khác xác định mức tối đa cho một đối số không?
  3. Là hành vi này giới hạn trong Linux (hoặc thậm chí các phiên bản cụ thể như vậy)?
  4. Điều gì chiếm sự khác biệt ~ 5KB bổ sung giữa kích thước tối đa thực tế của mảng đối số cộng với kích thước gần đúng của môi trường và ARG_MAX?

Thông tin bổ sung:

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

5
Trên Linux, nó được mã hóa cứng thành 32 trang (128kiB). Xem MAX_ARG_STRLEN trong nguồn.
Stéphane Chazelas


1
Ít nhất là trên máy của tôi, getconf ARG_MAXphụ thuộc vào hiện tại ulimit -s. Đặt nó thành không giới hạn và nhận được 4611686018427387903 tuyệt vời cho ARG_MAX.
derobert


Tại sao bạn sử dụng path / Proc / $$ / môi trường? Procfs trong linux hỗ trợ symlink / Proc / self, sau đó bạn có thể sử dụng / Proc / self / môi trường. tất cả các bản vá được chỉ định để xử lý, khi cùng một quy trình kiểm tra điều này, trỏ đến / Proc / self. Điều tương tự là với devfs, ví dụ bên trong / dev, thiết bị xuất chuẩn của thiết bị là liên kết tượng trưng đến fd / 1, nhưng fd trỏ đến / self / fd. nhiều hệ thống sao chép hành vi này.
Znik

Câu trả lời:


48

Đáp án

  1. Chắc chắn không phải là một lỗi.
  2. Tham số xác định kích thước tối đa cho một đối số là MAX_ARG_STRLEN. Không có tài liệu nào cho tham số này ngoài các ý kiến ​​trong 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
    

    Như được hiển thị, Linux cũng có giới hạn (rất lớn) về số lượng đối số cho một lệnh.

  3. Một giới hạn về kích thước của một đối số (khác với giới hạn tổng thể đối với môi trường cộng với đối số) dường như là dành riêng cho Linux. Bài viết này đưa ra một so sánh chi tiết ARG_MAXvà tương đương trên các hệ thống giống như Unix. MAX_ARG_STRLENđược thảo luận cho Linux, nhưng không có đề cập đến bất kỳ tương đương trên bất kỳ hệ thống nào khác.

    Bài viết trên cũng nêu rõ MAX_ARG_STRLENđã được giới thiệu trong Linux 2.6.23, cùng với một số thay đổi khác liên quan đến mức tối đa của đối số lệnh (được thảo luận dưới đây). Nhật ký / diff cho cam kết có thể được tìm thấy ở đây .

  4. Vẫn chưa rõ tài khoản nào cho sự khác biệt bổ sung giữa kết quả getconf ARG_MAXvà kích thước tối đa có thể thực tế của các đối số cộng với môi trường. Câu trả lời liên quan của Stephane Chazelas , cho thấy rằng một phần của không gian được tính bởi các con trỏ cho mỗi chuỗi đối số / môi trường. Tuy nhiên, điều tra riêng của tôi cho thấy rằng các con trỏ này không được tạo sớm trong lệnh execvegọi hệ thống khi nó vẫn có thể trả về E2BIGlỗi cho quá trình gọi (mặc dù các con trỏ tới mỗi argvchuỗi chắc chắn được tạo sau).

    Ngoài ra, các chuỗi nằm liền kề trong bộ nhớ theo như tôi có thể thấy, vì vậy không có khoảng trống bộ nhớ nào do căn chỉnh ở đây. Mặc dù là rất có khả năng là một yếu tố trong bất cứ điều gì không sử dụng hết bộ nhớ thêm. Hiểu những gì sử dụng không gian bổ sung đòi hỏi kiến ​​thức chi tiết hơn về cách phân bổ bộ nhớ (đây là kiến ​​thức hữu ích để có, vì vậy tôi sẽ điều tra và cập nhật sau).

ARG_MAX nhầm lẫn

Kể từ Linux 2.6.23 (là kết quả của cam kết này ), đã có những thay đổi về cách xử lý tối đa đối số lệnh khiến Linux khác với các hệ thống tương tự Unix khác. Ngoài việc thêm MAX_ARG_STRLENMAX_ARG_STRINGS, kết quả getconf ARG_MAXbây giờ phụ thuộc vào kích thước ngăn xếp và có thể khác với ARG_MAXtrong limits.h.

Thông thường kết quả của getconf ARG_MAXsẽ là 1/4kích thước ngăn xếp. Hãy xem xét những điều sau trong bashviệc sử dụng ulimitđể có được kích thước ngăn xếp:

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

Tuy nhiên, hành vi trên đã được thay đổi một chút bởi cam kết này (được thêm vào Linux 2.6.25-rc4 ~ 121). ARG_MAXtrong limits.hngày nay dùng làm cứng thấp hơn bị ràng buộc vào kết quả getconf ARG_MAX. Nếu kích thước ngăn xếp được đặt sao cho 1/4kích thước ngăn xếp nhỏ hơn ARG_MAXtrong limits.h, thì limits.hgiá trị sẽ được sử dụng:

$ 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

Cũng lưu ý rằng nếu kích thước ngăn xếp được đặt thấp hơn mức tối thiểu có thể ARG_MAX, thì kích thước của ngăn xếp ( RLIMIT_STACK) sẽ trở thành giới hạn trên của kích thước đối số / môi trường trước khi E2BIGđược trả về (mặc dù getconf ARG_MAXvẫn sẽ hiển thị giá trị trong limits.h).

Một điều cuối cùng cần lưu ý là nếu kernel được xây dựng mà không có CONFIG_MMU(hỗ trợ cho phần cứng quản lý bộ nhớ), thì việc kiểm tra ARG_MAXbị vô hiệu hóa, do đó giới hạn không được áp dụng. Mặc dù MAX_ARG_STRLENMAX_ARG_STRINGSvẫn áp dụng.

Đọc thêm


2
Đây là một câu trả lời tốt, chắc chắn tốt hơn của tôi - tôi đã nâng cao nó. Nhưng câu trả lời chúng tôi yêu cầu không phải luôn luôn là câu trả lời chúng tôi nên nhận - đó là lý do tại sao chúng tôi hỏi, vì chúng tôi không biết. Nó không giải quyết vấn đề với dòng công việc của bạn khiến bạn phải đối mặt với vấn đề này ngay từ đầu. Tôi chứng minh làm thế nào điều đó có thể được giảm thiểu trong câu trả lời của riêng tôi và làm thế nào các đối số chuỗi biến vỏ đơn có độ dài hơn 2mbs có thể được chuyển đến các quy trình mới được thực hiện chỉ bằng một vài dòng script shell.
mikeerv

Tôi đã tạo một tập lệnh Python thể hiện các trang 32 * 4KB = giới hạn 128 KB của các biến môi trường trên Linux mặc định.
nh2 ngày

0

Trong eglibc-2.18/NEWS

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

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

Trong linux/include/uapi/linux/limits.h

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

131072là của bạn $(getconf ARG_MAX)/16-1, có lẽ bạn nên bắt đầu từ 0.

Bạn đang làm việc với glibc và Linux. Sẽ rất tốt nếu vá getconf để có được ARG_MAXgiá trị "đúng" được trả về.

Biên tập:

Để làm rõ một chút (sau một cuộc thảo luận ngắn nhưng nóng)

Các ARG_MAXhằng số được định nghĩa trong limits.h, mang đến cho chiều dài tối đa của một đối số thông qua với exec.

Các getconf ARG_MAXlệnh trả về giá trị tối đa của tích lũy kích thước đối số và môi trường kích thước truyền cho exec.


2
ARG_MAX đó là mức tối thiểu được đảm bảo cho giới hạn kích thước arg + env, nó không phải là kích thước tối đa của một đối số (mặc dù nó có cùng giá trị với MAX_ARG_STRLEN)
Stéphane Chazelas

Bạn có một ngày cho eglibc-2.18/NEWSđoạn trích của bạn ? Sẽ tốt hơn nếu ghim nó xuống một phiên bản kernel cụ thể.
Graeme

@StephaneChazelas: Tôi quá lười để tìm phần, nhưng nếu arg vượt quá giá trị tối đa thì không cần thiết phải tìm ra kích thước env.

@Graeme: Tôi cũng có một số linux cũ hơn đang chạy trong đó giá trị getconf hiển thị 131072. Tôi nghĩ rằng điều này thuộc về các linux mới hơn với eglibc> ?? chỉ có. Xin chúc mừng, bạn đã tìm thấy một lỗi BTW.

2
Bạn đang xem mã glibc, điều đó không liên quan ở đây. Libc không quan tâm kích thước của các đối số bạn đang vượt qua. Mã bạn trích dẫn là về sysconf, một API để cung cấp cho người dùng ý tưởng về kích thước tối đa (bất kể điều đó có nghĩa là gì) của argv + env được truyền cho người thực thi (2). Đó là kernel chấp nhận hay không danh sách arg và env được truyền cùng với lệnh gọi hệ thống execve (). Các getconf ARG_MAXlà về kích thước tích lũy của arg + env (biến trong Linux gần đây, xem ulimit -svà câu hỏi khác tôi liên kết), nó không phải về chiều dài tối đa của một arg duy nhất mà không có sysconf / getconf truy vấn.
Stéphane Chazelas

-1

Vì vậy, @StephaneChazelas sửa lỗi cho tôi trong các bình luận bên dưới - bản thân trình bao không quy định theo bất kỳ cách nào kích thước đối số tối đa được hệ thống của bạn cho phép, mà là do hạt nhân của bạn đặt.

Như một số người khác đã nói, có vẻ như kernel giới hạn ở mức 128kb kích thước đối số tối đa mà bạn có thể trao cho một quy trình mới từ bất kỳ quy trình nào khác khi lần đầu tiên thực hiện nó. Bạn gặp phải vấn đề này đặc biệt do có nhiều mạng con lồng nhau $(command substitution)phải thực thi tại chỗ và trao toàn bộ đầu ra của chúng từ cái này sang cái khác.

Và đây là một phỏng đoán hoang dã, nhưng vì sự khác biệt ~ 5kb dường như rất gần với kích thước trang hệ thống tiêu chuẩn, tôi nghi ngờ rằng nó được dành riêng cho trang bashsử dụng để xử lý phần con mà bạn $(command substitution)yêu cầu để cuối cùng cung cấp đầu ra và / hoặc chức năng ngăn xếp nó sử dụng trong việc liên kết array tabledữ liệu của bạn. Tôi chỉ có thể giả sử không đến miễn phí.

Tôi chứng minh bên dưới rằng, mặc dù có thể hơi khó khăn, nhưng có thể chuyển các giá trị biến vỏ rất lớn cho các quy trình mới khi gọi, miễn là bạn có thể quản lý để truyền phát nó.

Để làm như vậy, tôi chủ yếu sử dụng đường ống. Nhưng tôi cũng đã đánh giá mảng shell theo here-documentchỉ dẫn cat's stdin. Kết quả bên dưới.

Nhưng một lưu ý cuối cùng - nếu bạn không có nhu cầu đặc biệt về mã di động, điều đó gây ấn tượng với tôi rằng mapfilecó thể đơn giản hóa công việc shell của bạn một chút.

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

Có thể bạn có thể tăng gấp đôi số này và sau đó làm lại nếu bạn thực hiện theo luồng - tôi không đủ bệnh hoạn để tìm hiểu - nhưng chắc chắn nó hoạt động nếu bạn phát trực tuyến.

Tôi đã thử thay đổi phần printfmáy phát điện trong dòng hai thành:

printf \ b%.0b

Nó cũng hoạt động:

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

Vì vậy, có lẽ tôi là một chút bệnh hoạn. Tôi sử dụng zero padding herevà thêm "$arg"giá trị trước vào giá trị hiện tại "$arg". Tôi vượt quá 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

Và nếu tôi thay đổi catdòng giống như thế này:

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

Tôi có thể nhận được số byte từ wc.Ghi nhớ đây là kích thước của từng khóa trong argsmảng. Tổng kích thước của mảng là tổng của tất cả các giá trị này.

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
Không, không có gì để làm với shell, đó là cuộc gọi hệ thống thực thi (2) trả về E2BIG khi một đối số duy nhất vượt quá 128kiB.
Stéphane Chazelas

Cũng xem xét rằng không có giới hạn đối với nội dung shell - echo $(tr -dc [:alnum:] </dev/urandom | head -c $(($(getconf ARG_MAX)*10))) >/dev/nullsẽ chạy tốt. Chỉ khi bạn sử dụng một lệnh bên ngoài thì có vấn đề.
Graeme

@Graeme Vâng, tôi cũng đã làm điều này với mèo - không vấn đề gì. Biến được đánh giá trong một di sản ở cuối. Xem chỉnh sửa cuối cùng của tôi. Tôi đã cắt giảm tổng số xuống còn 33 vì tôi đang thêm giá trị cuối cùng mỗi lần. Và không đệm ...
mikeserv

@StephaneChazelas - vậy tôi có khắc phục được điều đó bằng cách đánh giá đối số trong luồng di truyền không? Hoặc là bashnén nó bằng cách nào đó?
mikeerv

1
@mikeerv, tôi không thể thấy bất cứ nơi nào trong mã của bạn bất kỳ trường hợp nào bạn thực thi lệnh với danh sách đối số lớn. printflà một nội dung nên không được thực thi và AFAICT, bạn catkhông được cung cấp bất kỳ đối số nào.
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.