Tôi đang nghiên cứu một câu hỏi khác , khi tôi nhận ra mình không hiểu chuyện gì đang xảy ra, đó là những /dev/fd/*
tập tin nào và làm thế nào để các tiến trình con có thể mở chúng.
Tôi đang nghiên cứu một câu hỏi khác , khi tôi nhận ra mình không hiểu chuyện gì đang xảy ra, đó là những /dev/fd/*
tập tin nào và làm thế nào để các tiến trình con có thể mở chúng.
Câu trả lời:
Vâng, có nhiều khía cạnh của nó.
Mô tả tập tin
Đối với mỗi quy trình, kernel duy trì một bảng các tệp đang mở (tốt, nó có thể được triển khai khác nhau, nhưng vì bạn không thể nhìn thấy nó bằng mọi cách, bạn chỉ có thể giả sử đó là một bảng đơn giản). Bảng đó chứa thông tin về tệp nào / nơi có thể tìm thấy, ở chế độ bạn đã mở, ở vị trí bạn đang đọc / ghi và ở bất kỳ vị trí nào khác cần thiết để thực hiện các thao tác I / O trên tệp đó. Bây giờ quá trình không bao giờ được đọc (hoặc thậm chí viết) bảng đó. Khi quá trình mở một tệp, nó sẽ lấy lại một cái gọi là mô tả tệp. Mà chỉ đơn giản là một chỉ mục vào bảng.
Thư mục /dev/fd
và nội dung của nó
Trên Linux dev/fd
thực sự là một liên kết tượng trưng đến /proc/self/fd
. /proc
là một hệ thống tệp giả trong đó kernel ánh xạ một số cấu trúc dữ liệu nội bộ được truy cập bằng API tệp (vì vậy chúng trông giống như các tệp / thư mục / liên kết thông thường cho các chương trình). Đặc biệt là có thông tin về tất cả các quy trình (đó là cái đã đặt tên cho nó). Liên kết tượng trưng /proc/self
luôn đề cập đến thư mục được liên kết với quy trình hiện đang chạy (nghĩa là quá trình yêu cầu nó; do đó các quy trình khác nhau sẽ thấy các giá trị khác nhau). Trong thư mục của tiến trình, có một thư mục confd
mà đối với mỗi tệp đang mở chứa một liên kết tượng trưng có tên chỉ là biểu diễn thập phân của bộ mô tả tệp (chỉ mục vào bảng tệp của quy trình, xem phần trước) và mục tiêu của nó là tệp tương ứng.
Mô tả tệp khi tạo các tiến trình con
Một quá trình con được tạo bởi a fork
. A fork
tạo một bản sao của các mô tả tệp, có nghĩa là tiến trình con được tạo có cùng một danh sách các tệp đang mở giống như tiến trình cha mẹ. Vì vậy, trừ khi một trong những tệp đang mở được đóng bởi đứa trẻ, việc truy cập một bộ mô tả tệp được kế thừa ở trẻ sẽ truy cập vào cùng một tệp như truy cập vào bộ mô tả tệp gốc trong quy trình cha.
Lưu ý rằng sau một ngã ba, ban đầu bạn có hai bản sao của cùng một quy trình, chỉ khác nhau về giá trị trả về từ cuộc gọi ngã ba (cha mẹ nhận được PID của con, con được 0). Thông thường, một ngã ba được theo sau bởi một exec
để thay thế một trong các bản sao bằng một thực thi khác. Các mô tả tập tin mở tồn tại mà thực hiện. Cũng lưu ý rằng trước khi thực thi, quy trình có thể thực hiện các thao tác khác (như đóng các tệp mà quy trình mới không nên nhận hoặc mở các tệp khác).
Ống không tên
Một ống không tên chỉ là một cặp mô tả tệp được tạo theo yêu cầu của kernel, để mọi thứ được ghi vào bộ mô tả tệp đầu tiên được chuyển sang thứ hai. Việc sử dụng phổ biến nhất là cho cấu trúc đường ống foo | bar
của bash
, trong đó đầu ra tiêu chuẩn foo
được thay thế bằng phần ghi của đường ống và đầu vào tiêu chuẩn được thay thế bằng phần đọc. Đầu vào tiêu chuẩn và đầu ra tiêu chuẩn chỉ là hai mục nhập đầu tiên trong bảng tệp (mục nhập 0 và 1; 2 là lỗi tiêu chuẩn), và do đó thay thế nó có nghĩa là chỉ viết lại mục nhập bảng đó với dữ liệu tương ứng với bộ mô tả tệp khác (một lần nữa, thực hiện thực tế có thể khác nhau). Vì quá trình không thể truy cập trực tiếp vào bảng, nên có một hàm kernel để làm điều đó.
Quá trình thay thế
Bây giờ chúng ta có mọi thứ cùng nhau để hiểu cách thức thay thế quá trình hoạt động:
echo
quá trình. Quá trình con (là bản sao chính xác của bash
quy trình gốc ) đóng đầu đọc của ống và thay thế đầu ra tiêu chuẩn của chính nó bằng đầu viết của ống. Cho rằng đó echo
là một phần mềm dựng sẵn, bash
có thể tự thực hiện exec
cuộc gọi, nhưng dù sao thì nó cũng không thành vấn đề (phần tích hợp trình bao này cũng có thể bị vô hiệu hóa, trong trường hợp nó thực thi /bin/echo
).<(echo 1)
bằng liên kết tệp giả trong /dev/fd
tham chiếu đến đầu đọc của ống không tên./dev/fd/
. Vì bộ mô tả tệp tương ứng vẫn mở, nên nó vẫn tương ứng với đầu đọc của ống. Do đó, nếu chương trình PHP mở tệp đã cho để đọc, thì việc thực sự làm là tạo một bộ second
mô tả tệp cho đầu đọc của ống không tên. Nhưng đó không phải là vấn đề, nó có thể đọc từ một trong hai.echo
lệnh đi đến đầu ghi của cùng một ống.php
kịch bản, nhưng php
không xử lý tốt các đường ống . Ngoài ra, xem xét lệnh cat <(echo test)
, điều kỳ lạ ở đây là bash
dĩa một lần cho cat
, nhưng hai lần cho echo test
.
Mượn từ celtschk
câu trả lời của, /dev/fd
là một liên kết tượng trưng đến /proc/self/fd
. Và /proc
là một hệ thống tệp giả, trình bày thông tin về các quy trình và thông tin hệ thống khác trong một cấu trúc giống như tệp phân cấp. Các tệp /dev/fd
tương ứng với các tệp, được mở bởi một quy trình và có mô tả tệp là tên và tệp của chúng làm mục tiêu. Mở tệp /dev/fd/N
tương đương với sao chép mô tả N
(giả sử rằng mô tả đó N
đang mở).
Và đây là kết quả điều tra của tôi về cách thức hoạt động của nó ( strace
đầu ra loại bỏ các chi tiết không cần thiết và được sửa đổi để diễn đạt tốt hơn những gì đang xảy ra):
$ cat 1.c
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char buf[100];
int fd;
fd = open(argv[1], O_RDONLY);
read(fd, buf, 100);
write(STDOUT_FILENO, buf, n_read);
return 0;
}
$ gcc 1.c -o 1.out
$ cat 2.c
#include <unistd.h>
#include <string.h>
int main(void)
{
char *p = "hello, world\n";
write(STDOUT_FILENO, p, strlen(p));
return 0;
}
$ gcc 2.c -o 2.out
$ strace -f -e pipe,fcntl,dup2,close,clone,close,execve,wait4,read,open,write bash -c './1.out <(./2.out)'
[bash] pipe([3, 4]) = 0
[bash] dup2(3, 63) = 63
[bash] close(3) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p2
Process p2 attached
[bash] close(4) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p1
Process p1 attached
[bash] close(63) = 0
[p2] dup2(4, 1) = 1
[p2] close(4) = 0
[p2] close(63) = 0
[bash] wait4(-1, <unfinished ...>
Process bash suspended
[p1] execve("/home/yuri/_/1.out", ["/home/yuri/_/1.out", "/dev/fd/63"], [/* 31 vars */]) = 0
[p2] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p22
Process p22 attached
[p22] execve("/home/yuri/_/2.out", ["/home/yuri/_/2.out"], [/* 31 vars */]) = 0
[p2] wait4(-1, <unfinished ...>
Process p2 suspended
[p1] open("/dev/fd/63", O_RDONLY) = 3
[p1] read(3, <unfinished ...>
[p22] write(1, "hello, world\n", 13) = 13
[p1] <... read resumed> "hello, world\n", 100) = 13
Process p2 resumed
Process p22 detached
[p1] write(1, "hello, world\n", 13) = 13
hello, world
[p2] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p22
[p2] --- SIGCHLD (Child exited) @ 0 (0) ---
[p2] wait4(-1, 0x7fff190f289c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Process bash resumed
Process p1 detached
[bash] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p1
[bash] --- SIGCHLD (Child exited) @ 0 (0) ---
Process p2 detached
[bash] wait4(-1, 0x7fff190f2bdc, WNOHANG, NULL) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
[bash] wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = p2
[bash] wait4(-1, 0x7fff190f299c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Về cơ bản, bash
tạo một đường ống và chuyển các đầu của nó cho các phần tử con của nó dưới dạng mô tả tệp (đọc kết thúc 1.out
và viết kết thúc 2.out
). Và chuyển kết thúc đọc dưới dạng tham số dòng lệnh tới 1.out
( /dev/fd/63
). Cách 1.out
này có thể mở /dev/fd/63
.