UNIX高級環境編程 第14章

第14章 高級IO

14.1 引言

本章的概念和函數
1. 非阻塞I/O
2. 記錄鎖
3. I/O多路轉接(select & poll)
4. 異步I/O
5. readv & writev函數
6. 存儲映射I/O: mmap數組

14.2 非阻塞I/O

低速系統調用可能會使進程永久阻塞,包括:
--1. 若是數據並不存在,則讀文件可能會使調用者永遠阻塞·--例如讀管道、終端設備和網絡設備
--2. 若是數據不能當即被接受,則寫這些一樣的文件也會使調用者永遠阻塞
--3. 在某些條件發生以前,打開文件會被阻塞·--例如以只寫方式打開一個FIFO,
-----那麼在沒有其餘進程已用讀方式打開該FIFO時;
--4. 對已經加上強制性鎖的文件進行讀、寫;
--5. 某些ioctl操做;
--6. 某些進程間通訊函數;網絡

非阻塞IO調用open、read和write等IO操做使上述的慢速系統調用在不能當即完成的狀況下,當即出錯返回。異步

設置非阻塞IO方法:函數

open(pathname, O_NONBLOCK | O_... , S_...)
int status;
status = fcntl(fd, GETFL, 0);
status |= O_NONBLOCK;
fcntl(fd, SETFL, status);

14.3 記錄鎖

記錄鎖的功能是:
一個進程正在讀或修改文件的某個部分時,能夠阻止其餘進程修改同一文件區域.測試

int fcntl(int fd, int cmd,  struct flock *flockptr);

struct flock 結構以下:spa

struct flock{
    short l_type;
    short l_whence;
    off_t l_start;
    off_t l_len;
    pid_t l_pid;
};

結構說明操作系統

l_type:
F_RDLCK(共享讀鎖)、F_WRLCK(獨佔性寫鎖)或F_UNLCK(解鎖一個區域)3d

加鎖或解鎖的區域:
l_whence : SEEK_SET, SEEK_CUR, SEEK_END
l_start: 相對於l_whence的起點
l_len: 區域的長度rest

注意
1. 該區域能夠在當前文件尾端處開始或越過其尾端處開始,可是不能在文件起始位置以前開始或越過該起始位置。
2. 如若l_len爲0,則表示鎖的區域從其起點(由l_start和l_whence決定)開始直至最大可能位置爲止。
---也就是無論添寫到該文件中多少數據,它都處於鎖的範圍。
3. 爲了鎖整個文件,一般的方法是將l_start說明爲0,l_whence說明爲SEEK_SET,l_len說明爲0code

fcntl cmd類型

F_GETLK:
決定由flockptr所描述的鎖是否被另一把鎖所排斥(阻塞)。
若是存在一把鎖,它阻止建立由flockptr所描述的鎖,則這把現存的鎖的信息寫到flockptr指向的結構中。
若是不存在這種狀況,則除了將l_type設置爲F_UNLCK以外,flockptr所指向結構中的其餘信息保持不變。
這意味則利用F_GETLK不能獲取由本進程維護的記錄鎖。

F_SETLK:
F_SETLK設置由flockptr所描述的鎖。若是試圖創建一把按上述兼容性規則並不容許的鎖,則fcntl當即出錯返回
此時errno設置爲EACCES或EAGAIN。

F_SETLKW:
是F_SETLK的阻塞版本。若是因爲存在其餘鎖,那麼由flockptr所要求的鎖不能被建立,則調用進程睡眠。若是捕捉到信號則睡眠中斷。

注意
用F_GETLK測試可否創建一把鎖,而後用F_SETLK和F_SETLKW企圖創建一把鎖,這二者不是一個原子操做。
在這兩個操做之間可能會有另外一個進程插入並創建一把相關的鎖,使原來測試到的狀況發生變化.
若是不但願在創建鎖時可能產生的長期阻塞,則應使用F_SETLK,並對返回結果進行測試,以判別是否成功地創建了所要求的鎖。

利用宏填充struct flock

int file_lock(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_whence = whence;
    lock.l_start = offset;
    lock.l_len =len;
    return (fcntl(fd,cmd,&lock));

}
#define file_read_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLK),(F_RDLCK),(offset),(whence),(len))
#define file_readw_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLKW),(F_RDLCK),(offset),(whence),(len))
#define file_write_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLK),(F_WRLCK),(offset),(whence),(len))
#define file_writew_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLKW),(F_WRLCK),(offset),(whence),(len))

鎖的隱含繼承和釋放

1. 當一個進程終止時,它所創建的鎖所有釋放;任什麼時候候關閉一個描述符時,則該進程經過這一描述符能夠存訪的文件上的任何一把鎖都被釋放
2. 由fork產生的子程序不繼承父進程所設置的鎖。
3. 在執行exec後,新程序能夠繼承原執行程序的鎖。

14.4 IO多路轉接

14.4.1 函數select


#include <sys/select.h>
int select(int maxfdp1, fd_set* readfds, fd_set* restrict writefds, 
                        fd_set* restrict exceptfds, struct timeval * restrict tvptr);

參數tvptr的含義
1. timeout == NULL,永遠等待
2. timeout->tv_sec == 0 && timeout->tv_usec == 0,不等待
3. timeout->tv_sec != 0 || timeout->tv_usec != 0,等待指定時間

操做

void FD_CLR(fd, fd_set *fdset);
void FD_ISSET(fd, fd_set *fdset);
void FD_SET(fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset); // 初始化

第一個參數maxfdp1的意思就是「最大文件描述符編號值+1」

int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, 
                      fd_set *restrict errorfds, const struct timespec *restrict timeout, 
                                                 const sigset_t *restrict sigmask);

pselect可以使用可選信號屏蔽字,若是sigmask爲null,則二者同樣,可是sigmask指向屏蔽字的時候,將以原子操做形式安裝屏蔽字

14.4.2 函數poll

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

struct pollfd 結構體

struct pollfd {
    int    fd;       /* file descriptor */
    short  events;   /* events to look for */
    short  revents;  /* events returned */
};

events 和 revents 類型
clipboard.png

14.5 異步I/O

異步IO使用AIO控制塊來描述IO操做:

struct aiocb {
        int             aio_fildes;             /* File descriptor */
        off_t           aio_offset;             /* File offset */
        volatile void   *aio_buf;               /* Location of buffer */
        size_t          aio_nbytes;             /* Length of transfer */
        int             aio_reqprio;            /* Request priority offset */
        struct sigevent aio_sigevent;           /* Signal number and value */
        int             aio_lio_opcode;         /* Operation for list IO */
};

aio_fildes就是文件描述符
讀寫操做從aio_offset指定的偏移量位置開始,長度爲aio_nbytes
aio_reqprio就是異步IO請求的順序,當不必定遵照
aio_sigevent就是IO事件完成後如何通知

typedef struct sigevent
  {
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;

    union
    {
        int _pad[__SIGEV_PAD_SIZE];

        /* When SIGEV_SIGNAL and SIGEV_THREAD_ID set, LWP ID of the
        thread to receive the signal.  */
        __pid_t _tid;

        struct
        {
           void (*_function) (sigval_t);    /* Function to start.  */
           pthread_attr_t *_attribute;        /* Thread attributes.  */
        } _sigev_thread;
    } _sigev_un;
  } sigevent_t;

# define sigev_notify_function   _sigev_un._sigev_thread._function
# define sigev_notify_attributes _sigev_un._sigev_thread._attribute

APUE課本結構

struct sigevent {
        int  sigev_notify;                              /* Notification type */
        int  sigev_signo;                               /* Signal number */
        union sigval    sigev_value;                    /* Signal value */
        void (*sigev_notify_function)(union sigval);    /* Notification function */
        pthread_attr_t  *sigev_notify_attributes;       /* Notification attributes */
}

sigev_notify字段是通知類型
1. SIGEV_NONE 不通知進程
2. SIGEV_SIGNAL 異步IO完成後,產生sigev_signo指定的信號
3. SIGEV_THREAD 異步請求完成後,由sigev_notify_function指定的函數被調用

注意:異步操做不影響有操做系統維護的文件偏移量
注意:若以追加方式打開文件時,aio_offset被忽略

異步讀寫操做:

int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);

當兩個函數返回時,異步IO請求被放在了等待處理隊列中。但返回值與實際IO操做的結果沒有任何關係。
若是想要強制全部等待中的異步操做不等待直接寫入存儲,則調用aio_fsync函數 :

int aio_fsync(int op, struct aiocb *aiocb);

注意: aio_fsync的返回時,讀寫操做也可能並未完成

獲取一個異步讀寫的完成狀態,調用aio_error函數 :

int aio_error(const struct aiocb *aiocbp);

返回值 :
0,     異步操做成功,使用aio_return函數得到返回值
-1,    對aio_error操做失敗
EINPROGRESS,  讀寫操做仍處於等待狀態

異步操做完成後,調用aio_return :

ssize_t aio_return(struct aiocb *aiocbp);

注意:記住在aio_error檢查成功以前,不要使用aio_return函數
注意:每一個異步操做只能調用一次aio_return函數。

aio_suspend函數會阻塞當前進程直到list中的 任意一個 操做完成 :

int aio_suspend(const struct aiocb *const list[], int nent, const struct timespec *timeout);

返回值 :
0,     操做成功,某個異步操做完成
-1,    信號中斷,設置errno爲EINTR;超時,設置errno爲EAGAIN

取消已經處於進行中的異步操做:

int aio_cancel(int fildes, struct aiocb *aiocbp);

返回值:
AIO_ALLDONE,全部操做已經完成
AIO_CANCELED,全部操做已經取消
AIO_NOtCANCELED,至少有一個請求沒有取消
-1,對aio_cancel調用失敗

把多個異步操做 集中處理 :

int lio_listio(int mode, struct aiocb* restrict const list[restrict], 
                int nent, struct sigevent *restrict sigev);

struct aiocb中的成員aio_lio_opcode指定了操做類型

14.6 函數readv和writev

兩個函數用於在一次函數調用中讀寫多個非連續緩衝區

ssize_t readv(int d, const struct iovec *iov, int iovcnt); // iovcnt 爲數組長度

ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);

struct iovec結構體 :

struct iovec {
    char   *iov_base;  /* Base address. */
    size_t iov_len;    /* Length. */
};

clipboard.png

14.7 函數readn和writen

ssize_t readn(int fd, void* buf, size_t nbytes);
ssize_t writen(int fd, void* buf, size_t nbytes);

兩個函數按需屢次調用read和write直至讀或寫N字節數據

14.8 存儲映射I/O

存儲映射IO能將一個磁盤文件映射到存儲空間中的一個緩衝區上,因而,當從緩衝區中讀取數據的時候,就等同於讀取文件。

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
// 建立匿名存儲映射,對存儲區的讀寫不涉及磁盤的讀寫,可是隻能在相關進程之間進行通訊
mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONUMOUS, 0, 0);

內存地址空間: [addr, addr + len)
addr 默認指定爲0,有系統指定內存區域

clipboard.png

描述符爲fd的文件被映射區域: [offset, offset + len)

參數prot:映射區域的讀寫執行特性

clipboard.png

參數flag :
MAP_FIXED
返回值必須等於addr。由於這不利於可移植性,因此不鼓勵使用此標誌。
若是未指定此標誌,並且addr非0,則內核只把addr視爲什麼處設置映射區的一種建議。
經過將addr指定爲0可得到最大可移植性。

MAP_SHARED
這一標誌說明了本進程對映射區所進行的存儲操做的配置。
此標誌指定存儲操做修改映射文件—也就是,存儲操做至關於對該文件write。
必須指定本標誌或下一個標誌(MAP_PRIVATE)。

MAP_PRIVATE
本標誌說明,對映射區的存儲操做致使建立該映射文件的一個副本。
全部後來對該映射區的存訪都是存訪該副本,而不是原始文件。

注意:不能將數據添加(不是修改)到文件中。必須先加長文件。

理解與映射區相關的信號有SIGSEGV和SIGBUS

更改現有映射的權限:

int mprotect(void *addr, size_t len, int prot); // addr頁長的整數倍

當頁已經修改完畢,能夠調用msync函數沖洗到被映射的文件中。

int msync(void *addr, size_t len, int flags);

手動解除存儲區的映射

int munmap(void *addr, size_t len);

該函數刪除了指定地址的映射,若是繼續對其進行讀寫會致使無效內存引用。而且這個函數不會沖洗緩衝區內容到文件。

相關文章
相關標籤/搜索