shell/init 如何创建 stdio 流?

shell/init 如何创建 stdio 流?

我正在阅读麻省理工学院的来源xv6操作系统。该片段出现在以下内容的开头sh.c

// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
}

我知道这会检查是否至少通过检查新分配的文件描述符是否高于(或相同)3,打开 3 个文件描述符(大概用于 stdin、stdout 和 stderr)。

1)如何可能open从同一进程多次访问同一设备并期望不同的文件描述符?

2)为了理解这一点,我在我的主机(x86_64 Linux 4.6.0.1)上运行了类似的代码片段。测试程序open在循环中重复编辑一个文本文件,看看我们是否可以期待不同的 fd,但它总是产生相同的文件描述符。由此,我得出结论,open-ing 一个真实文件和一个设备(如/dev/console)在某种程度上有所不同,因为来自 xv6 的代码片段显然有效(在 Qemu 中测试)。到底有什么区别呢?

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int cnt = 0;

    while ((fd = open("sample.txt", O_RDWR) > 0)) {
        if (cnt != 10) {
            cnt++;
            printf("File descriptor opened: %d\n", fd);
        } else {
            break;
        }
    }

    return 0;
}

这是运行它的输出:

$ ./a.out
File descriptor opened: 1
File descriptor opened: 1
[snip]
File descriptor opened: 1
File descriptor opened: 1

编辑根据答案之一,我运行strace了可执行文件并发现open 的确返回多个文件描述符,但由于某种原因,所有这些描述符都没有被打印。为什么会这样呢?

3)有点不相关,但在 fds 0-2 中使用 stdio 流的约定不就是这样 - 一个约定吗?例如,如果初始化序列将输入/输出文件描述符分配给其他东西 - 它会以某种方式影响其子级执行 I/O 的方式吗?

答案1

这实际上是3个问题。立即处理#2,因为程序不正确:

    while ((fd = open("sample.txt", O_RDWR) > 0)) {

你可能的意思是

    while ((fd = open("sample.txt", O_RDWR)) > 0) {

使用不正确放置的括号,您只是测试是否fd大于零(由于文件描述符 0、1 和 2 是打开的,这可能是一个很好的假设)。

对于#1:open调用(成功时)被定义为返回不同的文件描述符。如果设备无法重新打开,open将返回-1

对于#3:当然,那是习俗,而且还在POSIX标准。其他系统使用其他约定,包括为每个程序设置第四个开放流。

进一步阅读:使用您的 Aegis 环境(1988 年 7 月)
请参阅第 6-9 页,其中显示 Apollo Domain/OS 有错误输入和**输出*

答案2

不,代码没有查看描述符,它实际上打开它们。尚未给出描述符。每次打开都会给出一个新的文件描述符,即0,1,2,3。当到达 fd 3 时代码中断,而 0 到 2 保持打开状态。

每个文件描述符只是指向某个文件中某个位置的指针。因此,同一文件有多个描述符是没有问题的。

如果您的测试程序为不同的打开调用提供相同的 fd,则其中存在错误。请出示代码。

是的,fd 0 到 2 有一个严格的约定。如果某些代码想要在 stdout 上打印,它实际上会在 fd 1 上打印。没有办法将 stdout“映射”到其他内容。

答案3

1)如果系统支持多个进程同时打开同一个文件,那么为什么不允许一个进程多次打开呢?由于文件描述符是继承的,因此您最终可能会在同一个进程中两次使用同一个文件,即如果它被继承一次并由进程本身打开一次。检查进程打开了哪些文件并返回对较早文件的引用将是额外的工作。

另外,还有一个问题是进程的不同部分是否同时使用同一个文件。假设一个库在主程序使用它的同时使用了一些配置文件。文件描述符与访问模式和文件指针的位置相关。如果只有一个副本,就会发生奇怪的事情。另外,如果文件被打开两次(到同一个 fd),那么关闭时会发生什么?可能还有另一层引用计数来决定何时真的关闭文件,但这无助于解决其他问题。


2)取决于您的代码。智能语言(即不是 C)可能会对保存打开文件的变量进行重新计数,并在重新打开文件之前将其关闭。没有看到代码很难说。

但做个小测试,在 Perl 中对同一个变量打开同一个文件两次会产生相同的 FD 编号:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open F, "test.txt"; printf "%d\n", fileno(F)'
3 3

运行它strace表明该文件在重新打开之前立即关闭。

通过两个不同的变量,我们得到两个 FD 数:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open G, "test.txt"; printf "%d\n", fileno(G)'
3 4

3) 从技术上讲,您可以说标准文件编号是一种约定。编入的公约POSIX 和 ISO C 标准

在程序启动时,应预定义三个流,无需显式打开:标准输入(用于读取常规输入)、标准输出(用于写入常规输出)和标准错误(用于写入诊断输出)。

但无论如何,约定是可以在没有它们的情况下运行程序,而无需内核关注。或者它可能不是:阅读调用规范exec,它似乎是允许实施为您打开一些东西

如果在成功调用 exec 系列函数之一后文件描述符 0、1 或 2 将被关闭,则实现可能会在新进程映像中为文件描述符打开一个未指定的文件。

(您当然可以在程序本身中关闭它们。)

如果您安排在启动时不使用它们,那么兼容性就会被排除在外:

如果标准实用程序或符合要求的应用程序在文件描述符 0 未打开以供读取或文件描述符 1 或 2 未打开以写入的情况下执行,则执行实用程序或应用程序的环境应被视为不符合要求,因此实用程序或应用程序的行为可能不符合本标准中的描述。

Linux 手册页说得很实用:

作为一般原则,任何可移植程序,无论是否有特权,都可以假设这三个文件描述符在 execve() 期间保持关闭状态。

相关内容