天天看點

linux非阻塞IO與阻塞IO的應用

1.為什麼有阻塞IO

(1)常見的阻塞:wait、pause、sleep等函數,read和write某些特殊檔案時

(2)阻塞式的好處,在阻塞等待的程序不消耗CPU運作,提高性能。

2.如何實作非阻塞IO通路

(1)O_NONBLOCK和fcntl

3.阻塞式IO的困境

(1)程式中讀取鍵盤

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

int main(void)
{
        char buf[100];

        memset(buf,0,sizeof(buf));
        printf("before read\n");
        //讀取鍵盤鍵盤就是标準輸入,stdin
        read(0,buf,2);//read預設是阻塞的
        printf("讀出的内容是:[%s]\n",buf);

        return 0;
}

           

(2)程式中讀取滑鼠

這裡的滑鼠輸入在/dev/input/mouse1有的是在mouse0下面通過

cat /dev/input/mouse0或者mouse1然後移動滑鼠出現亂碼則就是滑鼠輸入裝置。

#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        int fd = -1;
        char buf[200];

        fd = open("/dev/input/mouse0",O_RDONLY);
        if(fd < 0)
        {
                perror("open");
                return -1;
        }
        memset(buf,0,sizeof(buf));
        printf("before read\n");
        //讀取滑鼠
        read(fd,buf,5);//read預設是阻塞的
        printf("讀出的内容是:[%s]\n",buf);

		return 0;
}
           

(3)程式中同時讀取鍵盤滑鼠

#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        int fd = -1;
        char buf[200];

        fd = open("/dev/input/mouse0",O_RDONLY);//打開滑鼠
        if(fd < 0)
        {
                perror("open");
                return -1;
        }
        memset(buf,0,sizeof(buf));
        printf("before 滑鼠 read\n");
        //讀取滑鼠
        read(fd,buf,5);//read預設是阻塞的
        printf("滑鼠讀出的内容是:[%s]\n",buf);

        memset(buf,0,sizeof(buf));
        printf("before 鍵盤 read\n");
        //讀取鍵盤鍵盤就是标準輸入,stdin
        read(0,buf,2);//read預設是阻塞的
        printf("鍵盤讀出的内容是:[%s]\n",buf);
		return 0;
}
           

(4)問題分析

像這種情況就隻能先讀滑鼠再讀鍵盤這就是阻塞式的缺陷

4.并發式IO

(1)非阻塞式IO

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{


        int fd = -1;
        int ret = -1;
        int flag = -1;
        char buf[200];

        fd = open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK);//打開滑鼠
        if(fd < 0)
        {
                perror("open");
                return -1;
        }

        //把0号檔案描述符(stdin)變成非阻塞式
        //int fcntl(int fd, int cmd, ... * arg * );
        flag = fcntl(0,F_GETFL);        //先擷取原來的flag
        flag |= O_NONBLOCK;             //添加非阻塞屬性
        fcntl(0,F_SETFL,flag);          //更新flag
        //上面三步将0這個檔案描述符變為非阻塞式的

        while(1)
        {
                memset(buf,0,sizeof(buf));
                //printf("before 滑鼠 read\n");
                //讀取滑鼠
                ret = read(fd,buf,5);//read預設是阻塞的
                if(ret > 0)
                {
                        printf("滑鼠讀出的内容是:[%s]\n",buf);
                }

                memset(buf,0,sizeof(buf));
				//printf("before 鍵盤 read\n");
                //讀取鍵盤鍵盤就是标準輸入,stdin
                ret = read(0,buf,5);//read預設是阻塞的
                if(ret > 0)
                {
                        printf("鍵盤讀出的内容是:[%s]\n",buf);
                }
        }
        
	return 0;
}

           

5.多路複用IO

(1)何為IO多路複用(IO multiplexing)

(2)用在什麼地方:多路非阻塞式IO

(3)select和poll(本身是阻塞式函數)

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
參數1:檔案描述符的個數(檔案IO中最大檔案描述符+1)
參數2:要讀的檔案描述符的集合(相當于結構體,放多個需要讀的檔案描述符)
參數3:要寫的多個檔案描述符
參數4:看檔案描述符是否出錯
參數5:設定逾時時間(如果這段事間一直沒有IO事件發生就傳回0)
傳回值:傳回發生了幾次IO事件
           
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數1:多個檔案描述符的集合
參數2:放入這個集合的檔案描述符個數+1
參數3:逾時時間
傳回值:傳回發生了幾次IO事件
           

select函數實作

#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>

int main(void)
{
        int fd = -1;
        int ret = -1;
        fd_set myset;
        char buf[200];
        struct timeval tm;

        fd = open("/dev/input/mouse0",O_RDONLY);//打開滑鼠
        if(fd < 0)
        {
                perror("open");
                return -1;
        }

        //目前有兩個fd,1個是fd,1個是0
        //處理myset
        FD_ZERO(&myset);//先清零
        FD_SET(fd,&myset);//設定fd
        FD_SET(0,&myset);//設定0這個fd
        tm.tv_sec = 10;//設定秒
        tm.tv_usec = 0;//設定微妙(兩個秒數是相加的)
        //int select(int nfds, fd_set *readfds, fd_set *writefds,
        //fd_set *exceptfds, struct timeval *timeout);
        ret = select(fd+1,&myset,NULL,NULL,&tm);
        if(ret < 0)
        {
                perror("select");
                return -1;
        }
		else if(ret == 0)
        {
                printf("逾時了\n");
        }
        else
        {
                //等到了一路IO,然後去監測是哪個IO到了
                if(FD_ISSET(0,&myset))
                {
                        //這裡處理鍵盤
                        memset(buf,0,sizeof(buf));
                        //讀取鍵盤鍵盤就是标準輸入,stdin
                        read(0,buf,5);//read預設是阻塞的
                        printf("鍵盤讀出的内容是:[%s]\n",buf);

                }
                else if(FD_ISSET(fd,&myset))
                {
                        //這裡處理滑鼠
                        memset(buf,0,sizeof(buf));
                        read(fd,buf,5);//read預設是阻塞的
                        printf("滑鼠讀出的内容是:[%s]\n",buf);

                }
        }

        return 0;
}

           

poll函數實作:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <poll.h>

int main(void)
{
        int fd = -1;
        int ret = -1;
        struct pollfd myfds[2] = {0};
        char buf[200];
        struct timespec tm;

        fd = open("/dev/input/mouse0",O_RDONLY);//打開滑鼠
        if(fd < 0)
        {
                perror("open");
                return -1;
        }

        //初始化我們的pollfd
        myfds[0].fd = 0;        //鍵盤
        myfds[0].events = POLLIN;//等待讀操作
        myfds[1].fd = fd;        //滑鼠
        myfds[1].events = POLLIN;//等待讀操作
        tm.tv_sec = 10000;
        //int poll(struct pollfd *fds, nfds_t nfds, int timeout);
        //ret = poll(myfds,2,tm.tv_sec);//2個檔案描述符,10000毫秒
        ret = poll(myfds,fd,10000);//2個檔案描述符,10000毫秒,這裡寫fd檔案運作>直接退出
        if(ret < 0)
        {
                perror("poll:");
                return -1;
        }
        else if(ret == 0)
        {
                printf("逾時了\n");
		}
        else
        {
                //等到了一路IO,然後去監測是哪個IO到了
                if(myfds[0].events == myfds[0].revents)
                {
                        //這裡處理鍵盤
                        memset(buf,0,sizeof(buf));
                        //讀取鍵盤鍵盤就是标準輸入,stdin
                        read(0,buf,5);//read預設是阻塞的
                        printf("鍵盤讀出的内容是:[%s]\n\r",buf);

                }
                if(myfds[1].events == myfds[1].revents)
                {
                        //這裡處理滑鼠
                        memset(buf,0,sizeof(buf));
                        read(fd,buf,50);//read預設是阻塞的
                        printf("滑鼠讀出的内容是:[%s]\n",buf);

                }
        }

        return 0;
}

           

(4)外部阻塞式,内部非阻塞式自動輪詢多路阻塞式IO

6.異步通知(異步IO)

(1)異步IO就是作業系統用軟體實作的一套中斷系統。

(2)異步IO的工作方法:我們目前程序注冊一個異步IO事件(使用signal注冊一個信号SIGIO的處理函數),然後目前程序可以正常處理自己的事情,當異步事件發生後目前程序會收到一個SIGIO信号進而執行綁定的處理函數去處理這個異步事件。

(3)涉及函數(fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN))、signal或sigaction(SIGIO)

(4)代碼實作

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int mousefd = -1;


//異步通知函數,綁定SIGIO信号,在函數内處理異步通知事件
void func(int sig)
{
        char buf[200] = {0};

        if(sig != SIGIO)
                return;

        //讀取滑鼠
        read(mousefd,buf,5);//read預設是阻塞的
        printf("滑鼠讀出的内容是:[%s]\n",buf);

}

int main(void)
{
        char buf[200];
        int flag = -1;

        mousefd = open("/dev/input/mouse0",O_RDONLY);//打開滑鼠
        if(mousefd < 0)
        {
                perror("open");
                return -1;
        }

        //注冊異步通知(把滑鼠的檔案描述符設定為可以接受異步IO)
        flag = fcntl(mousefd,F_GETFL);
        flag |= O_ASYNC;
        fcntl(mousefd,F_SETFL,flag);
        //把異步IO事件的接收程序設定為目前程序
		fcntl(mousefd,F_SETOWN,getpid());
        //注冊目前程序的SIGIO信号捕捉函數
        signal(SIGIO,func);

        while(1)
        {
                memset(buf,0,sizeof(buf));
                //讀取鍵盤鍵盤就是标準輸入,stdin
                read(0,buf,5);//read預設是阻塞的
                printf("鍵盤讀出的内容是:[%s]\n",buf);
        }


        return 0;
}

           

7.存儲映射IO

(1)mmap函數

(2)LCD顯示和IPC之共享記憶體

(3)存儲映射的特點:共享而不是複制,減少記憶體操作。處理大檔案時效率高,小檔案不劃算

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
           

繼續閱讀