您好,登錄后才能下訂單哦!
1、1、非阻塞IO
1.阻塞與非阻塞
(1)阻塞:就是當前的函數要執行的話,需要某些條件,但是沒有達到,就被阻塞住,內核掛起,當前進程暫停。CPU被拿去運行別的進程了。比如父進程執行wait這個阻塞函數,等待子進程結束后,去回收子進程剩余的8KB內存資源,如果這個時候子進程沒有結束,父進程的wait就會被阻塞,因為條件沒有達到,這個時候父進程就暫停了,內核就掛起了,CPU的時間就去執行別的進程了,不在父進程這里耗了,當子進程結束的時候,OS會給子進程的父進程發送一個SIGCHILD信號,這時父進程就被喚醒了,wait函數執行就將子進程回收掉了
(2)常見的阻塞:
wait、sleep、pasue等函數;
read或write寫文件的時候
(3)阻塞的好處:有利于操作系統的性能的發揮。因為阻塞住了進程會暫停,CPU可以去做別的事情,并且要完成的任務在一定情況下也會被完成的。
(4)但是一個進程中,進行多路IO操作的時候,有時阻塞式的就不好了,比如你當前進程排在前面的IO,由于因為你被阻塞住了,導致你的當前進程陷入了休眠狀態,雖然你將CPU的時間讓給了別的進程了,但是對于你自己的進程來說,你自己進程的剩下那些IO操作就要等你前面的IO操作完了之后才能進行,這樣就會降低了你當前的一個進程中的工作效率了,因為你排在前面的IO操作沒有進行完,你排在后面的IO操作是不肯能得到運行的,都是在一個進程中的。這個時候阻塞就是不好的了,所以有時也需要非阻塞式的操作。
(5)非阻塞式的操作,當你一個進程中進行多路IO的操作的時候,你排在前面的IO操作,由于你沒有用阻塞式的操作,而是非阻塞式的操作,所以即使你的條件沒有達到你也不會讓整個進程陷入沉睡暫停狀態,而是直接返回,直接執行這個進程的下面的內容,這樣對于當前進程的別IO來說,別的進程的執行效率就加大了,這種情況往往是在IO操作設備文件的時候,比如你在讀鼠標這個設備文件,就因為你這讀這個設備文件的時候被阻塞住了,而影響到了其他的設備文件的IO操作,這樣可想而知,真的不好
(6)如何實現非阻塞呢,就是在打開文件的時候加上O_NONBLOCK這個標志屬性和fcntl,fcntl可以讀出一個文件的flag,然后加上O_NONBLOCK這個標志,在將這個flag放回去
2、用read讀取鍵盤,來看阻塞的感覺
(1)因為鍵盤是標準輸入設備,對應的文件描述符是0,在每個進程中都是默認被打開的,所以我們直接read(0, buf, 2);直接讀這個鍵盤就行,讀取兩個字節放到buf中,我們執行這個時候,當我們鍵盤沒有輸入東西完畢按回車的時候(因為Linux的控制臺是行緩沖的,你沒有按下回車的時候,只是把你輸入的東西放到控制臺上顯示,還沒有送到read中,你按下回車后才會被輸入進去。),當我們沒有輸入東西按回車的時候,發現是被阻塞住的,被卡住了,不動了,這就是阻塞的感覺,這個時候當前的進程被暫停了,因為沒有等待條件,read讀取不到東西,當我們輸入東西按下回車的時候,當前的進程就被喚醒了,read讀取到東西了
3、read讀取鼠標
(1)首先鼠標這個設備文件是需要我們打開的,才能得到文件描述符fd,鼠標的設備文件在/dev/input/mouse0或者可能是/dev/input/mouse1
4、先讀鼠標在鍵盤的情況
(1)我們程序在設計的時候,是先讀取鼠標這個設備文件,在讀取鍵盤這個標準輸入文件,我們會發現,由于我們是阻塞式的,所以我們在鼠標沒有讀取成功的時候,鍵盤輸入了東西敲回車也不好使,只有當鼠標讀取完了之后,才會去讀取鍵盤,這就是阻塞式的壞處,解決方法就是非阻塞
5、并發式IO的解決方案(就是一個進程中對多個IO進行操作時引發的問題,就是上面的4的問題)
(1)解決方案有三個
(2)非阻塞式IO
@1:首先將鍵盤這個文件設置成非阻塞式的,因為鍵盤這個標準輸入,是在每一個進程都打開的,所以我們無法直接用open這個函數去打開這個文件,將其中的flag加上O_NONBLOCK這個標志屬性
所以我們要用fcntl這個函數來去利用文件描述符fd,去將這個文件的屬性進行改成非阻塞的。
int fcntl(int fd, int cmd, ... /* arg */ );
@2:第一個參數是文件描述符
@3:第二個參數是要的功能,我們的cmd用的宏是F_GETFD (void) F_SETFD (int),這兩個。
@4:利用F_GETFD這個cmd宏,將fd這個文件的flag讀取出來,之后用F_SETFD這個cmd宏將O_NONBLOCK這個非阻塞式的屬性添加上。
@5:先flag = fcntl(0, F_GETFD);獲取原來的這個文件的flag,然后 flag |= O_NONBLOCK;位或一個O_NONBLOCK。然后在fcntl(0, F_SETFD, flag);將屬性在寫到這個fd中
6、多路復用IO
(1)IO多路復用原理(IO multiplexing),作用和非阻塞IO有點像,是解決并發式IO的問題的
(2)什么時候用多路復用IO呢,在多路IO都是非阻塞式的情況下。為了應對多路非阻塞IO,CPU要一直不停的去輪詢這些非阻塞IO,因為是輪詢,所以必然要消耗時間,有空檔。所以有了多路復用IO。
(3)多路復用IO涉及到的兩個函數:select和poll
(4)IO多路復用的實現原理就是:外部阻塞式,內部非阻塞式自動輪詢多路阻塞式IO。
@1:外部阻塞式的意思是說,select和poll這兩個函數本身是阻塞式的
@2:內部非阻塞式,意思是說,select和poll內部在輪詢幾路IO的時候是非阻塞式的。
@3:select類似于一個監聽的函數,比如,當A這個鼠標IO,和B這個鍵盤IO,我們用select去監聽這兩個IO,我們的鼠標IO,和鍵盤IO,都是阻塞式的,并沒有設置成非阻塞式的,當我們調用select去監聽的時候,當鼠標IO事件和鍵盤IO事件都沒有發生的情況,select本身就是阻塞式的,select會一直在這里等待他監聽的兩個IO事件其中至少發生一個,select去監聽這個兩個IO的時候,是以非阻塞式的方式去輪詢監聽的,這就是外部阻塞式,內部非阻塞式自動輪詢多路阻塞式IO。select函數表現為對外是阻塞式的,對內部監聽的A和B這兩個阻塞式IO表現為非阻塞式的方式去輪詢A和B這兩個阻塞式IO。只要A或者B有一個IO中來數據了,select就會被喚醒,select就會向外面匯報,在沒數據時,select是阻塞式的,select所在的進程變成了暫停狀態
(5)select函數
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval
*timeout);
@1:第一個參數表示文件描述符的個數,這個值我們應該設置select內部要監聽的多路IO中的那個文件描述符最大的那一個的fd+1,因為文件描述符是從0開始的,所以要加1。
@2:第二個參數中的fd_set是多個文件描述符的集合,是一個結構體。select這個函數在運行的時候,是區分文件描述符所要進行的操作的,所以第二個參數是傳遞了一個要讀的文件描述符,是要對多路文件描述符中那些要讀的文件描述符。第三個參數是傳遞多路文件描述符中要寫的那些文件描述符。第四個參數是那些發生異常的文件描述符,都是輸出型參數。第五個參數是設置超時時間的,因為select是阻塞式的,所以當監聽的IO沒有IO事件時,select就一直被阻塞了,卡住了,當前進程就一直暫停了,所以有時不想select一直被阻塞住,所以有了這個第五個參數,用來設置超時的,時間到了,select就會被喚醒返回
@3:返回值,當select成功的時候,就select監聽的內部有IO事件發生的時候,select返回,這個返回值就是表示select內部有幾個IO事件發生了,如果返回值是0就表示是超時了,沒有監聽到任何的IO事件發生自己超時后返回了,返回值是0,如果返回值是-1,就表示select函數運行錯誤了
@4:void FD_CLR(int fd, fd_set *set);//將fd,從fd_set中清除出去
int FD_ISSET(int fd, fd_set *set);//判斷fd,在fd_set中有沒有被置位,發生了IO事件對應fd
//會被置位返回值是1,返回值0表示沒有發生這個fd的IO
//事件
void FD_SET(int fd, fd_set *set);//將fd,添加到fd_set中
void FD_ZERO(fd_set *set); //是用來將fd_set這個集合中文件描述符置位的清除置位
這四個函數是用來操作fd_set這個文件描述符集合的
(6)poll函數
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
@1:第一個參數是輸出型參數,是文件描述符的集合,第二個參數是poll函數內部監聽的多路IO的最大的文件描述符fd+1,第三個參數是超時時間,是毫秒級別的
@2: struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
我們給poll傳遞第一個參數的時候,要設置這個結構體的fd和events,fd表示我們要監測哪個fd,events表示要監測這個fd的什么事件
@3:events如果是
POLLIN 表示監測的是讀
POLLOUT 表示監測的是寫
@4:revents是內核設置的,當監測的IO事件發生POLLIN或者POLLOUT,我們可以判斷events和revents是否相等來確定是否發生我們想要的IO事件
@5:返回值,當poll成功的時候,返回值是一個正數,非零,表示內核幫我們設置了一個revents,表示有IO事件發生,有幾路IO事件發生了,返回值就是幾,超時時返回0,返回-1表示poll函數運行錯誤了,和select的返回值定義是一樣的
7、多路復用IO的代碼實戰
(1)select函數實現的讀取鍵盤和鼠標的IO多路復用
(2)poll函數實現的讀取鍵盤和鼠標的IO多路復用
8、異步通知(異步IO)
(1)什么是異步IO,可以認為是操作系統用軟件實現的一種軟件中斷響應系統
(2)異步IO的工作方法就是,當前進程在做自己該做的事情,當有一個異步IO的信號SIGIO的時候,就會去執行這個異步IO信號對應的信號處理函數。
(3)所以就是一個進程中向操作系統注冊了一個異步IO的信號對應的信號處理函數,用signal或者sigaction注冊了一個SIGIO對應的信號處理函數,當前進程可以做想做的事情,當有異步IO事件發生了的時候,操作系統會給當前進程發一個SIGIO的異步信號,進程接受到這個SIGIO信號后就會去執行這個信號對應的信號處理函數。就跟被中斷了一樣,去執行了中斷處理函數一樣,只不過這個中斷是由信號來產生的。
(4)異步IO涉及到的函數:
fcntl(主要是設置一些異步通知,F_GETFL,F_SETFL,O_ASYNC,F_SETOWN)
@1:F_GETFL來獲取flag屬性信息
@2:F_SETFL來設置flag屬性信息
@3:O_ASYNC來指示當前的文件描述符可以被接受異步通知,也是一個flag文件屬性,將文件描述符添加這個屬性
@4:F_SETOWN用這個告訴當異步IO事件發生的時候讓OS通知誰,fcntl帶上這個參數后,后面一個變參要加上當前進程的pid,可以getpid來獲得,表示發生異步IO事件的時候OS通知當前的這個進程
signal或者sigaction(綁定SIGIO對應的信號處理函數的)
8、存儲映射IO
(1)mmao函數
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
把一個文件和一個內存區域映射起來。
(2)在LCD顯示的時候和IPC共享內存通信的時候會用到
比如,你的LCD要顯示的圖片的話,是和驅動層的一個LCD驅動中劃分出來的一個物理內存中的顯存區域,將這個內存區域設置到LCD控制器中的一個參數中,當這個內存區域有一個圖片的數據的時候,硬件就會自動刷新到LCD屏幕去顯示,但是我們現在是在應用層下,我們在文件系統中的一個打開了一個圖片文件,得到了這個圖片的數據,我們要將這個打開的圖片的數據,也就是要將一塊內存復制到LCD的顯存的那一個內存區域中,這樣每次都直接復制是很浪費CPU時間的,所以就可以mmap將LCD的顯存內存區域和我們的這個打開這個文件時的內存區域映射起來,這樣就相當于關聯起來了,實際上就變成了一個內存區域了,
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。