UNIX高級編程總結-----文件I/O (二)

上一篇

九、文件共享

        內核使用了3種數據結構表示打開的文件,他們之間的關係決定了在文件共享方面一個進程對另一個進程可能產生的影響。以下是書中總結的三個部分:

        如果說,兩個單獨的進程各自打開了同一個文件,其關係如下圖:

        由圖3-8可以看出,兩個進程打開同一個文件,他們各自有自己的文件表項,但是其v節點指向的是同一個v節點表項。之所以每個進程都獲得自己的文件表項,是因爲這可以使每個進程都有他自己的對文件的當前偏移量。以下是對其進行進一步說明:

注意:文件描述符標誌  和  文件狀態標誌在左右範圍方面是有區別的。

        文件描述符標誌,只用於對一個進程的一個描述符,每一個進程都有屬於自己的一個。

        文件狀態標誌,則應用於指向該給定文件表項的任何進行的所有描述符。只要指向該文件表項,都是這個狀態

 

十、文件的原子性操作

        1、對於一個稍微成型點的項目而言,多進程和多線程異步衝突問題,都是一件很重要也很頭疼的事。對於多線程操作一個文件也是這樣的。在Unix早期,open 中沒有 O_APPEND 這個選項,打開文件追加一般都是通過兩步去實現的:

        這樣就會造成一個問題,如果有A、B兩個進程同時打開一個文件,然後同時利用 lseek 將文件指針移到1500字節處。這是他們都沒有做 IO 操作。如果B進程先 write 100 字節的數據,這是在B進程的文件表項中,文件偏移量爲1600。然後再用 A 進程write 100字節數據,這是A進程寫入的100字節就會覆蓋掉B進程寫入的100字節。

        爲解決上述問題,引入了O_APPEND。如果還是上述情況,在B進程寫入100字節數據之後,A進程的文件偏移也會向後移100字節,到文件的尾部,這時再寫入數據就不至於覆蓋了。

       2、書中還介紹了兩個在多線程或是多進程開發中非常好用的兩個函數:

        這兩個讀寫函數相當於 lseek 之後,進行read 和 write。但是有兩點需要注意(重點注意第二點):

        (1) 它們都是原子操作,無法將 lseek 和讀寫操作分開。

        (2) 它們可以指定一個偏移量進行讀寫,但是該讀寫無法改變當前的偏移量。也就是藉助上一例子。A進程pwrite 1500處寫入100字節之後,現在的偏移位置還是在1500處。如果用write會到1600處。

        3、在創建一個文件時也需要考慮原子操作,書中舉了一個不考慮原子性的一個原始做法去打開創建一個文件。

        根據上述代碼不難看出,在A進程操作時還沒有這個文件,它會 open 出 ENOENT 的錯,這時就要創建文件了,但是進程B去創建了這個文件,這時進程A在creat處創建文件失敗,並且將B進程已創建的文件清空。

        所以建議使用open 加  O_CREAT | O_EXCL 的方式去創建和檢查一個文件的存在。

 十一、文件描述符的複製 dup 和 dup2

        對文件描述符的複製,主要有三個方式。下面的兩個函數是兩種,還有一種是用 fcntl 函數。

  

        dup 函數返回的是當前可用文件描述符中最小的一個。

        dup2則是用 fd2 指定的新的描述符的值。如果fd2已經打開,則將其關閉。若fd 和 fd2相等,則返回fd2,並不關閉它。

        這些函數返回的新的文件描述符與參數fd共享一個文件表項,如下圖:

        用fcntl函數複製文件描述符方式如下:

十二、同步函數(sync、fsync 和 fdatafsync)

        傳統的UNIX實現在內核中設有緩衝區高速緩存或頁面高速緩存,大多數磁盤I/O都通過緩衝進行。當將數據寫入文件時,內核通常先將該數據複製到其中一個緩衝區中,如果該緩衝區尚未寫滿,則並不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩衝區以便存放其他磁盤塊數據時,再將該緩衝排入輸出隊列,然後待其到達隊首時,才進行實際的I/O操作。這種輸出方式被稱爲延遲寫(delayed write)

        延遲寫減少了磁盤讀寫次數,但是卻降低了文件內容的更新速度,使得欲寫到文件中的數據在一段時間內並沒有寫到磁盤上。當系統發生故障時,這種延遲可能造成文件更新內容的丟失。爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數。
        sync函數只是將所有修改過的塊緩衝區排入寫隊列,然後就返回,它並不等待實際寫磁盤操作結束。
通常稱爲update的系統守護進程會週期性地(一般每隔30秒)調用sync函數。這就保證了定期沖洗內核的塊緩衝區。命令sync(1)也調用sync函數。
        fsync函數只對由文件描述符filedes指定的單一文件起作用,並且等待寫磁盤操作結束,然後返回。fsync可用於數據庫這樣的應用程序,這種應用程序需要確保將修改過的塊立即寫到磁盤上。
        fdatasync函數類似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。

書上介紹不夠詳細。

十二、函數 fcntl

        這一部分就是講了 fcntl 的各種用法,最後書中的一個例子不錯。

fcntl函數有五個功能如下:

 

還有11種 cmd 介紹了前8個,下面是書中的介紹:

#include "apue.h"
#include <fcntl.h>

int
main(int argc, char *argv[])
{
    int        val;

    if (argc != 2)
        err_quit("usage: a.out <descriptor#>");

    if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
        err_sys("fcntl error for fd %d", atoi(argv[1]));

    switch (val & O_ACCMODE) {
    case O_RDONLY:
        printf("read only");
        break;

    case O_WRONLY:
        printf("write only");
        break;

    case O_RDWR:
        printf("read write");
        break;

    default:
        err_dump("unknown access mode");
    }

    if (val & O_APPEND)
        printf(", append");
    if (val & O_NONBLOCK)
        printf(", nonblocking");
    if (val & O_SYNC)
        printf(", synchronous writes");

#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
    if (val & O_FSYNC)
        printf(", synchronous writes");
#endif

    putchar('\n');
    exit(0);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章