APUE學習筆記——第十四章 高級IO

1、非阻塞IO
低速系統調用是可能會使進程永遠阻塞的一類系統調用,包括:
(1)如果某些文件類型(管道、終端設備)的數據並不存在,則讀操作可能會使調用者永遠阻塞
(2)如果數據不能立即被上述同樣類型的文件接受,則寫操作也會使調用者永遠阻塞
(3)在某種條件發生之前,打開某些類型的文件會被阻塞
(4)對已經加上強制性記錄鎖的文件進行讀、寫
(5)某些ioctl操作
(6)某些進程間通信函數
非阻塞I/O使我們調用open、read和write等I/O操作,使這些操作不會永遠阻塞,如果這種操作不能完成,則調用立即出錯返回,
表示該操作如繼續執行將阻塞。
對一個給定的描述符有兩種方法設置其爲非阻塞:
(1)如果是調用open以獲得該描述符,則可指定O_NONBLOCK標誌;

(2)對於已經打開的一個描述符,則可調用fcntl打開O_NONBLOCK文件狀態標誌

下面給出一個非阻塞IO實例,它從標準輸入讀取500000字節,並試圖將它們輸出到標準輸出

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <fcntl.h>
 
 char buf[500000];
 void set_fl(int fd,int flag){
     int val;
     val = fcntl(fd,F_GETFL,0);  
     val |= flag;
     fcntl(fd,F_SETFL,val); 
 }
 void clr_fl(int fd,int flag){
     int val;
     val = fcntl(fd,F_GETFL,0);
     val &= ~flag;
     fcntl(fd,F_SETFL,val);
 }
 int main(){
     int ntowrite,nwrite;
     char *ptr;
     ntowrite = read(STDIN_FILENO,buf,sizeof(buf));
     fprintf(stderr,"read %d bytes\n",ntowrite);
     set_fl(STDOUT_FILENO,O_NONBLOCK); //設置非阻塞狀態
     ptr = buf;
     while(ntowrite > 0){
         errno = 0;
         nwrite = write(STDOUT_FILENO,ptr,ntowrite);
         fprintf(stderr,"nwrite = %d,errno =%d\n",nwrite,errno);
         if(nwrite > 0){
             ptr += nwrite;
             ntowrite -= nwrite;
         }
     }
     clr_fl(STDOUT_FILENO,O_NONBLOCK); //清除非阻塞狀態
     exit(0);
 }
此處給出書上的運行結果

當輸出到文件只寫一次,而輸出到標準輸出則write好多次。

2、記錄鎖
記錄鎖的功能是;當一個進程正在讀或者修改文件的某個部分時,它可以阻止其他進程修改同一文件區。記錄鎖更合理的命名應該是字節範圍鎖,因爲它鎖定的只是文件中的一個區域(也可能是整個文件)。
用fcntl函數來實現記錄鎖
int fcntl(int filedes,int cmd,/**struct flock *flockptr/)
對於記錄鎖,cmd是F_GETLK,F_SETLK,F_SETLKW。flock結構如下:
struct flock{
    short l_type; /* F_RDLCK, F_WRLCK, F_UNLCK*/
off_t l_start; /* offset in bytes, relative to l_whence */
short l_whence; /* SEEK_SET,SEEK_CUR,SEEK_END */
off_t l_len; /* length, in bytes; 0 means lock to EOF*/
pid_t l_pid; /* returned with F_GETLK*/
}
對flock結構說明:
鎖的類型(l_type)F_RDLCK(共享讀鎖), F_WRLCK(獨佔性寫鎖), F_UNLCK(解鎖)
要加鎖或解鎖區域的起始字節偏移量,分別由l_start和l_whence決定
區域的字節長度由l_len表示

上面的兼容性規則適用於不同進程提出的鎖請求,並不適用於單個進程提出的多個鎖請求。如果一個進程對一個文件區域已經有一把鎖,後來
該進程又企圖在同一文件區間再加一把鎖,那麼新鎖將替換老鎖。
加讀鎖時,該文件描述符必須是讀打開;加寫鎖時,該描述符必須是寫打開
關於記錄鎖的自動繼承和釋放有三條規則:
(1)鎖與進程、文件兩方面有關:第一,當一個進程終止時,它鎖建立的鎖全部釋放;第二,任何時候關閉一個描述符,則該進程通過這一描述符可以訪問的文件上的任何一把鎖都被釋放。
(2)由fork產生的子進程不繼承父進程所設置的鎖。
(3)在執行exec後,新程序可以繼承原執行程序的鎖。
(此部分內容較多,詳細見書本)

3、IO多路轉接
當從一個描述符讀,然後又寫到另一個描述符時,可以在下列形式的循環中使用阻塞IO:
 while((ntowrite = read(fd_in,buf,sizeof(buf))) > 0){
        nwrite = write(fd_out,buf,ntowrite);
        printf("ntowrite = %d, nwrite = %d\n",ntowrite,nwrite);
 }這種形式的阻塞IO到處可見。
 但是如果必須從兩個描述符讀,仍舊使用阻塞IO,那麼就可能長時間阻塞在一個描述符上,而另一個描述符雖有很多數據卻得不到及時的處理。
 比較好的處理方法是使用IO多路轉接。先構造一張有關描述符的列表,然後調用一個函數,直到這些描述符中的一個已準備好進行IO時,該函數才返回。在返回時,它告訴進程哪些描述符已經準備好可以進行IO。poll、pselect和select這三個函數使我們能夠執行IO 多路轉接。
 在POSIX平臺上,select函數使我們可以執行IO多路轉接,傳向select的參數告訴內核:
 關心的描述符、對於每個描述符所關心的狀態、願意等待多長時間。從select返回時,內核告訴我們:
 已準備好的描述符的數量;對於讀、寫或異常這三個狀態中的每一個,哪些描述符已準備好

使用這些返回信息,就可以調用相應的IO函數(一般是read或write),並且確知該函數不會阻塞
#include <sys/select.h>
int select(int maxfdpl,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *tvptr)
最後一個參數,它指定願意等待的時間:
struct timeval{
    long tv_sec; //seconds
long tv_usec;//microseconds
}
tvptr==NULL:永遠等待
tvptr->tv_sec==0&&tvptr->tv_usec==0:完全不等待
tvptr->tv_sec!=0||tvptr->tv_usec!=0:等待指定的秒數和微秒數
中間三個參數readfds, writefds, exceptfds是指向描述符集的指針,它們描述了我們關心的可讀、可寫和處異常條件的各個描述符。這種描述符集存在一種叫fd_set的數據類型中(在頭文件select.h中有定義)。具體做法每個描述符對應於數據結構fd_set所佔用內存空間的一個位,如果第i位爲0則表示值爲i的描述符不包含在該集中,反之亦然。爲了方便用戶使用,系統提供瞭如下的四個宏進行操作。
void FD_ZERO(fd_set *fdset); //清空fdset中的所有位
void FD_SET(int fd, fd_set *fdset); //在fdset中打開fd所對應的位
void FD_CLR(int fd, fd_set *fdset); //在fdset中關閉fd所對應的位
int FD_ISSET(int fd, fd_set *fdset); //測試fd是否在fdset中
通常做法是,先定義一個描述符集
fd_set rset;
int fd;
必須使用FD_ZERO清除其所有位
FD_ZERO(&rset);
然後設置我們所關心的位
FD_SET(fd, &rset);
FD_SET(STDOUT_FILENO,&rset);
從select返回時,用FD_ISSET測試該集中的一個給定位是否仍舊設置
if( FD_ISSET(fd, &rset)){
...
}

int pselect(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
timespec結構以秒和納秒錶示超時值。
對於pselect可使用一可選擇的信號屏蔽字。若sigmask爲空,則pselect的運行狀況和select相同。否則,sigmask指向一信號屏蔽字在調用pselect時,以原子操作的方式安裝該信號屏蔽字。在返回時恢復以前的信號屏蔽字。

poll函數類似select,它起源於System V
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

IO多路轉接將在進程間通信詳細介紹。

4、readv和writev函數

readv(散佈讀)和writev(聚集寫)函數用於在一次函數調用中讀、寫多個非連續緩衝區。
#include <sys/uio.h>
ssize_t readv(int filedes,const struct iovec *iov, int iovcnt)
ssize_t writev(int filedes,const struct iovec *iov,int iovcnt)
struct ioven{
    void* iov_base;/* starting address of buffer*/
size_t iov_len;/* size of buffer */
};
iov數組中的元素數由iovcnt說明,其最大值受限於IOV_MAX


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>

int main(){
    char buf1[32],buf2[64];
    memset(buf1,0,sizeof(buf1));
    memset(buf2,0,sizeof(buf2));
    struct iovec iov[2];
    iov[0].iov_base = buf1;
    iov[0].iov_len = 31;
    iov[1].iov_base = buf2;
    iov[1].iov_len = 63;
    int fd = open("cp.file",O_RDONLY);
    size_t rd = readv(fd,iov,2);//這裏完全是按照字節數來讀的
    printf("read bytes = %d\n",rd);
    int i;
    for(i = 0 ; i < 2;  i ++){
        printf("%s\n",(char *)iov[i].iov_base);
        puts("=================================");
    }
    iov[0].iov_base = buf1;
    iov[0].iov_len = strlen(buf1);
    iov[1].iov_base = buf2;
    iov[1].iov_len = strlen(buf2);
    
    size_t wr = writev(1,iov,2);
    printf("write bytes = %d\n",wr);
    return 0;
}

5、readn和writen函數

這兩個函數是讀、寫指定的N字節數據

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

ssize_t readn(int fd,void* ptr,size_t n){
    size_t nleft = n;
    ssize_t nread;
    while(nleft > 0){
        if((nread = read(fd,ptr,nleft)) < 0) {
            if(nleft == n) return -1;
            else break;
        }else if(nread == 0){break;}
        nleft -= nread;
        ptr += nread;//point address add
    }
    return n-nleft;
}
ssize_t writen(int fd,const void* ptr,size_t n){
    size_t nleft = n;
    ssize_t nwritten;
    while(nleft > 0){
        if((nwritten = write(fd,ptr,nleft)) < 0){
            if(nleft == n) return -1;
            else break;
        }else if(nwritten == 0) break;
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n-nleft;
}

int main(){
    ssize_t rd,wr;
    char buf[20];
    rd = readn(0,buf,10);
    printf("%d, %s\n",rd,buf);
    memset(buf,0,sizeof(buf));
    strcpy(buf,"I love you, linux");
    wr = writen(1,buf,10);
    printf("%d\n",wr);
    return 0;
 
}


發佈了101 篇原創文章 · 獲贊 26 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章