1. 程式人生 > 其它 >淺談NIO和Epoll實現原理

淺談NIO和Epoll實現原理

1 前置知識

1.1 socket是什麼?

就是傳輸控制層tcp連線通訊。

1.2 fd是什麼?

fd既file descriptor-檔案描述符。Java中用物件代表輸入輸出流等...在Linux系統中不是面向物件的,是一切皆檔案的,拿檔案來代表輸入輸出流。其實是一個數值,例如1,2,3,4...0是標準輸入,1是標準輸出,2是錯誤輸出... 任何程序在作業系統中都有自己IO對應的檔案描述符,參考如下:
[root@iZj6c1dib207c054itjn30Z ~]# ps -ef | grep redis
root     20688     1  0 Nov23 ?        00:01:01
/opt/redis/bin/redis-server 127.0.0.1:6379 root 20786 1 0 Nov23 ? 00:01:00 /opt/redis/bin/redis-server 127.0.0.1:6380 root 30741 30719 0 12:26 pts/1 00:00:00 grep --color=auto redis [root@iZj6c1dib207c054itjn30Z ~]# cd /proc/20688/fd [root@iZj6c1dib207c054itjn30Z fd]# ll total 0 lrwx------ 1 root root 64 Nov 23 21:50
0 -> /dev/null lrwx------ 1 root root 64 Nov 23 21:50 1 -> /dev/null lrwx------ 1 root root 64 Nov 23 21:50 2 -> /dev/null lr-x------ 1 root root 64 Nov 23 21:50 3 -> pipe:[27847377] l-wx------ 1 root root 64 Nov 23 21:50 4 -> pipe:[27847377] lrwx------ 1 root root 64 Nov 23 21:50 5 -> anon_inode:[eventpoll] lrwx
------ 1 root root 64 Nov 23 21:50 6 -> socket:[27847384]

2 從BIO到epoll

2.1 BIO

計算機有核心,客戶端和核心連線產生fd,計算機的程序或執行緒會讀取相應的fd。 因為socket在這個時期是blocking的,執行緒讀取socket產生的檔案描述符時,如果資料包沒到,讀取命令不能返回,會被阻塞。導致會有更多的執行緒被丟擲,單位CPU在某一時間片只能處理一個執行緒,出現數據包到了的執行緒等著資料包沒到的執行緒的情況,造成CPU資源浪費,CPU切換執行緒成本巨大。 例如早期Tomcat7.0版預設就是BIO。

2.2 早期NIO

socket對應的fd是非阻塞的 單位CPU只用一個執行緒,就一顆CPU只跑一個執行緒,沒有CPU切換損耗,在使用者空間輪詢遍歷所有的檔案描述符。從遍歷到取資料都是自己完成,所以是同步非阻塞IO。 問題是如果有1000個fd,代表使用者程序輪詢呼叫1000次kernel, 使用者空間查詢一次檔案描述符就得呼叫一次系統呼叫,讓後核心態使用者態來回切換成本很大。

2.3 多路複用NIO

核心向前發展,輪詢放到核心裡,核心增加一個系統呼叫select函式,統一把一千個fd傳給select函式,核心操作select函式,fd準備好後返回給執行緒,拿著返回的檔案描述符找出ready的再去調read。如果1000個fd只有1個有資料,以前要調1000次,現在只調1次,相對前面在系統呼叫上更加精準。還是同步非阻塞的,只是減少了使用者態和核心態的切換。 Linux只能實現NIO,不能實現AIO 問題:使用者態和核心態溝通的fd相關資料要來回拷貝

2.3 epoll

核心通過mmap實現共享空間,使用者態和核心態有一個空間是共享的,檔案描述符fd存在共享空間實現使用者態和核心態共享。epoll裡面有三個呼叫,使用者空間先epoll_create準備一個共享空間mmap,裡面維護一個紅黑樹,核心態將連線註冊進紅黑樹,epoll_ctl寫入。當有資料準備好了,呼叫epoll_wait中斷阻塞,取連結串列fd,再單獨呼叫read mmap應用:kafka實現資料通過socket存到伺服器檔案上的過程也是mmap epoll應用:redis和nginx的worker程序等等