Linux下的檔案I/O編程小結

來源:互聯網
上載者:User

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快,因為前者直接對映射取緩衝進行操作,而對於後者,核心需要將資料在使用者緩衝和它自己的緩衝之間進行拷貝。
但是記憶體映射不能用在某些裝置(如網路和終端裝置)之間進行複製,並且操作時必須注意檔案的長度是否改變。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.