Hãy bắt đầu bằng cách xem xét một số khái niệm quan trọng liên quan đến:
tập tin mô tả
Trong hạt nhân hệ điều hành, mỗi tập tin, thiết bị đầu cuối đường ống, thiết bị đầu cuối ổ cắm, nút thiết bị mở, và cứ như vậy, có một mô tả tệp . Hạt nhân sử dụng chúng để theo dõi vị trí trong tệp, các cờ (đọc, viết, nối thêm, đóng-trên-exec), khóa ghi, v.v.
Mô tả tệp nằm bên trong hạt nhân và không thuộc về bất kỳ quá trình cụ thể nào (trong các triển khai điển hình).
file descriptor
Từ quá trình quan điểm, mô tả tập tin là các số nguyên mà xác định tập tin mở, đường ống, ổ cắm, FIFOs, hoặc các thiết bị.
Hạt nhân hệ điều hành giữ bảng mô tả cho từng quy trình. Bộ mô tả tập tin được sử dụng bởi quá trình này chỉ đơn giản là một chỉ mục cho bảng này.
Các mục nhập trong bảng mô tả tệp tham chiếu đến mô tả tệp hạt nhân.
Bất kỳ khi nào quá trình sử dụng dup()
or dup2()
để nhân bản bộ mô tả tệp, hạt nhân chỉ sao chép mục nhập trong bảng mô tả tệp cho quá trình đó; nó không trùng lặp với các mô tả tập tin nó giữ cho chính nó.
Khi một quy trình dĩa, tiến trình con sẽ có bảng mô tả tệp riêng, nhưng các mục vẫn trỏ đến cùng một mô tả tệp hạt nhân. (Về bản chất là một shallow copy, tất cả sẽ ghi các mục bảng mô tả là tham chiếu đến các mô tả tệp. Các tham chiếu được sao chép; các mục tiêu được đề cập vẫn giữ nguyên.)
Khi một quá trình gửi một bộ mô tả tệp đến một quy trình khác qua Unix Thông báo phụ trợ của ổ cắm miền, hạt nhân thực sự phân bổ một bộ mô tả mới trên máy thu, và sao chép mô tả tệp mà bộ mô tả được chuyển đến đề cập đến.
Tất cả hoạt động rất tốt, mặc dù có một chút nhầm lẫn rằng "mô tả tệp" và "mô tả tệp" rất giống nhau.
Tất cả những gì phải làm với các hiệu ứng mà OP đang thấy?
Bất cứ khi nào các quy trình mới được tạo, thường mở thiết bị đích, đường ống hoặc ổ cắm và dup2()
bộ mô tả cho đầu vào tiêu chuẩn, đầu ra tiêu chuẩn và lỗi chuẩn. Điều này dẫn đến tất cả ba mô tả chuẩn đề cập đến cùng một mô tả tệp và do đó bất kỳ hoạt động nào hợp lệ sử dụng một bộ mô tả tệp, đều hợp lệ bằng cách sử dụng các trình mô tả tệp khác.
Điều này là phổ biến nhất khi chạy các chương trình trên bảng điều khiển, khi đó ba trình mô tả tất cả đều tham chiếu đến cùng một mô tả tệp; và mô tả tập tin đó mô tả kết thúc nô lệ của một thiết bị nhân vật giả.
Hãy xem xét các chương trình sau đây, run.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
static void wrerrp(const char *p, const char *q)
{
while (p < q) {
ssize_t n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
return;
}
}
static inline void wrerr(const char *s)
{
if (s)
wrerrp(s, s + strlen(s));
}
int main(int argc, char *argv[])
{
int fd;
if (argc < 3) {
wrerr("\nUsage: ");
wrerr(argv[0]);
wrerr(" FILE-OR-DEVICE COMMAND [ ARGS ... ]\n\n");
return 127;
}
fd = open(argv[1], O_RDWR | O_CREAT, 0666);
if (fd == -1) {
const char *msg = strerror(errno);
wrerr(argv[1]);
wrerr(": Cannot open file: ");
wrerr(msg);
wrerr(".\n");
return 127;
}
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO ||
dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) {
const char *msg = strerror(errno);
wrerr("Cannot duplicate file descriptors: ");
wrerr(msg);
wrerr(".\n");
return 126;
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) {
/* We might not have standard error anymore.. */
return 126;
}
/* Close fd, since it is no longer needed. */
if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
/* Execute the command. */
if (strchr(argv[2], '/'))
execv(argv[2], argv + 2); /* Command has /, so it is a path */
else
execvp(argv[2], argv + 2); /* command has no /, so it is a filename */
/* Whoops; failed. But we have no stderr left.. */
return 125;
}
Phải mất hai hoặc nhiều tham số. Tham số đầu tiên là một tệp hoặc thiết bị, và thứ hai là lệnh, với phần còn lại của các tham số được cung cấp cho lệnh. Lệnh này được chạy, với tất cả ba mô tả tiêu chuẩn được chuyển hướng đến tệp hoặc thiết bị được đặt tên trong tham số đầu tiên. Bạn có thể biên dịch ở trên bằng gcc bằng cách sử dụng ví dụ:
gcc -Wall -O2 run.c -o run
Hãy viết một tiện ích thử nghiệm nhỏ, report.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char buffer[16] = { "\n" };
ssize_t result;
FILE *out;
if (argc != 2) {
fprintf(stderr, "\nUsage: %s FILENAME\n\n", argv[0]);
return EXIT_FAILURE;
}
out = fopen(argv[1], "w");
if (!out)
return EXIT_FAILURE;
result = write(STDIN_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "write(STDIN_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "write(STDIN_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDOUT_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDERR_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDERR_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDERR_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
if (ferror(out))
return EXIT_FAILURE;
if (fclose(out))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
Phải mất đúng một tham số, một tập tin hoặc điện thoại để viết, để báo cáo bằng văn bản cho dù đầu vào tiêu chuẩn, và đọc từ đầu ra tiêu chuẩn và công việc lỗi. (Chúng ta thường có thể sử dụng $(tty)
trong các vỏ Bash và POSIX, để tham chiếu đến thiết bị đầu cuối thực tế, sao cho báo cáo có thể nhìn thấy trên thiết bị đầu cuối.) Biên dịch nó bằng cách sử dụng ví dụ:
gcc -Wall -O2 report.c -o report
Bây giờ, chúng ta có thể kiểm tra một số thiết bị:
./run /dev/null ./report $(tty)
./run /dev/zero ./report $(tty)
./run /dev/urandom ./report $(tty)
hoặc trên bất cứ điều gì chúng tôi muốn. Trên máy tính của tôi, khi tôi chạy trên một tập tin, nói
./run some-file ./report $(tty)
bằng văn bản cho đầu vào tiêu chuẩn, và đọc từ đầu ra tiêu chuẩn và sai số chuẩn tất cả các công việc - được như mong đợi, như mô tả tập tin tham khảo cùng , có thể đọc và ghi được, mô tả tệp.
Kết luận, sau khi chơi với phần trên, là có không có hành vi lạ ở đây tại tất cả. Tất cả đều hoạt động chính xác như mong đợi, nếu mô tả tệp được sử dụng bởi các quy trình chỉ đơn giản là tham chiếu đến mô tả nội bộ của hệ điều hành mô tả tệp và mô tả đầu vào, đầu ra và lỗi chuẩn là dup
ủy quyền cho nhau.
Tại sao trên thế giới bạn nghĩ rằng nó đang viết bất cứ điều gì để stdout? Nó đang viết cho thiết bị đầu cuối của bạn. Các stdout của quá trình của bạn có thể được kết hợp với thiết bị đầu cuối của bạn, nhưng họ không phải là điều tương tự. Đừng xúi giục cả hai. Trong trường hợp của bạn, stdin cũng được kết hợp với thiết bị đầu cuối vì vậy nó không đáng ngạc nhiên khi ghi vào stdin hiển thị trên thiết bị đầu cuối. –