Linux下的文件I/O編程小結

1.1  文件描述符
    文件描述符(fd)相當於windows編程中的文件句柄,使一個非負整數,引用一個打開的文件。
    Unix的慣例是文件描述符0(STDIN_FILENO)是標準輸出,1(STDOUT_FILENO)是標準輸出,2(STDERR_FILENO)是標準錯誤輸出。

1.2  文件的打開與關閉
1.2.1   相關函數
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    int creat(const char *pathname, mode_t mode);
    int close(int fd);
1.2.2   flag的選項
    O_RDONLY、O_WRONLY和O_RDWR,這三個標誌中只能指定一個。另外還有以下常數可以選擇:O_APPEND、O_CREAT、O_EXEL(與O_CREAT同時指定,而文件已存在則出錯,否則創建)、O_TRUNC、O_NONBLOCK(非阻塞方式)、O_SYNC(同步寫,每次write都等到物理I/O完成)等常用選項。
1.2.3   創建新文件
    當flags指定O_CREAT時,需要指定第三個參數mode,說明新文件的存取權限。也可以用creat函數,creat等價於:
    open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)
    
1.3  改變文件的偏移量
1.3.1   相關函數
    off_t lseek(int fildes, off_t offset, int whence);
1.3.2   設置文件位移量
    參數offset指定偏移量,參數whence可指定爲SEEK_SET(相對於文件頭)、SEEK_CUR(相對於當前位置)、SEEK_END(相對於從文件尾)。
    因爲位移量可能是負值,比較lseek的返回值時必須謹慎,不要測試它是否小於0,而要測試它是否等於-1。
1.3.3   文件空洞
    文件的位移量可以大於文件當前長度,這種情況下,對該文件的下一次寫操作將延長該文件。形成一個空洞,空洞中的字節都爲0。

1.4  文件的讀寫
1.4.1   相關函數
    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
1.4.2   讀文件
    read函數的返回值是實際讀到的字節數,他可能少於要求讀的字節數count:讀到文件尾了;或者讀的是終端設備、網絡設備或者某些面向記錄的設備時。
1.4.3   寫文件
    寫操作從當前位移開始。若打開文件時設置了O_APPEND選項,則每次寫之前,都會將當前位移量設爲文件尾。
1.4.4   效率
    使用不同的BUFFSIZE將數據從標準輸入讀入然後寫到標準輸出。在下面的測試中,標準輸入重定向到一個7175K的文件,標準輸出重定向到/dev/null。
    BUFFSIZE爲1:
    real    0m15.945s
    user    0m5.420s
    sys     0m10.500s
    
    BUFFSIZE爲4:
    real    0m4.025s
    user    0m1.570s
    sys     0m2.460s
    
    BUFFSIZE爲16:
    real    0m1.057s
    user    0m0.200s
    sys     0m0.860s
    
    BUFFSIZE爲256:
    real    0m0.153s
    user    0m0.030s
    sys     0m0.120s
    
    BUFFSIZE爲1024:
    real    0m0.093s
    user    0m0.000s
    sys     0m0.100s
    
    BUFFSIZE爲4096:
    real    0m0.081s
    user    0m0.010s
    sys     0m0.080s
    
    BUFFSIZE爲16384:
    real    0m0.078s
    user    0m0.000s
    sys     0m0.080s
    
    BUFFSIZE爲65536:
    real    0m0.080s
    user    0m0.000s
    sys     0m0.080s
繼續增大BUFFSIZE對系統時間並沒有影響。

1.5  複製文件描述符
1.5.1   相關函數
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);
1.5.2   說明
    這些函數返回一個和oldfd共享同一文件表項的新fd。不同之處是dup2可以指定新fd的值,並且如果newfd已經打開,則先將其關閉。他們可以用fcntl(oldfd,F_DUPFD, 0/newfd)實現,不同的只是errno,並且dup2是一個原子操作。

1.6  fcntl函數
1.6.1   相關函數
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    int fcntl(int fd, int cmd, struct flock *lock);
1.6.2   功能

  •     複製一個現存的描述符(cmd=F_DUPFD)。
  •     獲得/設置文件描述符標誌(cmd = F_GETFD或F_SETFD)。
  •     獲得/設置文件狀態標誌(cmd d = F_GETFL或F_SETFL)。
  •     獲得/設置異步I / O有權(cmd = F_GETOWN或F_SETOWN)。
  •     獲得/設置記錄鎖(cmd = F_GETLK, F_SETLK或F_SETLKW)。

1.6.3   F_GETFL或F_SETFL
    對於這兩種操作,可以獲得或者設置文件的狀態標誌。F_GETFL時可以獲得O_RDONLY、O_WRONLY、O_RDWR、O_APPEND、O_NONBLOCK、O_SYNC和O_ASYNC。對於前三種標誌,必須使用屏蔽字O_ACCMODE,然後將結果與這三種標誌一一比較。F_SETFL時只能設置後四種標誌。
    accmode = val & O_ACCMODE;
    if (accmode == O_RDONLY) …
    else if (accmode == O_WRONLY) …
    else if (accmode == O_RDWR) …
    else err_dump("unknown access mode");
    
    if (val & O_APPEND) …
    if (val & O_NONBLOCK) …
    ……

1.7  記錄鎖
1.7.1   相關函數:
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, struct flock *lock);
1.7.2   相關結構:
    struct flock {
        short l_type;         /* F_RDLCK, F_WRLCK, F_UNLCK */
        short l_whence;     /* SEEK_SET, SEEK_CUR, SEEK_END */
        off_t l_start;         /* Starting offset for lock */
        off_t l_len;       /* Number of bytes to lock */
        pid_t l_pid;       /* PID of process blocking our lock */
    };

    l_start和l_whence決定加鎖或解鎖區域的起始位置,l_len決定其長度。該區域可以越過文件尾端,但不能越過文件起始位置。l_len爲0,則表示鎖的區域從開始位置直至文件尾,而不管文件如何增長。鎖整個文件的通常方法是:l_start爲0,l_whence爲SEEK_SET,l_len爲0。
1.7.3   共享鎖和獨佔鎖
    共享鎖,又叫讀鎖(RDLCK);獨佔鎖,又叫寫鎖(WRLCK)。

表格 1 (讀鎖和寫鎖)

當前所有/要求

讀鎖

寫鎖

讀鎖(一把或多把)

可以

拒絕

寫鎖

拒絕

拒絕

1.7.4   加鎖和解鎖
    根據fcntl函數的cmd參數進行不同操作:
 

  • F_GETLK:檢查是否可以建立參數lock描述的鎖

  • F_SETLK:設置lock描述的鎖,出錯立刻返回
  • F_SETLKW:F_SETLK的阻塞版,若不能加鎖則等待


1.7.5   鎖的繼承和釋放
    進程終止時,它建立的鎖全部釋放。
    關閉一個文件描述符時,則與該描述符相關的文件上的所有由這個進程設置的鎖都被釋放,即使進程還有打開的描述符指向該文件。
    fork產生的子進程不繼承父進程設置的鎖。而exec調用後,新程序可以繼承原執行程序的鎖。
1.7.6   建議性鎖和強制性鎖
    建議性鎖(Advisory lock)並不能保證其他對文件有寫權限的進程讀寫文件,使用建議性鎖的程序必須以一致的方法處理記錄鎖。使用強制性鎖(Mandatory lock),則內核對每個open、read和write都要檢查調用進程是否違背了某一把鎖的作用。
    要使用強制性鎖,首先文件所在的文件系統必須開啓此功能(mount的時候加上-o mand參數);然後打開其set-guid,關閉其組執行位(chmod命令中的g-x和g+s)即可。強制性鎖的使用和建議性鎖基本相同。]
    強制性鎖不是POSIX標準。

1.8  I/O多路轉接
1.8.1   相關函數:
    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    int   pselect(int   n,   fd_set   *readfds,  fd_set  *writefds,  fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask)
    int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
    
    FD_CLR(int fd, fd_set *set);
    FD_ISSET(int fd, fd_set *set);
    FD_SET(int fd, fd_set *set);
    FD_ZERO(fd_set *set);
1.8.2   相關結構
    fd_set是一個fd的集合,有四個宏可對其進行操作:清空一個fd即(FD_ZERO),檢測指定 fd是否在集中(FD_ISSET),加入(FD_SET)和移除(FD_CLR)一個fd。
    select使用結構timeval:
        struct timeval {
            long    tv_sec;         /* seconds */
            long    tv_usec;        /* microseconds */
        };
    pselect使用結構timespec:
        struct timespec {
            long    tv_sec;         /* seconds */
            long    tv_nsec;        /* nanoseconds */
        };
1.8.3   說明
    select等待指定的fd對應的文件準備就緒。對於readfds中的文件,等待起可讀;對於writefds中的文件,等待其可寫;對於exceptfds中的文件等待其發生異常。而n則是參數中最大的fd加1,可以設爲FD_SETSIZE,但會影響效率。timeout則是等待的時間,爲NULL則無限等待。返回值是就緒的fd的個數。
    在select函數中,如果在一個fd上碰到了文件結束,則select認爲該fd是可讀的,而不是指示一個異常。
    pselect和select類似,只是使用了timespec結構指定時間,另外和sigsuspend類似,在等待時會將信號屏蔽改爲sigmask。
    poll是select函數的另一版本,不同之處是它構造一格pollfd結構數組,對每個元素指定一個fd及對其關心的條件。
1.8.4   使用
    select可以作爲提供更精確的sleep函數使用。
    當必須讀寫多個fd時,使用select函數,可以避免長時間阻塞在一個fd上而另一個fd的數據卻得不到及時處理的情況。

1.9  讀寫多個緩存
1.9.1   相關函數
    ssize_t readv(int fd, const struct iovec *vector, int count);
    ssize_t writev(int fd, const struct iovec *vector, int count);
1.9.2   相關結構
    struct iovec {
        void *iov_base;   /* Starting address */
        size_t iov_len;   /* Number of bytes */
    };
1.9.3   說明
    這兩個函數用於讀寫多個緩存。讀寫的順序是按數組的順序。
1.9.4   性能
    兩個緩存,一個512,一個1024,分別用三種方法將其寫入文件10000遍,第一種是調用writev,第二種是每個循環調用兩次write,第三種是聲名一個1536的緩存,將前兩個緩存的內容都拷貝入這個緩存中然後調用一次write,結果如下。右邊是apue上的測試結果,他的緩存大小爲100、200,機器是80386。
    one writev:
    real    0m0.318s      8.2s
    user    0m0.000s      0.3s
    sys     0m0.320s      7.8s
    
    two write:
    real    0m0.990s      13.7s
    user    0m0.000s      0.5s
    sys     0m0.330s      13.1s
    
    memcpy and one write
    real    0m0.255s      8.1s
    user    0m0.010s      0.7s
    sys     0m0.250s      7.3s

    很奇怪,調用2次write所用系統時間應該是調用一次write或writev的2倍,但我的出的結果卻是調用一次writev和兩次write所用系統時間差不多,而memcpy後調用一次write反而比一次writev還快。

1.10  I/O存儲映射
1.10.1   概念
    存儲映射使一個磁盤文件或設備同存儲空間的一個緩存區相映射。於是從緩存中存取數據就相當於文件讀寫。
1.10.2   相關函數
    void  *  mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
    int munmap(void *start, size_t length);
    int msync(void *start, size_t length, int flags);
1.10.3   說明
    start參數一般設爲0,即讓系統選擇映射緩存取的起始地址,這個地址通過返回值獲得。fd,length和offset指定文件區域。prot指定對映射區的保護方式,flags則是一些標識位。MAP_SHARED表示和其他進程共享此映射,存取緩存取回直接反映到文件中;MAP_PRIVATE表示獨享此進程,寫入映射緩存區並不影響原文件。這兩個標識只能指定一個。
    munmap解除存儲映射。要注意的是,關閉文件描述符並不解除對此文件的映射。調用munmap也並不使映射區的內容寫到文件中,同步文件和映射區可以調用msync函數。
1.10.4   使用
    使用mmap/memcpy進行文件操作比read/write快,因爲前者直接對映射取緩存進行操作,而對於後者,內核需要將數據在用戶緩存和它自己的緩存之間進行拷貝。
但是內存映射不能用在某些設備(如網絡和終端設備)之間進行復制,並且操作時必須注意文件的長度是否改變。

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