相關視頻決議
epoll的網路模型,從redis、memcached到nginx,一起搞定
linux服務器性能優化之異步的原理與實作
手寫一個用戶態網路協議堆疊,瞬間提升你網路功底
前言
強迫癥不能忍受這種極其繞的概念而不給個說法,同步(synchronous)/異步(asynchronous),阻塞(blocking)/非阻塞(non-blocking),阻塞IO/非阻塞IO/同步IO/異步IO/IO復用(IO Multiplexing),select/poll/epoll這些概念困擾我許久,下面給出這一階段我個人的理解,
同步/異步與阻塞/非阻塞的理解
執行緒是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器調度和分派的基本單位,用執行緒執行程式流的程序去理解同步異步,阻塞非阻塞,同步異步關注的是流執行程序需不需要等待外部呼叫的結果,而阻塞非阻塞關注的是外部呼叫對流本身產生的影響,
同步與異步
執行緒的執行程序中,產生一個外部呼叫,如果需要等待該呼叫回傳才能繼續執行緒流則叫做同步,不需要等待結果回傳執行緒流可以繼續往下執行的情況叫做異步,
區分:執行緒流向下執行需不需要等待系統呼叫的結果,
阻塞與非阻塞
執行緒執行程序中,產生一個外部呼叫后,會不會把該執行緒流“堵”住,會“堵”對應的是阻塞,反之為非阻塞,
Linux的五種IO模型
上一節中對同步/異步,阻塞/非阻塞的描述只能說能夠恰好區分它們,如果不是在計算機領域而是生活中,道理也類似,然而計算機中的某些專業術語又需要放在專門的情景中去看,例如下面將要提到的Linux IO模型,建議理解模型本身,而不是摳同步/異步與阻塞非阻塞的字眼,因為會發現就算是非阻塞模型也有阻塞的部分,同步IO與異步IO的區別是IO操作的時候會不會讓process阻塞,
下面對模型描述的最終來源為:
Richard Stevens的“UNIX?? Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2節“I/O Models ”
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
同步IO模型
用IO操作中有阻塞來判斷,5種IO模型中4種屬于同步IO,分別是阻塞IO模型,非阻塞IO模型,IO復用模型,信號驅動IO模型,
blocking IO
阻塞IO是socket的默認設定,其模型如下圖所示,程式呼叫recvfrom產生一個系統呼叫,kernel收到該呼叫請求后有兩個步驟,第一是等待資料準備好,第二是將資料從內核空間拷貝到用戶空間然后回傳OK,用戶空間收到系統呼叫回傳后才會繼續程式流的執行,

non-blocking IO
socket使用非阻塞IO模型需要對socket進行另行設定,非阻塞IO模型如下所示,內核收到系統呼叫后,若資料未準備好立即回傳error,用戶行程收到error會繼續產生系統呼叫,直到資料準備好了并被拷貝到用戶空間,

【文章福利】需要C/C++ Linux服務器架構師學習資料加群812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)

IO multiplexing
select/poll/epoll對應的是IO復用模型,優勢是能夠監聽多個socket,模型圖如下所示,用戶行程呼叫select產生系統呼叫,kernel會監聽所有select負責的socket,一旦有一個socket資料準備好了,kernel即回傳,用戶再去recvfrom產生系統呼叫將資料從內核空間讀到用戶空間,

signal-driven IO
用戶程式注冊一個信號handler,然后繼續做后續的事情,當內核資料準備好了會發送一個信號,程式呼叫recvfrom進行系統呼叫將資料從內核空間拷貝到用戶空間,

異步I/O模型
用IO操作中無阻塞來判斷,5種IO模型中只有異步IO,
asynchronous IO
異步IO模型如下,aio_read產生系統呼叫,kernel在資料準備好后將資料從內核空間拷貝到用戶空間后回傳一個信號告知read資料成功,整個程序程式呼叫aio_read后就繼續執行其他部分直到收到信號,呼叫handler處理,

模型的對比
Kernel有兩個程序,等待資料準備好和拷貝資料到用戶空間,用戶程式的阻塞一般有兩種情況,select的阻塞和socket IO的阻塞,5中IO模型的對比如下,

select/poll/epoll
Select/poll/epoll能夠同時監聽多個檔案描述符fd,當有fd的讀寫操作完成時會回傳這些fd,可以對應于IO復用模型中的系統呼叫查詢fd是否準備好資料的那一部分,
select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
typedef struct {
unsigned long *in, *out, *ex;
unsigned long *res_in, *res_out, *res_ex;
} fd_set_bits;
select在用戶層使用某個結構標識被監聽的fd以及監聽的狀態,每一個fd用1bit表示,為1表示這個檔案是被監聽的,0表示不監聽,in, out, ex指向的bit陣串列示對應的讀,寫,例外檔案的描述符,res_in, res_out,res_ex指向的bit陣串列示對應的讀,寫,例外檔案的描述符的檢測結果,
1、這個結構被拷貝到內核層,
2、對所有的fd注冊回呼函式__pollwait
3、呼叫fd的poll方法遍歷整個FD_SESIZET所有的fd,檢查是不是自己需要監聽的,如果監聽的fd發生了感興趣的事(檔案讀寫操作完成或者例外,參考用戶態預先的設定),則poll方法回傳一個描述讀寫操作是否就緒的mask掩碼,根據mask掩碼給fd_set賦值,poll->poll_wait->__pollwait會把當前行程掛到對應檔案的inode中的fd的等待佇列,
4、如果一輪遍歷無果則掛起,直到超時或者有設備驅動發生自身資源可讀寫后將其從等待佇列喚醒,則執行新一輪的遍歷,
5、把fd_set從內核空間拷貝到用戶空間并將行程從各個等待佇列中洗掉,
poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd;
short events;
short revents;
};
poll的實作機制與select類似,不一樣的是poll的使用中用戶態直接提供了需要監聽的fd的資訊,pollfd結構記錄被監聽的fd和它的狀態,
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[0];
};
另外poll使用poll_list結構來記錄監聽的fd,每一個poll_list節點都包含一個pollfd陣列,引數被拷貝到內核后,poll_list被遍歷,換言之pollfd陣列被遍歷,與select一樣,所有的fd都被遍歷,
epoll
int epoll_create(int size);
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將程序細化為一組系統呼叫,包括1個epoll_create,多個epoll_ctrl,1個epoll_wait,內核針對epoll操作添加了一個檔案系統”eventpollfs”,每一個或者多個要監視的fd都有一個對應的eventpollfs檔案系統的inode節點,主要資訊保存在eventpoll結構體中,而被監視的檔案的重要資訊則保存在epitem結構體中,在執行epoll_create和epoll_ctrl時把用戶態的資訊保存到內核態了,之后即使反復地呼叫epoll_wait,也不會像select/poll那樣重復地拷貝引數,掃描fd,反復地把當前行程放入/放出等待佇列,
epoll_create
epoll_create()的功能是創建一個eventpollfs檔案系統的inode節點,主要資訊保存在eventpoll結構體中,eventpoll記錄了eventpollfs檔案系統的inde節點的重要資訊,其中成員rbr保存該epoll檔案節點監視的所有描述符,組織的方式是一棵紅黑樹,被監視的檔案的重要資訊則保存在epitem結構體中,
epoll_ctl
Epoll_ctl實作一系列操作,它呼叫ep_find()從eventpoll中的紅黑樹獲得epitem結構體,根據op引數的不同而選擇不同的操作,當op為EPOLL_CTL_ADD,一般情況下epitem無法在eventpoll的紅黑樹中找到,所以呼叫ep_insert創建一個epitem結構體并插入到對應的紅黑樹中,
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
…
revents = tfile->f_op->poll(tfile, &epq.pt);
…
然后ep_insert呼叫init_poll_funcptr注冊一個回呼函式ep_ptable_queue_proc,該函式會在呼叫f_op->poll時被執行,Ep_ptable_queue_proc函式分配一個epoll等待佇列結點epoll_entry,一方面把它掛到檔案操作的等待佇列中,另一方面把它掛到epitem的佇列中,此外它還注冊了一個等待佇列的回呼函式ep_poll_callback,ep_poll_callback在完成操作完成,喚醒當前等待行程之前被呼叫,會把epitem放到eventpoll的完成佇列,然后喚醒等待行程,
epoll_waite
epoll_wait的作業是等待檔案操作完成并回傳,它的主體是ep_poll(),該函式檢查eventpoll中有沒有已經完成的事件,有的話就把結果回傳,沒有的話呼叫schedule_timeout()進入休眠,直到行程被再度喚醒或者超時,
對比
select/poll的弱點在于需要輪詢遍歷fd,當監聽fd多時開銷大;而epoll依賴于回呼函式,當活躍fd過多時開銷就大,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/247749.html
標籤:其他
上一篇:指標深度剖析——深入淺出解讀懸空指標和指向指標,告訴你為什么不能沒有指標
下一篇:讀書筆記-精準努力-自信與自卑
