圖文詳解 epoll 原理【Redis如筛,Netty堡牡,Nginx實(shí)現(xiàn)高性能IO的核心原理】epoll 詳解

【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)對象:

  1. Application 調(diào)用這個(gè)IO的進(jìn)程
  2. 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笆凌。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末圣猎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乞而,更是在濱河造成了極大的恐慌送悔,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爪模,死亡現(xiàn)場離奇詭異欠啤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屋灌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門洁段,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人共郭,你說我怎么就攤上這事祠丝。” “怎么了落塑?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵纽疟,是天一觀的道長罐韩。 經(jīng)常有香客問我憾赁,道長,這世上最難降的妖魔是什么散吵? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任龙考,我火速辦了婚禮蟆肆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晦款。我一直安慰自己炎功,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布缓溅。 她就那樣靜靜地躺著蛇损,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坛怪。 梳的紋絲不亂的頭發(fā)上淤齐,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音袜匿,去河邊找鬼更啄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛居灯,可吹牛的內(nèi)容都是我干的祭务。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼怪嫌,長吁一口氣:“原來是場噩夢啊……” “哼义锥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岩灭,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤缨该,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后川背,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贰拿,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年熄云,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膨更。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缴允,死狀恐怖荚守,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情练般,我是刑警寧澤矗漾,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站薄料,受9級特大地震影響敞贡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摄职,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一誊役、第九天 我趴在偏房一處隱蔽的房頂上張望获列。 院中可真熱鬧,春花似錦蛔垢、人聲如沸击孩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巩梢。三九已至,卻和暖如春艺玲,著一層夾襖步出監(jiān)牢的瞬間且改,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工板驳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留又跛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓若治,卻偏偏與公主長得像慨蓝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子端幼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容