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 有一個嚴格的約定。

答案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() 期間保持關閉狀態。

相關內容