Linux面試題2:網(wǎng)絡(luò)IO模型 & IO多路復(fù)用
網(wǎng)絡(luò)IO
先確定一下范圍,我們討論的都是網(wǎng)絡(luò)IO,現(xiàn)階段計算機早已經(jīng)從CPU密集型轉(zhuǎn)換成網(wǎng)絡(luò)IO密集型,所以網(wǎng)絡(luò)io的類型對于服務(wù)響應(yīng)而言更重要。
五種IO模型
依據(jù)Unix的IO分類,網(wǎng)絡(luò)IO分為五類
阻塞IO(BlockingIO
非阻塞IO(Non-Blocking IO
IO多路復(fù)用( IO Multiplexing
信號驅(qū)動IO(signal driven IO
異步IO(async IO
內(nèi)核態(tài)和用戶態(tài)
可見另一篇文章
網(wǎng)絡(luò)IO的兩階段階段

等待網(wǎng)卡讀就緒 —> 將網(wǎng)卡數(shù)據(jù)復(fù)制奧內(nèi)核緩沖區(qū)
將內(nèi)核緩沖區(qū)的數(shù)據(jù)復(fù)制到用戶空間
其中:第一階段主要用來區(qū)分是否是阻塞IO
阻塞與非阻塞
進行一個IO操作之后,無論是否有數(shù)據(jù)、是否就緒,是否會立刻返回而不阻塞用戶進程的邏輯。
當(dāng)用戶進程發(fā)出read操作時,如果kernel中的數(shù)據(jù)沒有準(zhǔn)備好,不會block用戶進程,而是返回一個EAGAIN err。從用戶的角度而言,發(fā)起一個讀操作,不需要等待,馬上得到了一個結(jié)果。
一旦kernel的數(shù)據(jù)準(zhǔn)備好了,收到用戶進程的一個systemcall,就會馬上把數(shù)據(jù)拷貝到用戶內(nèi)存,然后返回。
同步與異步
第二階段,內(nèi)核將數(shù)據(jù)拷貝到用戶空間是否是同步進行的,決定是否是異步IO;除了aync IO以外其他都是同步的IO模型。
面試回答
概述
IO多路復(fù)用實際就是select/poll/epoll這些多路選擇器,使用一個線程同時監(jiān)聽多個文件描述符(fd_set), I/O事件,阻塞等待并且在某個文件描述符可讀寫時收到通知。linux在處理網(wǎng)絡(luò)IO連接時的優(yōu)化,復(fù)用的不是I/O連接,而是復(fù)用的是線程,讓一個線程處理多個連接。
select/poll/epoll
選擇器運行邏輯特點缺點select1.最大并發(fā)數(shù)限制; 2.每次調(diào)用select,需要把fd_set集合拷貝到內(nèi)核態(tài);3.性能衰減嚴(yán)重pollpoll與select類似,只是沒有最大并發(fā)數(shù)限制epoll
select
#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);
// 和 select 緊密結(jié)合的四個宏:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
運行邏輯
fd_set如果是1 字節(jié)byte, 1byte = 8bit,每一個bit可以表示一個文件描述符fd,則1byte的fd_set最大可以對應(yīng)8個fd
執(zhí)行 FD_ZERO(&set), 則 set 用位表示是 0000,0000
若 fd=5, 執(zhí)行 FD_SET(fd, &set); 后 set 變?yōu)?0001,0000(第 5 位置 為 1)
再加入 fd=2, fd=1,則 set 變?yōu)?0001,0011
執(zhí)行 select(6, &set, 0, 0, 0) 阻塞等待
若 fd=1, fd=2 上都發(fā)生可讀事件,則 select 返回,此時 set 變?yōu)?0000,0011 (注意:沒有事件發(fā)生的 fd=5 被清空)
特點
可以監(jiān)控的文件描述符個數(shù)取決于 sizeof(fd_set)的值。如果 sizeof(fd_set) = 512, 每個bit表示一個文件描述符, 512 * 8 = 4096。
需要拷貝 fd_set,轉(zhuǎn)換成一個array
需要循環(huán)fd_set,線性掃描整個fd_set
缺點
epoll
epoll是Linux Kernel 2.6之后引入的IO事件驅(qū)動技術(shù),本質(zhì)上還是一個線程處理所有鏈接的等待消息準(zhǔn)備好IO事件。但是 當(dāng)數(shù)十萬的并發(fā)連接存在時,可能每一毫秒豬油數(shù)百個活躍的鏈接,同時其余數(shù)十萬連接在這一毫秒是非活躍的,而select&poll的使用方法是?返回的活躍鏈接 == select(全部帶監(jiān)控的連接)
高頻調(diào)用的接口是select()方法,而這個方法任何輕微的效率損失都會被高頻兩個字放大。epoll解決了這個問題.
#include <sys/epoll.h> ?
int epoll_create(int size); // int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll的工作原理如下圖:

epoll_ctl 來插入和刪除一個fd,實現(xiàn)從用戶態(tài)到內(nèi)核態(tài)的拷貝,確保每一個fd只在生命周期一次拷貝。
epoll使用紅黑樹存儲所有監(jiān)控的fd,紅黑樹的時間復(fù)雜度O(logN)。
每一個fd有一個關(guān)鍵步驟:fd回合相應(yīng)的設(shè)備(網(wǎng)卡、硬盤)驅(qū)動程序建立一個回調(diào)關(guān)系,在fd相應(yīng)的時間出發(fā)之后,內(nèi)核就會調(diào)用這個回調(diào)函數(shù),ep_poll_callback,這個回調(diào)函數(shù)會把fd添加到fdllist的雙向鏈表(就緒列表之中)epoll_wait這個就是檢查是否有就緒的fd,所以非常高效。
鏈接:https://www.dianjilingqu.com/623084.html