同步和異步
發出一個調用時,在沒有得到結果之前,該調用就不返回,返回時,就攜帶了結果。
調用在發出之后,這個調用就直接返回了,所以沒有返回結果,通過狀態,通知,回調來得到結果。
阻塞和非阻塞
阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。
非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。
舉個栗子
-
普通水壺,水開沒有特征
小明燒水,等水開了,再去吃飯
小明燒水,立馬去吃飯,但在吃飯過程中,沒隔一段就去看水的狀態
-
響水壺,水開壺會響
小明燒水,等水壺響了,再去吃飯
小明燒水,立馬去吃飯,等水壺響了,關火,繼續吃飯
IO發展史
相關技術
select
poll
epoll
kqueue
IOCP
epoll
//用戶數據載體
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
//fd裝載入內核的載體
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
//三板斧api
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);
poll_create是在內核區創建一個epoll相關的一些列結構,并且將一個句柄fd返回給用戶態,后續的操作都是基于此fd的,參數size是告訴內核這個結構的元素的大小。
epoll_ctl是將fd添加/刪除于epoll_create返回的epfd中,其中epoll_event是用戶態和內核態交互的結構,定義了用戶態關心的事件類型和觸發時數據的載體epoll_data。
epoll_wait是阻塞等待內核返回的可讀寫事件,epfd還是epoll_create的返回值,events是個結構體數組指針存儲epoll_event,也就是將內核返回的待處理epoll_event結構都存儲下來,maxevents告訴內核本次返回的最大fd數量,這個和events指向的數組是相關的。
epoll_event是用戶態需監控fd的代言人,后續用戶程序對fd的操作都是基于此結構的。
-
epoll_create場景:
- 我們剛進入公司,需要設備,確定一個學習委員,學習委員幫大家申請設備,學習委員告訴IT部門,我是校招負責人,他們給一個憑證,以后都會用到
-
epoll_ctl場景:
- 學習委員開始收集大家的設備需求,比如AA需要一個11 max pro,BB需要一個2019年16.1寸 macbook pro,學習委員告訴IT部門
-
epoll_wait場景:
- 學習委員就在等通知,這時候藍信收到通知,BB的macbook pro批下來了,AA的還沒有批下來,學習委員就繼續等;
epoll高效的原因
在Linux內核里,一切皆文件。所以,epoll向內核注冊了一個文件系統,用于存儲上述的被監控socket。當你調用epoll_create時,就會在這個虛擬的epoll文件系統里創建一個file結點。當然這個file不是普通文件,它只服務于epoll。
epoll在被內核初始化時(操作系統啟動),同時會開辟出epoll自己的內核高速cache區,用于安置每一個我們想監控的socket,這些socket會以紅黑樹的形式保存在內核cache里,以支持快速的查找、插入、刪除。這個內核高速cache區,就是建立連續的物理內存頁,然后在之上建立slab層,簡單的說,就是物理上分配好你想要的size的內存對象,每次使用時都是使用空閑的已分配好的對象。
由于我們在調用epoll_create時,內核除了幫我們在epoll文件系統里建了個file結點,在內核cache里建了個紅黑樹用于存儲以后epoll_ctl傳來的socket外,還會再建立一個list鏈表,用于存儲準備就緒的事件.
當我們執行epoll_ctl時,除了把socket放到epoll文件系統里file對象對應的紅黑樹上之外,還會給內核中斷處理程序注冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表里。所以,當一個socket上有數據到了,內核在把網卡上的數據copy到內核中后就來把socket插入到準備就緒鏈表里了。
當epoll_wait調用時,僅僅觀察這個list鏈表里有沒有數據即可。有數據就返回,沒有數據就sleep,等到timeout時間到后即使鏈表沒數據也返回。所以,epoll_wait非常高效。而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的準備就緒句柄而已,所以,epoll_wait僅需要從內核態copy少量的句柄到用戶態而已.
執行過程
- 執行epoll_create時,創建了紅黑樹和就緒鏈表,執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,用于當中斷事件來臨時向準備就緒鏈表中插入數據。執行epoll_wait時立刻返回準備就緒鏈表里的數據即可。
補充說明
- epoll不一定是最佳實踐,要結合具體業務場景
用戶態與內核態
內核態:控制計算機的硬件資源,并提供上層應用程序運行的環境。
用戶態:上層應用程序的活動空間,應用程序的執行必須依托于內核提供的資源。
系統調用:為了使上層應用能夠訪問到這些資源,內核為上層應用提供訪問的接口。
-
系統調用與上層應用程序的關系:
- 如果將系統調用比作是一個“比畫”,那么上層應用就是一個“漢字”。如果完成一個“漢字”,就需要通過多個系統調用。
-
系統調用與公用函數庫的關系:
- 公用函數庫實現對系統調用的封裝,將簡單的業務邏輯接口呈現給用戶,方便用戶調用,從這個角度上看,庫函數就像是組成漢字的“偏旁”。
在CPU的所有指令中,有一些指令是非常危險的,如果錯用,將導致整個系統崩潰。所以,CPU將指令分為特權指令和非特權指令,對于那些危險的指令,只允許操作系統及其相關模塊使用,普通的應用程序只能使用那些不會造成災難的指令。
-
用戶態到內核態
- 系統調用
- 異常事件,缺頁中斷
- 外設中斷,Ctrl+C
系統調用的本質其實也是中斷,相對于外圍設備的硬中斷,這種中斷稱為軟中斷。從觸發方式和效果上來看,這三種切換方式是完全一樣的,都相當于是執行了一個中斷響應的過程。但是從觸發的對象來看,系統調用是進程主動請求切換的,而異常和硬中斷則是被動的。