系統級 I/O

劉小緒同學發表於2019-01-21

    檔案對於我們來說,貌似再普通不過了,windows 使用副檔名來區分不同的檔案,我們接觸了 gif、docx、pdf、mp3、mp4、exe 等等好多好多不同的檔案,但是它們在磁碟中都長一個樣子,對於核心而言,沒有什麼區別。

基本概念

    檔案這個概念就是對 I/O裝置 的抽象,一個檔案就是一串 m 個位元組的序列,所有的 I/O 裝置(網路、磁碟、終端)都被模型化為檔案,所有的輸入輸出都被當做對相應檔案的讀和寫來執行。

image

    稍微特殊一點的是檔案目錄吧,目錄也是一個檔案,它是包含一組連結的檔案,其中每個連結都將一個檔名對映到一個檔案,這個檔案也可能是另一個目錄。

    每個目錄至少會有兩個條目,.是到該目錄自身的連結;..是到父目錄的連結,你可以使用mkdir命令建立一個目錄,然後檢視其內容觀察觀察,linux 用ls檢視,windows 用dir

image

    每個程式都有一個當前工作目錄,linux 是以/作為根目錄的,所以絕對路徑就是一個/開始,如下圖所示,hello.c 的絕對路徑名即為:/home/droh/hello.c;如果程式的當前工作目錄為:/home/droh,那麼 hello.c 的相對路徑就為:./hello.c,而要是工作目錄為:/home/bryant,那麼相對路徑名則為:../droh/hello.c

image

    我們可以使用open函式來開啟一個檔案,它的作用是把檔名轉換為一個檔案描述符,這個描述符是一個小的非負整數,在後續對此檔案的所有操作中標識這個檔案。open函式返回的描述符總是在程式中當前沒有開啟的最小描述符。

    Linux shell 建立的每個程式開始都有三個開啟的檔案:標準輸入(描述符為 0)、標準輸出(描述符為 1)、標準錯誤(描述符為 2)。

    open函式有三個入參,分別為filename, flags, mode,flags 指明瞭如何訪問這個檔案,即只讀、只寫、可讀寫;mode 指明瞭新檔案的訪問許可權位,它通過與程式自帶的umask進行運算來獲得檔案的訪問許可權,這個運算時:mode & ~umask

    讀檔案是從描述符為 fd 的當前檔案位置複製 n 個位元組到記憶體位置 buf,寫檔案則是把記憶體中的位元組複製到當前檔案中,系統中提供了 readwrite函式來提供相應的功能。

    read函式的返回值是代表實際傳送的位元組數,有趣的是當出錯時,它需要返回 -1,因此使用的是一個有符號長整數,而就僅僅為了這個 -1,就使得read的最大值減小了一半。

    當然還有可能會遇到需要讀取的位元組比檔案實際位元組數要多的情況,這時就會觸發一個稱為 end-of-file(EOF) 的條件,應用程式死可以檢測到這個條件的,此時read會返回 0。

    我總算知道在學校做 oj 題時,為什麼要把while(scanf("%s", str[i]))寫成while(scanf("%s", str[i]) != EOF)了。

共享檔案

    共享檔案的方法很多,但是共享檔案的概念比較晦澀難懂。

    系統核心使用三個相關的資料結構來表示開啟的檔案,分別為:描述符表、檔案表、v-node 表。每個程式都有一張描述符表,其表項是由程式開啟的檔案描述符來索引的;檔案表表示所有的開啟檔案的集合,所有程式共享這個表,關閉一個描述符會減少相應的檔案表表項中的引用計數,當引用計數為零時,核心就睡刪除對應的表項(是不是和垃圾回收機制很像?);v-node表 也是所有程式共享的,表中包含了使用者組、大小等很多資訊。

image

    如上圖所示,是一個程式開啟了兩個不同的檔案的樣子,這種情況下沒有共享檔案;而如果以同一個 filename 呼叫 open 函式兩次,就會發生共享檔案的情況,其關鍵思想是每個描述符都有它自己的檔案位置,如下圖所示。

image

    有了上面的基礎,那理解子程式如何繼承父程式開啟的檔案就容易的多了,直觀展示出來就是下面這個樣子。

image

    最後來看一個簡單的題:下面程式的輸出是什麼?

int main()
{
    int fd1, fd2;
    fd1 = Open("foo.txt", O_RDONLY, 0);
    Close(fd1);
    fd2 = Open("baz.txt", O_RDONLY, 0);
    printf("fd2 = %d\n", fd2);
    exit(0);
}

    Unix 程式生命週期開始,開啟的前三個描述符已經被使用了,而 open 函式總是返回最低的未開啟的描述符,所以第一次呼叫 open 函式會返回 3,呼叫 close 函式會釋放描述符 3,所以最後對 open 函式的呼叫還是會返回 3,即程式輸出是:fd2 = 3

    在學生時代,聽到 I/O、檔案等名詞時,下意識就認定了對方是個厲害的角色,本來以為自己也會能操作檔案後就不會有這種感覺了,而實際情況依舊如此,聽到老程式設計師談到 socket 等名詞時,依舊充滿了景仰之情。

    團隊老員工告訴我,很多工具看原始碼,都是一樣的原理,應該好好看下 socket 程式設計,讀完這一章,我貌似有點明白同事的意思了。

相關文章