14.高級IO

1.非阻塞IO

  • 設置方法
    • 1.open()時指定O_NONBLOCK標誌
    • 2.已經打開的可用fcntl()打開O_NONBLOCK標誌

    說明:POSIX標準規定無數據可讀時read()返回-1,,errno=EAGAIN,文件結束返回0


2.記錄鎖(字節範圍鎖)

商用UNIX系統提供了記錄鎖機制(使用數據庫的前提),POSIX標準的基礎是fcntl()方法,linux3.2.0支持fcntl()、lockf()、flock()

  • fcntl()記錄鎖
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    • cmd是F_GETLK、F_SETLK、F_SETLKW
    • 對三個參數是指向flock的指針
      struct flock {
          ...
          short l_type;    /* Type of lock: F_RDLCK,F_WRLCK, F_UNLCK */
          short l_whence;  /* How to interpret l_start: 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 (F_GETLK only) */
          ...
      };
      
      • l_len爲0表示鎖的範圍可擴大最大可能偏移量
      • 加讀鎖時描述符必須是讀打開,加寫鎖時必須是寫打開
      • 同一進程不適用兼容性規則(會直接覆蓋),故不能用F_GETLK來測試自己是否在文件的某一部分持有一把鎖
      • 在設置或釋放文件上的一把鎖時,系統按要求組合或分裂相鄰區
    • 死鎖
      • 兩個進程相互等待且不釋放
      • 一個進程已經控制了文件中的一個加鎖區域,又試圖對另一個進程控制的區域加鎖
  • 鎖的隱含繼承和釋放
    • 鎖與進程和文件兩者相關聯:
      • 進程終止時,建立的鎖全部釋放
      • 描述符關閉時,通過該描述符引用的文件上的任何一把鎖都會釋放
    • fork產生的子進程不會繼承父進程的鎖
    • exec()後,新進程基礎原執行程序的鎖(未設置O_CLOEXEC)
  • FreeBSD實現
    • 主要就是struct lockf結構是作爲v節點的一個表項(v又是i_node的表項),這樣就可以解釋自動繼承和釋放的規則,並可以看出文件鎖是非強制性鎖
    • 前面提到的可以用文件鎖保證守護進程只有一個實例在運行
    #define lockfile(fd) write_lock((fd),0,SEEK_SET,0)
    
  • 在文件尾端加鎖
    • 需要注意的是因爲還沒有獲得鎖,所以不能用fstat來得到當前文件的長度
      • 內核會將相對偏移量(SEEK_CUR或SEEK_END)轉換爲絕對偏移量
  • 建議性鎖和強制性鎖
    • 使用一致函數訪問數據庫的進程集叫合作進程(建議性鎖是可行的),但不能阻止對數據庫文件有寫權限的其他非合作進程。
      • 在linux上想使用,需要在各個文件系統的基礎上使用mount命令的-o mand選項來打開
    • SVR3:對一個特定的文件打開其設置組ID位、關閉其組執行位便開啓了對該文件的強制性鎖
    • open()一個具有強制性記錄鎖的文件時指定了O_TRUNC或O_CREAT,會失敗,errno=EAGAIN
    • 具有強制性記錄鎖的文件可以被刪除(所有鎖對某些編輯器沒用)

3.I/O多路轉接

3.1 函數select和pselect
/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);
  • timeout不爲空且超時事件未到時linux3.2.0將用剩餘時間更新該值
  • 說明:一個描述符阻塞與否並不影響select是否阻塞
  • 除一下幾點外,pselect與select相同
    • pselect使用timespec結構(秒和納秒),時間更精確
    • timeout爲const
    • 調用時以原子的方式安裝信號屏蔽字,返回時恢復
3.2 函數select和pselect
#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
        const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};
3.3 異步I/O
  • POSIX異步IO有下面這些侷限
    • 三個可能產生錯誤的地方:
      • 操作提交部分
      • 操作本身的結果
      • 決定異步操作的狀態函數中
  • System V異步I/O
    • 是streams的一部分(只對streams設備和管道起作用),信號是SIGPOLL
    • 啓動需要調用ioctl,第二個參數爲I_SETSIG,第三個參數見pdf421頁或stropts.h
    • streams在SUSv4被廢棄了,這裏就不討論了
  • BSD異步I/O
    • 我不寫IOS系統程序這裏就不討論了
  • POSIX異步
    • 對不同類型的文件提供了一套一致的方法,現在所有的平臺都被要求支持
    • 使用AIO控制塊
      /* Asynchronous I/O control block.  */
      struct aiocb {
          int aio_fildes;       /* File desriptor.  */
          int aio_lio_opcode;       /* Operation to be performed.  */
          int aio_reqprio;      /* Request priority offset.  */
          volatile void *aio_buf;   /* Location of buffer.  */
          size_t aio_nbytes;        /* Length of transfer.  */
          struct sigevent aio_sigevent; /* Signal number and value.  */
          /* Internal members.  */
          struct aiocb *__next_prio;};
      
      • 異步IO必須顯示指定偏移量(不會影響系統指定的偏移量),但是追加模式寫數據偏移量會被忽略
      • 系統並不一定遵循aio_reqprio字段請求的提示順序
      • aio_sigevent設置如何通知應用程序
        struct sigevent {
        
        int          sigev_notify; /* Notification method */
        int          sigev_signo;  /* Notification signal */
        union sigval sigev_value;  /* Data passed with notification */
        void       (*sigev_notify_function) (union sigval);
        /* Function used for thread notification (SIGEV_THREAD) */
        void        *sigev_notify_attributes;
        /* Attributes for notification thread (SIGEV_THREAD) */
        pid_t        sigev_notify_thread_id;
        /* ID of thread to signal (SIGEV_THREAD_ID) */
        };
        
  • 工作中用不到我就先不看啦,看了也記不住

4.函數readv()和writev()

也叫散步讀和聚集寫

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt,
                off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt,
                off_t offset);
struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};
  • 對於431頁的性能測試,雖然結果上緩衝區複製後接一次write時間少於一次writev(),那是因爲對於這種少量數據,使用writev的固定成本大於收益。
    • 總之,還是用盡量少的系統調用來完成任務

8.存儲映射I/O

將一個磁盤文件映射到存儲空間的一個緩衝區上

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
        int fd, off_t offset);
int munmap(void *addr, size_t length);
  • prot參數可以下面參數的按位與,不能超過open()時的權限:
    • PROT_READ
    • PROT_WRITE
    • PROT_EXEC
    • PROT_NONE
  • addr爲0可獲得最大的可移植性,
  • 基於上面的特性flag最好不要用MAP_FIXED
    • MAP_SHARED:
    • MAP_PRIVATE:創建文件的一個私有副本
    • off和addr通常是0
  • 這些在網絡編程卷2有更詳細的討論,這裏就不再贅述了
  • 需要注意的是:不能用mmap()將數據添加到文件中,必須先加長該文件,fork可以繼承存儲映射區但exec不能
  • 更改映射權限
    int mprotect(const void *addr, size_t len, int prot);
    
    • addr必須是頁長的整數倍?page435
  • 即使指定了MAP_SHARED,髒頁面何時寫回由內核守護進程決定
    int msync(void *addr, size_t length, int flags);
    
    • flags
      • MS_SYNC:同步方式
      • MS_ASYNC:異步方式,返回後不一定同步了
  • 關於性能,在page437頁linux 3.2.0對比中,可以看到使用read/write 總時長比mmap/memcpy要快將近4倍,但是CPU時間差不多(也可能CPU計算方式與我們理解的有誤差),而在Solaris 10中第二種方式更快
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章