(51)LINUX應用編程和網絡編程之六Linux高級IO

3.6.1.非阻塞IO
3.6.1.一、阻塞與非阻塞
阻塞:阻塞具備不少優點(是linux系統的默認設置),單路IO的時候使用阻塞式IO沒有下降CPU的性能
補充:阻塞/非阻塞, 它們是程序在等待消息(無所謂同步或者異步)時的狀態.
阻塞調用是指調用結果返回以前,當前線程會被掛起。函數只有在獲得結果以後纔會返回。
有人也許會把阻塞調用和同步調用等同起來,實際上他是不一樣的。
對於同步調用來講,不少時候當前線程仍是激活的,只是從邏輯上當前函數沒有返回而已。
非阻塞和阻塞的概念相對應,指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。
3.6.1.二、爲何有阻塞式
(1)常見的阻塞:wait(顯式回收子進程)、pause、sleep等默認阻塞函數;read或write某些文件時也是默認阻塞式的
(2)阻塞式的好處:當前線程被掛起等待條件知足才返回結果
3.6.1.三、非阻塞
(1)爲何要實現非阻塞
(2)如何實現【非阻塞IO訪問】:
1)打開文件時加入O_NONBLOCK
2)fcntl函數,對文件描述符(文件已經打開)進行操做
 
 
單路IO就用阻塞式比較好;
多路IO最好是用非阻塞式的。避免資源被一個佔有不放,讓其餘IO有資源可搶。
從CPU的利用角度來看,單路IO就是一對一;多路IO就是一對多,即一個CPU對應多個資源搶佔通道。前者單路效率更高,後者須要兼顧分配。單路IO模型只須要監聽一個IO流,多路IO模型能夠同時監聽(內部輪循)多個IO流,CPU要不停去查看。
 
 
3.6.2.阻塞式IO的困境
3.6.2.一、程序中讀取鍵盤
3.6.2.二、程序中讀取鼠標        cat         /dev/input/mouse1
3.6.2.三、程序中同時讀取鍵盤和鼠標
3.6.2.四、問題分析
並非全部的狀況下都適阻塞式io的。
代碼示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>   
/*
程序中讀取鍵盤和鼠標
*/
#define NAME     "/dev/input/mouse1"   
char buff[100]={0};
char buf[100]={0};
int main(int argc,char **argv)
{
    int ssize_t=-1;
    int fd=-1;
 
    fd=open(NAME,O_RDWR);
    if(-1==fd)
    {
        perror("open");
        _exit(-1);
    }
    printf("打開成功!fd=%d\n",fd);
    while(1){
         ssize_t=read(fd,buff,sizeof(buff));
    if(-1==ssize_t)
    {
        perror("read");
        _exit(-1);
    }
printf("讀到的鼠標字符數爲%d\n",ssize_t);
printf("讀到的鼠標字符是[%s]\n",buff);       
read(0,&buf,sizeof(buf));
printf("讀到的鼠標字符是[%s]\n",buf);         
sleep(1);
    }
     return 0;
3.6.3.併發式IO的3種解決方案【併發式IO】
3.6.3.一、非阻塞式IO:性能不夠好,有點相似於輪詢的方式,CPU不停的去查看
代碼示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
#define NAME     "/dev/input/mouse1"   
char buff[100]={0}; //鍵盤
char buf[100]={0};//鼠標
int main(void)
{
    int ssize_t=-1;
    int fd=-1;
    int flag=-1;
    int ret=-1;
    fd=open(NAME,O_RDWR | O_NONBLOCK );
    if(-1==fd)
    {
        perror("open");
        _exit(-1);
    }
 
    //把標準輸入文件描述符0經過fcntl函數變成非阻塞式子
 
    // 把0號文件描述符(stdin)變成非阻塞式的
    flag = fcntl(0, F_GETFL);        // 先獲取原來的flag
    flag |= O_NONBLOCK;                // 添加非阻塞屬性
    fcntl(0, F_SETFL, flag);        // 更新flag
    // 這3步以後,0就變成了非阻塞式的了
 
        while (1)
    {
        // 讀鼠標
        memset(buf, 0, sizeof(buf));
        //printf("before 鼠標 read.\n");
        ret = read(fd, buf, 50);
        if (ret > 0)
        {
            printf("鼠標讀出的內容是:[%s].\n", buf);
        }
 
        // 讀鍵盤
        memset(buff, 0, sizeof(buff));
        //printf("before 鍵盤 read.\n");
        ret = read(0, buff, 5);
        if (ret > 0)
        {
            printf("鍵盤讀出的內容是:[%s].\n", buff);
        }
    }
 
return 0;
}
3.6.3.二、多路複用IO :性能相對比較好,解決併發性IO的解決
 
 
 
3.6.4.IO多路複用原理
3.6.4.一、何爲IO多路複用   【說白了,多路複用其實就是一個管多個】
(1)IO           multiplexing
(2)用在什麼地方?多路非阻塞式IO(多路及時響應)。
(3)select和poll兩個函數:(poll出現的比較晚一點。其性能也要比select函數的性能更好一些)
(4)外部阻塞式(select和poll兩個函數自己在調用的時候是阻塞式的,對外表現爲阻塞式),內部非阻塞式(
兩個函數內部實現的時候,也就是當把要監聽的東西(好比A和B兩個阻塞式IO)放在這個函數的監聽範圍)【自動】輪詢【這兩個多路阻塞式IO】               (輪詢的意思就是CPU不停的去查看)
 
補充:
但是使用Select就能夠完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時沒必要非要
等待事件的發生,一旦執行確定返回,以返回值的不一樣來反映函數的執行狀況,若是事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,因此效率較高)方式工做的程序,它可以監視咱們須要監視的文件描述符的變化狀況——讀寫或是異常。
3.6.4.二、select函數介紹:
#include <sys/select.h>
#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);     //nfds表示文件描述符的個數 nfds is the highest-numbered file descriptor in any of the //three sets, plus 1,是指集合中全部文件描述符的範圍,【即全部文件描述符的最大值加1】這一點要注意,全部文件描述符的最大值加1,好比一個文件描述符是0,一個文件描述符是1,則爲1+1
相關函數:
(1)void FD_ZERO(fd_set *set);       //把文件描述符集合清零
(2)void FD_SET(int fd, fd_set *set);   //把某個文件描述符添加到這個集合中去
(3)int  FD_ISSET(int fd, fd_set *set);
//檢查集合中指定的文件描述符是否能夠讀寫 FD_ISSET() tests to see if a file descriptor is part of the set; this is useful after select() returns.
(4)void FD_CLR(int fd, fd_set *set);   //把一個給定的文件描述符從集合中刪除
相關結構體:
 (1)struct fd_set能夠理解爲一個集合,這個集合中存放的是文件描述符(file descriptor)。
(2)     
        struct timeval {
         long    tv_sec;         /* seconds */
         long    tv_usec;        /* microseconds */
        };                 
struct timeval* timeout是select的超時時間,這個參數相當重要,它可使select處於三種狀態,第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,必定等到監視文件描述符集合中某個文件描述符發生變化爲止;第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,無論文件描述符是否有變化,都馬上返回繼續執行,文件無變化返回0,有變化返回一個正值;第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間以內有事件到來就返回了,不然在超時後無論怎樣必定返回,返回值同上述。
linux內部代碼示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval;
 
    /* Watch stdin (fd 0) to see when it has input. */
    FD_ZERO(&rfds);   //先把文件描述符集合清零
    FD_SET(0, &rfds);   //把0文件描述符添加到這個集合中去
 
    /* Wait up to five seconds. */
    tv.tv_sec = 5;
    tv.tv_usec = 0;
 
    retval = select(1, &rfds, NULL, NULL, &tv);
     /* Don't rely on the value of tv now! */
    if (retval == -1)
        perror("select()");
           else if (retval)
               printf("Data is available now.\n");
               /* FD_ISSET(0, &rfds) will be true. */
           else
               printf("No data within five seconds.\n");
           exit(EXIT_SUCCESS);
}
 
3.6.4.三、poll函數介紹
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#define _GNU_SOURCE         /* See feature_test_macros(7) */
結構體:
           struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
 
代碼示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
/*
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
*/
#define NAME  "/dev/input/mouse1"
 
int main(void)
{
     char buf[100]={0};                 //注意這裏的定義的buf不可以使用全局的
    //int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd a[2]={0};
    int fd= open(NAME,O_RDWR);   //定義鼠標文件描述符
    if(fd<0)
    {
        perror("open mouse");
        _exit(-1);
    }
    //實例化結構體
    a[0].fd=fd;               //鼠標
    a[0].events=POLLIN;
    a[1].fd=0;                 //鍵盤
    a[1].events=POLLIN;
    int ret=poll(a,fd+1,10000);
    if(ret<0)
   {
       perror("poll");
       _exit(-1);
    }   
    if(ret==0)
    {
        printf("超時了");
    }
    else     //判斷是誰發生了IO
    {
        if(a[0].events==a[0].revents)        //鼠標
        {
            memset(buf,0,sizeof(buf));
            read(fd,buf,10);
            printf("讀出來的鼠標內容是:[%s]\n",buf);
        }
 
            if(a[1].events==a[1].revents)        //鍵盤
        {
            memset(buf,0,sizeof(buf));
            read(0,buf,50);
            printf("讀出來的鍵盤內容是:[%s]\n",buf);
       }
    }       
        return 0;
}               
 
3.6.5.IO多路複用實踐
3.6.5.一、用select函數實現同時讀取鍵盤鼠標
select()函數實現IO多路複用的步驟
(1)清空描述符集合
(2)創建須要監視的描述符與描述符集合的關係
(3)調用select函數
(4)檢查監視的描述符判斷是否已經準備好
(5)對已經準備好的描述符進程IO操做
代碼示例:
/*
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
 
#define NAME     "/dev/input/mouse1"   
char buf[100]={0};
int main(void)
{
    int fd=-1;
    fd_set readfds=-1;
    struct timeval time;
    int ret=-1;
    fd=open(NAME,O_RDWR);
    printf("fd=%d\n",fd);
    if(-1==fd)
    {
        perror("open");
        _exit(-1);
    }
        FD_ZERO(&readfds);    //清除文件描述符集合
        FD_SET(fd,&readfds);
        FD_SET(0,&readfds);
        time.tv_sec=5;            //設置時間爲5秒
        time.tv_usec=0;
        ret=select(3,&readfds,NULL,NULL,&time);           //調用select函數 ,注意第一個參數
       if (ret < 0)                      //表示出錯
    {
        perror("select: ");
        return -1;
    }
    else if (ret == 0)              //表示超過了規定的時間限制
    {
        printf("超時了\n");
    }
    else
    {
        // 等到了一路IO,而後去監測究竟是哪一個IO到了,處理之
        if (FD_ISSET(0, &readfds))
        {
            // 這裏處理鍵盤
            memset(buf, 0, sizeof(buf));
            read(0, buf, 5);
            printf("鍵盤讀出的內容是:[%s].\n", buf);
        }
 
        if (FD_ISSET(fd, &readfds))
        {
            // 這裏處理鼠標
            memset(buf, 0, sizeof(buf));
            read(fd, buf, 50);
            printf("鼠標讀出的內容是:[%s].\n", buf);
        }
    }
return 0;
}
3.6.5.二、用poll函數實現同時讀取鍵盤鼠標
poll函數介紹:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第一個參數 pollfd 結構體定義以下:
引用
/* Data structure describing a polling request.  */
struct pollfd
  {
    int fd;                         /* poll 的文件描述符.  */
    short int events;           /* fd 上感興趣的事件(等待的事件或者說是監視的事件).  */
    short int revents;          /* fd 上實際發生的事件.  */
  };
fd 成員表示感興趣的,且打開了的文件描述符;
events  成員是位掩碼,用於指定針對這個文件描述符感興趣的事件;
revents  成員是位掩碼,用於指定當 poll 返回時,在該文件描述符上已經發生了哪些事情。
 
events 和 revents 結合下列常數值(宏)指定即將喚醒的事件或調查已結束的 poll() 函數被喚醒的緣由,這些宏常數以下:
POLLIN
events 中使用該宏常數,可以在折本文件的可讀狀況下,結束 poll() 函數。相反,revents 上使用該宏常數,在檢查 poll() 函數結束後,可依此判斷設備文件是否處於可讀狀態(即便消息長度是 0)。
 
POLLPRI
在 events 域中使用該宏常數,可以在設備文件的高優先級數據讀取狀態下,結束 poll() 函數。相反,revents 上使用該宏常數,在檢查 poll() 函數結束後,可依此判斷設備文件是否處於可讀高優先級數據的狀態(即便消息長度是 0)。該宏常數用於處理網絡信息包(packet) 的數據傳遞。
 
POLLOUT
在 events 域中使用該宏常數,可以在設備文件的寫入狀態下,結束 poll() 函數。相反,revents 域上使用該宏常數,在檢查 poll() 結束後,可依此判斷設備文件是否處於可寫狀態。
 
POLLERR
在 events 域中使用該宏常數,可以在設備文件上發生錯誤時,結束 poll() 函數。相反,revents 域上使用該宏函數,在檢查 poll() 函數結束後,可依此判斷設備文件是否出錯。
 
POLLHUP
在 events 域中使用該宏常數,可以在設備文件中發生 hungup 時,結束 poll() 函數 。相反,在檢查 poll() 結束後,可依此判斷設備文件是否發生 hungup 。
 
POLLNVAL
在 events 域中使用該宏函數,可以在文件描述符的值無效時,結束 poll() 。相反,在 revents 域上使用該宏函數時,在檢查 poll() 函數後,文件描述符是否有效。可用於處理網絡信息時,檢查 socket handler 是否已經無效。
 
 
最後一個參數 timeout 指定 poll() 將在超時前等待一個事件多長事件。這裏有 3 種狀況:
 
1) timeout 爲 -1
這會形成 poll 永遠等待。poll() 只有在一個描述符就緒時返回,或者在調用進程捕捉到信號時返回(在這裏,poll 返回 -1),而且設置 errno 值爲 EINTR 。-1 能夠用宏定義常量 INFTIM 來代替(在 pth.h 中有定義) 。
 
2) timeout 等於0
在這種狀況下,測試全部的描述符,而且 poll() 馬上返回。這容許在 poll 中沒有阻塞的狀況下找出多個文件描述符的狀態。
 
3) time > 0
這將以毫秒爲單位指定 timeout 的超時週期。poll() 只有在超時到期時返回,除非一個描述符變爲就緒,在這種狀況下,它馬上返回。若是超時週期到齊,poll() 返回 0。這裏也可能會由於某個信號而中斷該等待。
 
和 select 同樣,文件描述符是否阻塞對 poll 是否阻塞沒有任何影響。
 
代碼示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
 
#define NAME  "/dev/input/mouse1"
 
int main(void)
{
     char buf[100]={0};                 //注意這裏的定義的buf不可以使用全局的
    //int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd a[2]={0};
    int fd= open(NAME,O_RDWR);   //定義鼠標文件描述符
    if(fd<0)
    {
        perror("open mouse");
        _exit(-1);
    }
    //實例化結構體
    a[0].fd=fd;               //鼠標
    a[0].events=POLLIN;
    a[1].fd=0;                 //鍵盤
    a[1].events=POLLIN;
    int ret=poll(a,fd+1,10000);
    if(ret<0)
   {
       perror("poll");
       _exit(-1);
    }   
    if(ret==0)
    {
        printf("超時了");
    }
    else     //判斷是誰發生了IO
    {
        if(a[0].events==a[0].revents)        //鼠標
        {
            memset(buf,0,sizeof(buf));
            read(fd,buf,10);
            printf("讀出來的鼠標內容是:[%s]\n",buf);
        }
 
            if(a[1].events==a[1].revents)        //鍵盤
        {
            memset(buf,0,sizeof(buf));
            read(0,buf,50);
            printf("讀出來的鍵盤內容是:[%s]\n",buf);
       }
    }       
        return 0;
}
 
3.6.6.異步IO
3.6.6.一、何爲異步IO
(1)幾乎能夠認爲:異步IO就是操做系統用軟件實現的一套中斷響應系統(有點相似與硬件中斷)。
有兩種類型的文件IO同步:同步文件IO和異步文件IO。異步文件IO也就是重疊IO。【在同步文件IO中,線程啓動一個IO操做而後就當即進入等待狀態,直到IO操做完成後才醒來繼續執行。而異步文件IO方式中,線程發送一個IO請求到內核,而後繼續處理其餘的事情,內核完成IO請求後,將會通知線程IO操做完成了。】
若是IO請求須要大量時間執行的話,異步文件IO方式能夠顯著提升效率,由於在線程等待的這段時間內,CPU將會調度其餘線程進行執行,若是沒有其餘線程須要執行的話,這段時間將會浪費掉(可能會調度操做系統的零頁線程)。若是IO請求操做很快,用異步IO方式反而還低效,還不如用同步IO方式。
同步IO在同一時刻只容許一個IO操做,也就是說對於同一個文件句柄的IO操做是序列化的,即便使用兩個線程也不能同時對同一個文件句柄同時發出讀寫操做。重疊IO容許一個或多個線程同時發出IO請求。
異步IO在請求完成時,經過將文件句柄設爲有信號狀態來通知應用程序,或者應用程序經過GetOverlappedResult察看IO請求是否完成,也能夠經過一個事件對象來通知應用程序。
簡單的說「同步在編程裏,通常是指某個IO操做執行完後,才能夠執行後面的操做。異步則是,將某個操做給系統,主線程去忙別的事情,等內核完成操做後通知主線程異步操做已經完成。」
(2)異步IO的工做方法是:咱們當前進程向內核註冊一個異步IO事件(使用signal註冊一個信號SIGIO的處理函數),而後當前進程能夠正常處理本身的事情,內核就去幫你完成或者說是檢測你但願的事件是否發生,當異步事件發生後當前進程會收到內核傳來的一個SIGIO信號(相似於中斷信號)從而執行綁定的處理函數去處理這個異步事件。
3.6.6.二、涉及的函數:
(1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)    設置異步通知
(2)signal或者sigaction函數(SIGIO)
3.6.3.代碼實踐
 
 
3.6.7.存儲映射IO
存儲映射IO使一個磁盤文件(物理地址)與存儲空間(內存地址)中的一個緩衝區相映射。因而當從緩衝區取數據,就至關於讀文件中的相應字節。與此相似,將數據存入緩衝區,則相應字節就自動地寫入文件。這樣就能夠在不使用read和write的狀況下執行IO。爲了使用這種功能,應首先告訴內核將一個給定的文件映射到一個存儲區域中,這是由mmap函數實現的。
3.6.7.一、mmap函數:把一個磁盤文件和一個內存映射起來    內存映射函數
3.6.7.二、LCD顯示和IPC之共享內存
3.6.7.三、存儲映射IO的特色
(1)共享而不是複製,減小內存操做
(2)處理大文件時效率高,小文件不划算(視頻用到的也比較多)
 
 
函數原型:
void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
addr參數用於指定映射存儲區的起始地址,一般將其設置爲0,這表示由系統選擇該映射區的起始地址,此函數的返回地址是該映射區的起始地址。
fd指定要被映射文件的描述符,在映射該文件到一個地址空間以前,先要打開該文件。
length是映射的字節數。
offset是要映射字節在文件中的起始偏移量。
prot參數說明對映射存儲區的保護要求,但不能超過文件open模式訪問權限,prot可選值以下:
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
flags參數影響映射存儲區的多種屬性,其中MAP_SHARED和MAP_PRIVATE二者必須選擇其一,還有許多其它的MAP_XXX是可選的。
 
【還有一種就是多進程下,父進程fork建立一個子進程來處理不一樣的事情】。
相關文章
相關標籤/搜索