Tại sao một số shell `read` dựng sẵn không đọc được toàn bộ dòng từ tệp trong` / Proc`?


19

Trong một số Bourne giống như vỏ, các readBUILTIN không thể đọc được toàn bộ dòng từ tập tin trong /proc(lệnh dưới đây sẽ được chạy trong zsh, thay thế $=shellvới $shellvới vỏ khác):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readtiêu chuẩn yêu cầu đầu vào tiêu chuẩn cần phải là một tệp văn bản , yêu cầu đó có gây ra các hành vi khác nhau không?


Đọc định nghĩa POSIX của tệp văn bản , tôi thực hiện một số xác minh:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Không có NULký tự nào trong nội dung /proc/sys/fs/file-maxvà cũng findbáo cáo đây là một tệp thông thường (Đây có phải là lỗi trong findkhông?).

Tôi đoán cái vỏ đã làm một cái gì đó dưới mui xe, như file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty

Câu trả lời:


31

Vấn đề là những /proctệp trên Linux xuất hiện dưới dạng tệp văn bản stat()/fstat()có liên quan, nhưng không hoạt động như vậy.

Vì đó là dữ liệu động, bạn chỉ có thể thực hiện một read()cuộc gọi trên hệ thống (ít nhất là đối với một số trong số họ). Làm nhiều hơn một có thể giúp bạn có hai khối hai nội dung khác nhau, vì vậy thay vào đó, có vẻ như một giây read()trên chúng chỉ trả về không có gì (có nghĩa là cuối tập tin) (trừ khi bạn lseek()quay lại từ đầu (và chỉ bắt đầu từ đầu)).

Các readtiện ích cần đọc nội dung của file một byte tại một thời điểm để chắc chắn không tiếp tục đọc phần kí tự xuống dòng. Đó là những gì dash:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

Một số shell như bashcó tối ưu hóa để tránh phải thực hiện nhiều read()cuộc gọi hệ thống. Trước tiên, họ kiểm tra xem tệp có thể tìm kiếm được hay không và nếu có, hãy đọc theo từng đoạn để họ biết rằng họ có thể đặt con trỏ trở lại ngay sau dòng mới nếu họ đã đọc qua nó:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

Với bash, bạn vẫn gặp sự cố đối với các tệp Proc lớn hơn 128 byte và chỉ có thể được đọc trong một cuộc gọi hệ thống đọc.

bashdường như cũng vô hiệu hóa tối ưu hóa đó khi -dtùy chọn được sử dụng.

ksh93tối ưu hóa hơn nữa đến mức trở thành không có thật. ksh93 readkhông tìm kiếm lại, nhưng nhớ dữ liệu bổ sung mà nó đã đọc cho lần tiếp theo read, do đó, phần tiếp theo read(hoặc bất kỳ nội dung nào khác đọc dữ liệu như cathoặc head) thậm chí không thử readdữ liệu (ngay cả khi dữ liệu đó đã được sửa đổi bởi các lệnh khác ở giữa):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st

À đúng rồi, một stracelời giải thích dựa trên dễ hiểu hơn nhiều!
Stephen Kitt

Cảm ơn, dữ liệu động có ý nghĩa. Vậy làm thế nào để shell phát hiện ra dữ liệu động của nó? Nếu tôi làm cat /proc/sys/fs/file-max | ..., vấn đề đã biến mất.
cuonglm

3
Vỏ không phát hiện ra nó. Thực tế là dữ liệu động có nghĩa là procfskhông thể xử lý nhiều read(2)cuộc gọi liên tiếp vào cùng một tệp; hành vi không phụ thuộc vào vỏ. Sử dụng catvà đường ống hoạt động vì catđọc tệp trong khối đủ lớn; Vỏ readđược tích hợp sau đó đọc từ một ký tự ống một lần.
Stephen Kitt

1
Có một chút cách giải quyết bẩn trong mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer

1
@IporSircer. Thật. Một công việc tương tự xung quanh dường như hoạt động với zsh: read -u0 -k10(hoặc sử dụng sysread; $mapfile[/proc/sys/fs/file-max]không hoạt động vì các tệp đó không thể được chỉnh sửa mmap). Trong mọi trường hợp, với bất kỳ vỏ, người ta luôn có thể a=$(cat /proc/sys/fs/file-max). Với một số bao gồm mksh, zshksh93, a=$(</proc/sys/fs/file-max)cũng hoạt động và không rẽ nhánh một quá trình để đọc.
Stéphane Chazelas

9

Nếu bạn quan tâm đến việc biết tại sao? điều này là như vậy, bạn có thể thấy câu trả lời trong các nguồn kernel ở đây :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

Về cơ bản, tìm kiếm ( *pposkhông phải 0) không được triển khai để đọc ( !write) các giá trị sysctl là các số. Bất cứ khi nào đọc xong /proc/sys/fs/file-max, thường trình trong câu hỏi __do_proc_doulongvec_minmax()được gọi từ mục nhập file-maxtrong bảng cấu hình trong cùng một tệp.

Các mục khác, chẳng hạn như /proc/sys/kernel/poweroff_cmdđược thực hiện thông qua proc_dostring()đó cho phép tìm kiếm, vì vậy bạn có thể thực hiện dd bs=1trên đó và đọc từ trình bao của bạn mà không gặp vấn đề gì.

Lưu ý rằng vì kernel 2.6, hầu hết các lần /procđọc được triển khai thông qua API mới có tên là seq_file và điều này hỗ trợ tìm kiếm, ví dụ như việc đọc /proc/statkhông gây ra vấn đề. Việc /proc/sys/thực hiện, như chúng ta có thể thấy, không sử dụng api này.


3

Trong lần thử đầu tiên, nó trông giống như một con bọ trong vỏ trả lại ít hơn Bourne Shell thực sự hoặc trả lại các dẫn xuất của nó (sh, bosh, ksh, gia truyền).

Bourne Shell ban đầu cố gắng đọc một khối (64 byte) các biến thể Bourne Shell mới hơn đọc 128 byte, nhưng chúng bắt đầu đọc lại nếu không có ký tự dòng mới.

Bối cảnh: / Procfs và các triển khai tương tự (ví dụ: /etc/mtabtệp ảo được gắn ) có nội dung động và stat()cuộc gọi không gây ra việc tạo lại nội dung động trước tiên. Vì lý do này, kích thước của một tệp như vậy (từ đọc đến EOF) có thể khác với những gì stat()trả về.

Cho rằng các tiêu chuẩn POSIX đòi hỏi các tiện ích mong đợi ngắn đọc bất cứ lúc nào, phần mềm mà tin rằng một read()mà lợi nhuận thấp hơn yêu cầu số lượng byte là một dấu hiệu EOF bị phá vỡ. Một tiện ích được triển khai chính xác gọi read()lần thứ hai trong trường hợp nó trả về ít hơn mong đợi - cho đến khi trả về 0. Trong trường hợp readdựng sẵn, tất nhiên sẽ đủ để đọc cho đến khi EOF hoặc cho đến khi NLnhìn thấy a.

Nếu bạn chạy trusshoặc một bản sao giàn, bạn sẽ có thể xác minh rằng hành vi không chính xác cho các vỏ chỉ quay trở lại 6trong thử nghiệm của bạn.

Trong trường hợp đặc biệt này, nó dường như là một lỗi nhân Linux, xem:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Nhân Linux trả về 0 với lần thứ hai readvà điều này tất nhiên là không chính xác.

Kết luận: Các shell đầu tiên cố gắng đọc một khối dữ liệu đủ lớn sẽ không kích hoạt lỗi kernel Linux này.


OK, đã thoát câu trả lời với một xác minh mới cho lỗi kernel Linux.
schily

Nó không phải là một lỗi, đó là một tính năng!
Guntram Blohm hỗ trợ Monica

Đây là một yêu cầu thực sự kỳ lạ.
schily

Nó sẽ là một tính năng nếu nó được ghi lại. Đọc kernel.org/doc/Documentation/filesystems/proc.txt , tôi thấy không có tài liệu nào cho hành vi. Điều đó nói rằng, nó rõ ràng là hoạt động theo dự định của người thực hiện, vì vậy nếu điều này được coi là một lỗi, thì đó là lỗi trong thiết kế, không phải là việc thực hiện.
Charles Duffy

0

Các tệp dưới / Proc đôi khi sử dụng ký tự NULL để tách các trường bên trong tệp. Có vẻ như đọc là không thể xử lý này.

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.