【Redis,Netty杨刨,Nginx 等實(shí)現(xiàn)高性能IO的核心原理】
I/O
輸入輸出(input/output)的對象可以是文件(file)晤柄, 網(wǎng)絡(luò)(socket),進(jìn)程之間的管道(pipe)妖胀。在linux系統(tǒng)中芥颈,都用文件描述符(fd)來表示。
I/O 多路復(fù)用(multiplexing)
I/O 多路復(fù)用的本質(zhì)赚抡,是通過一種機(jī)制(系統(tǒng)內(nèi)核緩沖I/O數(shù)據(jù))爬坑,讓單個(gè)進(jìn)程可以監(jiān)視多個(gè)文件描述符,一旦某個(gè)描述符就緒(一般是讀就緒或?qū)懢途w)涂臣,能夠通知程序進(jìn)行相應(yīng)的讀寫操作盾计。
select、poll 和 epoll 都是 Linux API 提供的 IO 復(fù)用方式赁遗。
Linux中提供的epoll相關(guān)函數(shù)如下:
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);
Unix五種IO模型
我們在進(jìn)行編程開發(fā)的時(shí)候署辉,經(jīng)常會涉及到同步,異步岩四,阻塞哭尝,非阻塞,IO多路復(fù)用等概念剖煌,這里簡單總結(jié)一下材鹦。
Unix網(wǎng)絡(luò)編程中的五種IO模型:
Blocking IO - 阻塞IO
NoneBlocking IO - 非阻塞IO
IO multiplexing - IO多路復(fù)用
signal driven IO - 信號驅(qū)動IO
asynchronous IO - 異步IO
對于一個(gè)network IO逝淹,它會涉及到兩個(gè)系統(tǒng)對象:
- Application 調(diào)用這個(gè)IO的進(jìn)程
- kernel 系統(tǒng)內(nèi)核
那他們經(jīng)歷的兩個(gè)交互過程是:
階段1: wait for data 等待數(shù)據(jù)準(zhǔn)備;
階段2: copy data from kernel to user 將數(shù)據(jù)從內(nèi)核拷貝到用戶進(jìn)程中桶唐。
之所以會有同步创橄、異步、阻塞和非阻塞這幾種說法就是根據(jù)程序在這兩個(gè)階段的處理方式不同而產(chǎn)生的莽红。
事件
可讀事件,當(dāng)文件描述符關(guān)聯(lián)的內(nèi)核讀緩沖區(qū)可讀邦邦,則觸發(fā)可讀事件安吁。
(可讀:內(nèi)核緩沖區(qū)非空,有數(shù)據(jù)可以讀取)
可寫事件燃辖,當(dāng)文件描述符關(guān)聯(lián)的內(nèi)核寫緩沖區(qū)可寫鬼店,則觸發(fā)可寫事件。
(可寫:內(nèi)核緩沖區(qū)不滿黔龟,有空閑空間可以寫入)
通知機(jī)制
通知機(jī)制妇智,就是當(dāng)事件發(fā)生的時(shí)候,則主動通知氏身。通知機(jī)制的反面巍棱,就是輪詢機(jī)制。
epoll 的通俗解釋
結(jié)合以上三條蛋欣,epoll的通俗解釋是:
當(dāng)文件描述符的內(nèi)核緩沖區(qū)非空的時(shí)候航徙,發(fā)出可讀信號進(jìn)行通知,當(dāng)寫緩沖區(qū)不滿的時(shí)候陷虎,發(fā)出可寫信號通知的機(jī)制到踏。
epoll 數(shù)據(jù)結(jié)構(gòu) + 算法
epoll 的核心數(shù)據(jù)結(jié)構(gòu)是:1個(gè)紅黑樹和1個(gè)鏈表。還有3個(gè)核心API尚猿。如下圖所示:
就緒列表的數(shù)據(jù)結(jié)構(gòu)
就緒列表引用著就緒的socket窝稿,所以它應(yīng)能夠快速的插入數(shù)據(jù)。
程序可能隨時(shí)調(diào)用epoll_ctl添加監(jiān)視socket凿掂,也可能隨時(shí)刪除伴榔。當(dāng)刪除時(shí),若該socket已經(jīng)存放在就緒列表中缠劝,它也應(yīng)該被移除潮梯。(事實(shí)上,每個(gè)epoll_item既是紅黑樹節(jié)點(diǎn)惨恭,也是鏈表節(jié)點(diǎn)秉馏,刪除紅黑樹節(jié)點(diǎn),自然刪除了鏈表節(jié)點(diǎn))
所以就緒列表應(yīng)是一種能夠快速插入和刪除的數(shù)據(jù)結(jié)構(gòu)脱羡。雙向鏈表就是這樣一種數(shù)據(jù)結(jié)構(gòu)萝究,epoll使用雙向鏈表來實(shí)現(xiàn)就緒隊(duì)列(對應(yīng)上圖的rdllist)免都。
epoll 索引結(jié)構(gòu)
既然epoll將“維護(hù)監(jiān)視隊(duì)列”和“進(jìn)程阻塞”分離,也意味著需要有個(gè)數(shù)據(jù)結(jié)構(gòu)來保存監(jiān)視的socket帆竹。至少要方便的添加和移除绕娘,還要便于搜索,以避免重復(fù)添加栽连。紅黑樹是一種自平衡二叉查找樹险领,搜索、插入和刪除時(shí)間復(fù)雜度都是O(log(N))秒紧,效率較好绢陌。epoll 使用了紅黑樹作為索引結(jié)構(gòu)。
Epoll在linux內(nèi)核中源碼主要為 eventpoll.c 和 eventpoll.h 主要位于fs/eventpoll.c 和 include/linux/eventpool.h, 具體可以參考linux3.16熔恢。
下述為部分關(guān)鍵數(shù)據(jù)結(jié)構(gòu)摘要, 主要介紹epitem 紅黑樹節(jié)點(diǎn) 和eventpoll 關(guān)鍵入口數(shù)據(jù)結(jié)構(gòu)脐湾,維護(hù)著鏈表頭節(jié)點(diǎn)ready list header和紅黑樹根節(jié)點(diǎn)RB-Tree root。
/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the "rbr" RB tree.
* Avoid increasing the size of this struct, there can be many thousands
* of these on a server and we do not want this to take another cache line.
*/
struct epitem {
union {
/* RB tree node links this structure to the eventpoll RB tree */
struct rb_node rbn;
/* Used to free the struct epitem */
struct rcu_head rcu;
};
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink;
/*
* Works together "struct eventpoll"->ovflist in keeping the
* single linked chain of items.
*/
struct epitem *next;
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;
/* Number of active wait queue attached to poll operations */
int nwait;
/* List containing poll wait queues */
struct list_head pwqlist;
/* The "container" of this item */
struct eventpoll *ep;
/* List header used to link this item to the "struct file" items list */
struct list_head fllink;
/* wakeup_source used when EPOLLWAKEUP is set */
struct wakeup_source __rcu *ws;
/* The structure that describe the interested events and the source fd */
struct epoll_event event;
};
/*
* This structure is stored inside the "private_data" member of the file
* structure and represents the main data structure for the eventpoll
* interface.
*/
struct eventpoll {
/* Protect the access to this structure */
spinlock_t lock;
/*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
struct mutex mtx;
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;
/* RB tree root used to store monitored fd structs */
struct rb_root rbr;
/*
* This is a single linked list that chains all the "struct epitem" that
* happened while transferring ready events to userspace w/out
* holding ->lock.
*/
struct epitem *ovflist;
/* wakeup_source used when ep_scan_ready_list is running */
struct wakeup_source *ws;
/* The user that created the eventpoll descriptor */
struct user_struct *user;
struct file *file;
/* used to optimize loop detection check */
int visited;
struct list_head visited_list_link;
};
epoll使用RB-Tree紅黑樹去監(jiān)聽并維護(hù)所有文件描述符叙淌,RB-Tree的根節(jié)點(diǎn)秤掌。
調(diào)用epoll_create時(shí),內(nèi)核除了幫我們在epoll文件系統(tǒng)里建了個(gè)file結(jié)點(diǎn)鹰霍,在內(nèi)核cache里建了個(gè) 紅黑樹 用于存儲以后epoll_ctl傳來的socket外闻鉴,還會再建立一個(gè)list鏈表,用于存儲準(zhǔn)備就緒的事件.
當(dāng)epoll_wait調(diào)用時(shí)茂洒,僅僅觀察這個(gè)list鏈表里有沒有數(shù)據(jù)即可椒拗。有數(shù)據(jù)就返回,沒有數(shù)據(jù)就sleep获黔,等到timeout時(shí)間到后即使鏈表沒數(shù)據(jù)也返回蚀苛。所以,epoll_wait非常高效玷氏。而且堵未,通常情況下即使我們要監(jiān)控百萬計(jì)的句柄,大多一次也只返回很少量的準(zhǔn)備就緒句柄而已盏触,所以渗蟹,epoll_wait僅需要從內(nèi)核態(tài)copy少量的句柄到用戶態(tài)而已.
epoll API
1. int epoll_create(int size)
功能:
內(nèi)核會產(chǎn)生一個(gè)epoll 實(shí)例數(shù)據(jù)結(jié)構(gòu)并返回一個(gè)文件描述符,這個(gè)特殊的描述符就是epoll實(shí)例的句柄赞辩,后面的兩個(gè)接口都以它為中心(即epfd形參)雌芽。size參數(shù)表示所要監(jiān)視文件描述符的最大值,不過在后來的Linux版本中已經(jīng)被棄用(同時(shí)辨嗽,size不要傳0世落,會報(bào)invalid argument錯(cuò)誤)
2. int epoll_ctl(int epfd, int op糟需, int fd屉佳, struct epoll_event *event)
功能:
將被監(jiān)聽的描述符添加到紅黑樹或從紅黑樹中刪除或者對監(jiān)聽事件進(jìn)行修改
typedef union epoll_data {
void *ptr; /* 指向用戶自定義數(shù)據(jù) */
int fd; /* 注冊的文件描述符 */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event {
uint32_t events; /* 描述epoll事件 */
epoll_data_t data; /* 見上面的結(jié)構(gòu)體 */
};
對于需要監(jiān)視的文件描述符集合谷朝,epoll_ctl對紅黑樹進(jìn)行管理,紅黑樹中每個(gè)成員由描述符值和所要監(jiān)控的文件描述符指向的文件表項(xiàng)的引用等組成武花。
op參數(shù)說明操作類型:
EPOLL_CTL_ADD:向interest list添加一個(gè)需要監(jiān)視的描述符EPOLL_CTL_DEL:從interest list中刪除一個(gè)描述符EPOLL_CTL_MOD:修改interest list中一個(gè)描述符struct epoll_event結(jié)構(gòu)描述一個(gè)文件描述符的epoll行為圆凰。在使用epoll_wait函數(shù)返回處于ready狀態(tài)的描述符列表時(shí),
data域是唯一能給出描述符信息的字段体箕,所以在調(diào)用epoll_ctl加入一個(gè)需要監(jiān)測的描述符時(shí)专钉,一定要在此域?qū)懭朊枋龇嚓P(guān)信息events域是bit mask,描述一組epoll事件累铅,在epoll_ctl調(diào)用中解釋為:描述符所期望的epoll事件驶沼,可多選。常用的epoll事件描述如下:
EPOLLIN:描述符處于可讀狀態(tài)EPOLLOUT:描述符處于可寫狀態(tài)EPOLLET:將epoll event通知模式設(shè)置成edge triggeredEPOLLONESHOT:第一次進(jìn)行通知争群,之后不再監(jiān)測EPOLLHUP:本端描述符產(chǎn)生一個(gè)掛斷事件,默認(rèn)監(jiān)測事件EPOLLRDHUP:對端描述符產(chǎn)生一個(gè)掛斷事件EPOLLPRI:由帶外數(shù)據(jù)觸發(fā)EPOLLERR:描述符產(chǎn)生錯(cuò)誤時(shí)觸發(fā)大年,默認(rèn)檢測事件
3. int epoll_wait(int epfd换薄, struct epoll_event *events, int maxevents翔试, int timeout);
功能:
阻塞等待注冊的事件發(fā)生轻要,返回事件的數(shù)目,并將觸發(fā)的事件寫入events數(shù)組中垦缅。
events: 用來記錄被觸發(fā)的events冲泥,其大小應(yīng)該和maxevents一致
maxevents: 返回的events的最大個(gè)數(shù)處于ready狀態(tài)的那些文件描述符會被復(fù)制進(jìn)ready list中,epoll_wait用于向用戶進(jìn)程返回ready list壁涎。
events和maxevents兩個(gè)參數(shù)描述一個(gè)由用戶分配的struct epoll event數(shù)組凡恍,調(diào)用返回時(shí),內(nèi)核將ready list復(fù)制到這個(gè)數(shù)組中怔球,并將實(shí)際復(fù)制的個(gè)數(shù)作為返回值嚼酝。
注意,如果ready list比maxevents長竟坛,則只能復(fù)制前maxevents個(gè)成員闽巩;反之,則能夠完全復(fù)制ready list担汤。
另外涎跨,struct epoll event結(jié)構(gòu)中的events域在這里的解釋是:
在被監(jiān)測的文件描述符上實(shí)際發(fā)生的事件。
參數(shù)timeout描述在函數(shù)調(diào)用中阻塞時(shí)間上限崭歧,單位是ms:
timeout = -1表示調(diào)用將一直阻塞隅很,直到有文件描述符進(jìn)入ready狀態(tài)或者捕獲到信號才返回;timeout = 0用于非阻塞檢測是否有描述符處于ready狀態(tài)率碾,不管結(jié)果怎么樣外构,調(diào)用都立即返回普泡;timeout > 0表示調(diào)用將最多持續(xù)timeout時(shí)間,如果期間有檢測對象變?yōu)閞eady狀態(tài)或者捕獲到信號則返回审编,否則直到超時(shí)撼班。epoll的兩種觸發(fā)方式
epoll監(jiān)控多個(gè)文件描述符的I/O事件。epoll支持邊緣觸發(fā)(edge trigger垒酬,ET)或水平觸發(fā)(level trigger砰嘁,LT),通過epoll_wait等待I/O事件勘究,如果當(dāng)前沒有可用的事件則阻塞調(diào)用線程矮湘。
select和poll只支持LT工作模式,epoll的默認(rèn)的工作模式是LT模式口糕。
epoll更高效的原因
select和poll的動作基本一致缅阳,只是poll采用鏈表來進(jìn)行文件描述符的存儲,而select采用fd標(biāo)注位來存放景描,所以select會受到最大連接數(shù)的限制十办,而poll不會。
select超棺、poll向族、epoll雖然都會返回就緒的文件描述符數(shù)量, 但是select和poll并不會明確指出是哪些文件描述符就緒,而epoll會棠绘。
造成的區(qū)別就是件相,系統(tǒng)調(diào)用返回后,調(diào)用select和poll的程序需要遍歷監(jiān)聽的整個(gè)文件描述符找到是誰處于就緒氧苍,而epoll則直接處理即可(直接監(jiān)聽到了哪些文件描述符就緒)夜矗。
select、poll都需要將有關(guān)文件描述符的數(shù)據(jù)結(jié)構(gòu)拷貝進(jìn)內(nèi)核让虐,最后再拷貝出來侯养。而epoll創(chuàng)建的有關(guān)文件描述符的數(shù)據(jù)結(jié)構(gòu)本身就存于內(nèi)核態(tài)中,系統(tǒng)調(diào)用返回時(shí)利用 mmap()
文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞:即 epoll 使用 mmap() 減少復(fù)制開銷澄干。
select逛揩、poll采用 輪詢 的方式來檢查文件描述符是否處于就緒態(tài),而epoll采用回調(diào)機(jī)制麸俘。造成的結(jié)果就是辩稽,隨著fd的增加,select和poll的效率會線性降低从媚,而epoll不會受到太大影響逞泄,除非活躍的socket很多。
epoll的邊緣觸發(fā)模式效率高,系統(tǒng)不會充斥大量不關(guān)心的就緒文件描述符喷众。
雖然epoll的性能最好各谚,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好到千,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)昌渤。
epoll與select、poll的對比
1. 用戶態(tài)將文件描述符傳入內(nèi)核的方式
select:創(chuàng)建3個(gè)文件描述符集并拷貝到內(nèi)核中憔四,分別監(jiān)聽讀膀息、寫、異常動作了赵。這里受到單個(gè)進(jìn)程可以打開的fd數(shù)量限制潜支,默認(rèn)是1024。
poll:將傳入的struct pollfd
結(jié)構(gòu)體數(shù)組拷貝到內(nèi)核中進(jìn)行監(jiān)聽柿汛。
epoll:執(zhí)行epoll_create
冗酿,會在內(nèi)核的高速cache
區(qū)中,建立一顆紅黑樹以及就緒鏈表(該鏈表存儲已經(jīng)就緒的文件描述符)络断。接著用戶執(zhí)行的epoll_ctl
函數(shù)裁替,添加文件描述符會在紅黑樹上增加相應(yīng)的結(jié)點(diǎn)。
2. 內(nèi)核態(tài)檢測文件描述符讀寫狀態(tài)的方式
select:采用輪詢方式妓羊,遍歷所有fd
,最后返回一個(gè)描述符讀寫操作是否就緒的mask掩碼稍计,根據(jù)這個(gè)掩碼給fd_set賦值躁绸。
poll:同樣采用輪詢方式,查詢每個(gè)fd的狀態(tài)臣嚣,如果就緒則在等待隊(duì)列中加入一項(xiàng)并繼續(xù)遍歷净刮。
epoll:采用事件回調(diào)機(jī)制。在執(zhí)行 epoll_ctl
的add操作時(shí)硅则,不僅將文件描述符放到紅黑樹上淹父,而且也注冊了回調(diào)函數(shù);內(nèi)核在檢測到某文件描述符可讀/可寫時(shí)會調(diào)用回調(diào)函數(shù),該回調(diào)函數(shù)將文件描述符放在就緒鏈表中怎虫。
3. 找到就緒的文件描述符并傳遞給用戶態(tài)的方式
select:將之前傳入的fd_set拷貝傳出到用戶態(tài)并返回就緒的文件描述符總數(shù)暑认。用戶態(tài)并不知道是哪些文件描述符處于就緒態(tài),需要遍歷來判斷大审。
poll:將之前傳入的 fd 數(shù)組拷貝傳出用戶態(tài)蘸际,并返回就緒的文件描述符總數(shù)。用戶態(tài)并不知道是哪些文件描述符處于就緒態(tài)徒扶,需要遍歷來判斷粮彤。
epoll:epoll_wait
只用觀察就緒鏈表中有無數(shù)據(jù)即可,最后將鏈表的數(shù)據(jù)返回給數(shù)組, 并返回就緒的數(shù)量。內(nèi)核导坟,將就緒的文件描述符屿良,放在傳入的數(shù)組中。然后惫周,依次遍歷尘惧,處理即可。這里返回的文件描述符闯两,是通過 mmap()
褥伴,讓內(nèi)核和用戶空間,共享同一塊內(nèi)存實(shí)現(xiàn)傳遞的漾狼,減少了不必要的拷貝重慢。
4. 重復(fù)監(jiān)聽的處理方式
select:將新的監(jiān)聽文件描述符集合拷貝傳入內(nèi)核中,繼續(xù)以上步驟逊躁。
poll:將新的struct pollfd結(jié)構(gòu)體數(shù)組拷貝傳入內(nèi)核中似踱,繼續(xù)以上步驟。
epoll:無需重新構(gòu)建紅黑樹稽煤,直接沿用已存在的即可兼呵。
epoll 水平觸發(fā)與邊緣觸發(fā)
epoll事件有兩種模型:
邊沿觸發(fā):edge-triggered (ET)
水平觸發(fā):level-triggered (LT)
水平觸發(fā)(level-triggered)
socket接收緩沖區(qū)不為空瓮钥, 有數(shù)據(jù)可讀, 讀事件一直觸發(fā)。
socket發(fā)送緩沖區(qū)不滿沸柔, 可以繼續(xù)寫入數(shù)據(jù), 寫事件一直觸發(fā)闷供。
邊沿觸發(fā)(edge-triggered)
socket的接收緩沖區(qū)狀態(tài)變化時(shí)仍稀,觸發(fā)讀事件,即空的接收緩沖區(qū)剛接收到數(shù)據(jù)時(shí)觸發(fā)讀事件察藐。
socket的發(fā)送緩沖區(qū)狀態(tài)變化時(shí)皮璧,觸發(fā)寫事件,即滿的緩沖區(qū)剛空出空間時(shí)觸發(fā)讀事件分飞。
邊沿觸發(fā)僅觸發(fā)一次悴务,水平觸發(fā)會一直觸發(fā)。
事件宏
EPOLLIN : 表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)譬猫;
EPOLLOUT: 表示對應(yīng)的文件描述符可以寫讯檐;
EPOLLPRI: 表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來);
EPOLLERR: 表示對應(yīng)的文件描述符發(fā)生錯(cuò)誤染服;
EPOLLHUP: 表示對應(yīng)的文件描述符被掛斷裂垦;
EPOLLET: 將 EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式(默認(rèn)為水平觸發(fā)),這是相對于水平觸發(fā)(Level Triggered)來說的肌索。
EPOLLONESHOT: 只監(jiān)聽一次事件蕉拢,當(dāng)監(jiān)聽完這次事件之后特碳,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里
libevent 采用水平觸發(fā)晕换, nginx 采用邊沿觸發(fā)
JDK并沒有實(shí)現(xiàn)邊緣觸發(fā)午乓,Netty重新實(shí)現(xiàn)了epoll機(jī)制,采用邊緣觸發(fā)方式闸准;另外像Nginx也采用邊緣觸發(fā)益愈。
JDK在Linux已經(jīng)默認(rèn)使用epoll方式,但是JDK的epoll采用的是水平觸發(fā)夷家,而Netty重新實(shí)現(xiàn)了epoll機(jī)制蒸其,采用邊緣觸發(fā)方式,netty epoll transport 暴露了更多的nio沒有的配置參數(shù)库快,如 TCP_CORK, SO_REUSEADDR等等摸袁;另外像Nginx也采用邊緣觸發(fā)。
mmap()
文件映射內(nèi)存
mmap是一種內(nèi)存映射文件(文件映射內(nèi)存义屏,建立映射關(guān)系)的方法靠汁,即將一個(gè)文件或者其它對象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對映關(guān)系闽铐。
實(shí)現(xiàn)這樣的映射關(guān)系后蝶怔,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會自動回寫臟頁面到對應(yīng)的文件磁盤上(參考:Linux文件讀寫與緩存)兄墅,即完成了對文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)踢星。
Dirty Page: 頁緩存對應(yīng)文件中的一塊區(qū)域,如果頁緩存和對應(yīng)的文件區(qū)域內(nèi)容不一致隙咸,則該頁緩存叫做臟頁(Dirty Page)沐悦。對頁緩存進(jìn)行修改或者新建頁緩存,只要沒有刷磁盤扎瓶,都會產(chǎn)生臟頁所踊。Linux支持以下5種緩沖區(qū)類型:
Clean 未使用泌枪、新創(chuàng)建的緩沖區(qū)
Locked 被鎖住概荷、等待被回寫
Dirty 包含最新的有效數(shù)據(jù),但還沒有被回寫
Shared 共享的緩沖區(qū)
Unshared 原來被共享但現(xiàn)在不共享
相反碌燕,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間误证,從而可以實(shí)現(xiàn)不同進(jìn)程間的文件共享。如下圖所示:
由上圖可以看出修壕,進(jìn)程的虛擬地址空間愈捅,由多個(gè)虛擬內(nèi)存區(qū)域構(gòu)成。虛擬內(nèi)存區(qū)域是進(jìn)程的虛擬地址空間中的一個(gè)同質(zhì)區(qū)間慈鸠,即具有同樣特性的連續(xù)地址范圍蓝谨。上圖中所示的text數(shù)據(jù)段(代碼段)、初始數(shù)據(jù)段、BSS數(shù)據(jù)段譬巫、堆咖楣、棧和內(nèi)存映射,都是一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域芦昔。而為內(nèi)存映射服務(wù)的地址空間處在堆棧之間的空余部分诱贿。
linux內(nèi)核使用vm_area_struct結(jié)構(gòu)來表示一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域,由于每個(gè)不同質(zhì)的虛擬內(nèi)存區(qū)域功能和內(nèi)部機(jī)制都不同咕缎,因此一個(gè)進(jìn)程使用多個(gè)vm_area_struct結(jié)構(gòu)來分別表示不同類型的虛擬內(nèi)存區(qū)域珠十。各個(gè)vm_area_struct結(jié)構(gòu)使用鏈表或者樹形結(jié)構(gòu)鏈接,方便進(jìn)程快速訪問凭豪,如下圖所示:
vm_area_struct結(jié)構(gòu)中包含區(qū)域起始和終止地址以及其他相關(guān)信息焙蹭,同時(shí)也包含一個(gè)vm_ops指針,其內(nèi)部可引出所有針對這個(gè)區(qū)域可以使用的系統(tǒng)調(diào)用函數(shù)墅诡。這樣壳嚎,進(jìn)程對某一虛擬內(nèi)存區(qū)域的任何操作需要用要的信息,都可以從vm_area_struct中獲得末早。mmap函數(shù)就是要創(chuàng)建一個(gè)新的vm_area_struct結(jié)構(gòu)烟馅,并將其與文件的物理磁盤地址相連。
mmap在write和read時(shí)會發(fā)生什么
1.write
- 1.進(jìn)程(用戶態(tài))將需要寫入的數(shù)據(jù)直接copy到對應(yīng)的mmap地址(內(nèi)存copy)
- 2.若mmap地址未對應(yīng)物理內(nèi)存然磷,則產(chǎn)生缺頁異常郑趁,由內(nèi)核處理
- 3.若已對應(yīng),則直接copy到對應(yīng)的物理內(nèi)存
- 4.由操作系統(tǒng)調(diào)用姿搜,將臟頁回寫到磁盤(通常是異步的)
因?yàn)槲锢韮?nèi)存是有限的寡润,mmap在寫入數(shù)據(jù)超過物理內(nèi)存時(shí),操作系統(tǒng)會進(jìn)行頁置換舅柜,根據(jù)淘汰算法梭纹,將需要淘汰的頁置換成所需的新頁,所以mmap對應(yīng)的內(nèi)存是可以被淘汰的(若內(nèi)存頁是"臟"的致份,則操作系統(tǒng)會先將數(shù)據(jù)回寫磁盤再淘汰)变抽。這樣,就算mmap的數(shù)據(jù)遠(yuǎn)大于物理內(nèi)存氮块,操作系統(tǒng)也能很好地處理绍载,不會產(chǎn)生功能上的問題。
2.read
從圖中可以看出滔蝉,mmap要比普通的read系統(tǒng)調(diào)用少了一次copy的過程击儡。因?yàn)閞ead調(diào)用,進(jìn)程是無法直接訪問kernel space的蝠引,所以在read系統(tǒng)調(diào)用返回前阳谍,內(nèi)核需要將數(shù)據(jù)從內(nèi)核復(fù)制到進(jìn)程指定的buffer蛀柴。但mmap之后,進(jìn)程可以直接訪問mmap的數(shù)據(jù)(page cache)矫夯。
總結(jié)
一張圖總結(jié)一下select,poll,epoll的區(qū)別:
epoll是Linux目前大規(guī)模網(wǎng)絡(luò)并發(fā)程序開發(fā)的首選模型名扛。在絕大多數(shù)情況下性能遠(yuǎn)超select和poll。目前流行的高性能web服務(wù)器Nginx正式依賴于epoll提供的高效網(wǎng)絡(luò)套接字輪詢服務(wù)茧痒。但是肮韧,在并發(fā)連接不高的情況下,多線程+阻塞I/O方式可能性能更好旺订。
既然select弄企,poll,epoll都是I/O多路復(fù)用的具體的實(shí)現(xiàn)区拳,之所以現(xiàn)在同時(shí)存在拘领,其實(shí)他們也是不同歷史時(shí)期的產(chǎn)物:
- select出現(xiàn)是1984年在BSD里面實(shí)現(xiàn)的
- 14年之后也就是1997年才實(shí)現(xiàn)了poll,其實(shí)拖那么久也不是效率問題樱调, 而是那個(gè)時(shí)代的硬件實(shí)在太弱约素,一臺服務(wù)器處理1千多個(gè)鏈接簡直就是神一樣的存在了,select很長段時(shí)間已經(jīng)滿足需求
- 2002, 大神 Davide Libenzi 實(shí)現(xiàn)了epoll笆凌。
參考資料
- http://www.reibang.com/p/397449cadc9a
- https://baijiahao.baidu.com/s?id=1641172494287388070&wfr=spider&for=pc
- http://www.reibang.com/p/b8203d46895c
- https://zhuanlan.zhihu.com/p/93609693
- epoll-asynchronous-network-programming
- epoll github
- epoll 官方手冊
- TCP echo server epoll
- C10k 問題
- epoll server code
- why is epoll faster than select
- (推薦)epoll implementation
- 知乎epoll
- https://segmentfault.com/a/1190000018517562
- https://blog.csdn.net/qq_33611327/article/details/81738195
- http://www.reibang.com/p/755338d11865