Epoll技術擴展

Epoll技術回顧

基於上一篇的epoll技術存在一些問題,對此將糾正部分要點

epoll使用技術

使用鎖的技術

  • 讀寫鎖:內核在操作對象進行輪詢的時候加讀鎖,而通過加寫鎖爲了保證喚醒只執行一次,即在網絡socket數據報可達,通過中斷上下文調用wake_up()方法來觸發回調callback方法的執行,通過callback方法將執行的task添加到CPU就緒隊列️中,方便CPU進行調度執行
  • 使用epoll空間的內置的鎖mtx:當事件就緒的時候,內核需要將就緒的socket拷貝到用戶空間,爲了保證期間能夠被拷貝而能夠進行休眠,休眠的過程需要進行加鎖
  • 全局鎖:通過加全局鎖釋放epoll容器下的資源,避免產生死鎖
epoll技術使用SLAB的方式進行內存管理

epoll技術基於SLAB的方式管理內存,通過SLAB的方式來創建epoll空間以及epitem結構體對象

epoll對象

// eventpoll.c
ep = kzalloc(sizeof(*ep), GFP_KERNEL);

// slab.h
static inline void *kzalloc(size_t size, gfp_t gfp)
{
     return kmalloc(size, gfp | __GFP_ZERO);
}

epitem結構體

/* Slab cache used to allocate "struct epitem" */
static struct kmem_cache *epi_cache __read_mostly;

/* Slab cache used to allocate "struct eppoll_entry" */
static struct kmem_cache *pwq_cache __read_mostly;

SLAB內存管理特點

  • 使用連續的內存地址空間來存儲epitem/epoll,避免內存碎片(多個epoll的產生是在多核下多進程)
  • 使用的epitem/epoll釋放存放在"對象池"中進行重複利用,同時減少創建和銷燬epitem帶來的性能開銷(內存申請和釋放的開銷),可以理解爲高速緩存
  • 內存分配原理如下:
    在這裏插入圖片描述
epoll技術設計

epoll空間以及epitem部分源代碼

struct eventpoll {
    /* 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;
    
    /* Lock which protects rdllist and ovflist */
    rwlock_t lock;  // 讀寫鎖
    
    /* RB tree root used to store monitored fd structs */
    struct rb_root_cached 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;
}

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;   // 就緒隊列header節點,與epoll空間的ready list連接
    
    /* The file descriptor information this item refers to */
    struct epoll_filefd ffd;    // 存儲註冊的socket的fd以及對應的file
    
	/* The "container" of this item */
	struct eventpoll *ep;   // 指向epoll容器
	
	/* 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;   // 監聽的事件變化結構
}

epoll設計分析

  • epoll技術在邏輯設計上,epoll空間作爲epitem的容器,同時將註冊的socket綁定到epitem中,並且epoll空間與epitem在邏輯設計存儲上用紅黑樹結構進行存儲,方便通過socket的fd快速定義到對應的epitem
  • epoll空間通過ovflist將所有就緒的epitem以單鏈表的結構連接起來
  • epitem包含wake_up喚醒函數以及對應的socket描述符信息,同時在註冊新的socket的時候綁定對應的一個epitem,而每個epitem都將添加到對應的epoll_entry節點上,並將epoll_entry節點添加到epoll空間下的等待隊列中,在系統調用epoll_wait的時候進行輪詢
  • ep_wait通過epoll空間的輪詢隊列wait queue進行事件輪詢遍歷檢測當前是否有就緒事件,如果有會將就緒事件拷貝到ready list隊列中
  • 對此基於上述進行小結,對於wait_queue - poll_wait_queue - ovflist - ready_list,其流程如下:
    在這裏插入圖片描述
Epoll給予的思考小結

使用epitem&epoll技術的思考

  • epoll技術通過使用epitem中間層的方式來完成對每個註冊的socket進行監控,通過對等待隊列上事件節點的輪詢將就緒節點以鏈表的方式連接起來,避免了數組遍歷與查找
  • 藉助epoll容器來對每個epitem進行管理,由內核對epoll空間進行事件監控,即將所有的事情委託給容器來進行操作,epoll容器將最終的結果返回告知喚醒處理邏輯
  • 從epoll技術的思考中,我們也可以想到在實際業務工作領域中,可以借鑑epoll思想引入中間層來解決原有結構存在的不足,通過中間層增強功能來彌補不足

採用分散冗餘思想

  • epoll技術爲了解決大內存數據拷貝問題,將註冊和等待進行拆分,分別針對對應的細小功能模塊進行優化和改進,也就是相比select/poll設計上更爲細粒度且專業化
  • 爲了提升性能,在內部使用SLAB內存管理方式會預先申請連續內存存儲對應的對象,也就是在某一個時刻上存在空間的冗餘
  • 對此,在我們大型系統中,爲了緩解高併發流量的壓力,也會採取集羣分佈式的方式來分擔系統的流量負載,也會存在分散冗餘的設計
其他高級輪詢技術
/dev/poll

是Solaris操作系統上名爲/dev/poll的特殊文件提供了可擴展的輪詢大量描述,相比select技術,其輪詢技術可以預先設置好待查詢的文件描述符列表,然後進入一個循環等待事件發生,每次循環回來之後不需要再設置該列表,其流程如下:

  • 打開/dev/poll文件,然後初始化一個pollfd結構數組(poll使用的結構)
  • 調用write方法往/dev/poll寫這個結構數組並傳遞給內核
  • 執行io_ctlDP_POLL阻塞自身以等待事件的發生
  • 調用結構如下:
struct dvpoll{
    struct pollfd* dp_fds;      // 鏈表的形式,指向一個緩衝區,提供給ioctl返回的時候存儲一個鏈表的數組
    int            dp_nfds;     // 緩衝區成員大小
    int            timeout; 
}
  • 解決大內存拷貝問題,但是對於/dev/poll的實現需要查看對應的solaris系統細節,存在兼容性問題
kqueue技術
  • FreeBSD4.1引入kqueue技術,允許進程向內核註冊描述所關注的kqueue事件的事件過濾器(event filter),其定義如下:
// 返回一個新的kqueue描述符,用戶後續的kevent調用
int kqueue(void);

// 用於註冊事件也用於確定是否有事件發生
int kevent(int kq,                                                                  // kqueue的註冊事件fd
           const struct kevent *changelist, int nchanges,                           // 給出關注事件作出的更改,無更改爲NULL & 0
           struct kevent *eventlist, int nevents,                                   // kevent結構數組
           const struct timespc *timeout);                                          // 超時

// 更新事件函數
void EV_SET(struct kevent *kev, uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_r data, void *udata);

// kevent結構體
struct kevent {
    uintptr_t   ident;
    short filter;
    u_short flags;
    u_int fflags;
    intptr_r data;
    void *udata;
}
  • 從上述的結構可以推測出與epoll的實現原理類似,只不過相比epoll實現,增加更多事件的監聽(異步IO/文件修改通知/進程跟蹤/信號處理等)
  • 但是和/dev/poll一樣存在的兼容性問題,目前是在FreeBSD系統中
  • 對應不同的事件以及事件的過濾器,如下:
    在這裏插入圖片描述在這裏插入圖片描述
C10K
什麼是C10K問題

對於C10K問題,給出以下的參考鏈接

https://en.wikipedia.org/wiki/C10k_problem

C10K的理解

摘錄wiki百科,在互聯網應用中,服務端進程要處理大量的客戶端的socket,C10K命名是爲了同時處理1w個連接的縮寫名稱,是屬於一個優化問題.

現有解決C10K成熟方案
  • Nginx是爲了解決C10K設計的高併發連接處理服務器
  • IO框架
  • 事件驅動設計
  • Reactor設計模式
C10K與C10M相關文章
http://www.kegel.com/c10k.html
http://highscalability.com/blog/2013/5/13/the-secret-to-10-million-concurrent-connections-the-kernel-i.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章