版權聲明:本文爲博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/zhaobryant/article/details/80557262
1、epoll簡介
epoll是當前在Linux下開發大規模併發網絡程序的熱門選擇,epoll在Linux2.6內核中正式引入,和select類似,都是IO多路複用(IO multiplexing)技術。node
按照man手冊的說法,epoll是爲處理大批量句柄而作了改進的poll。數組
Linux下有如下幾個經典的服務器模型:服務器
一、PPC模型和TPC模型
PPC(Process Per Connection)模型和TPC(Thread Per Connection)模型的設計思想相似,就是給每個到來的鏈接都分配一個獨立的進程或者線程來服務。對於這兩種模型,其須要耗費較大的時間和空間資源。當管理鏈接數較多時,進程或線程的切換開銷較大。所以,這類模型能接受的最大鏈接數都不會高,通常都在幾百個左右。網絡
二、select模型
對於select模型,其主要有如下幾個特色:數據結構
最大併發數限制:因爲一個進程所打開的fd(文件描述符)是有限制的,由FD_SETSIZE設置,默認值是1024/2048,所以,select模型的最大併發數就被限制了。架構
效率問題:每次進行select調用都會線性掃描所有的fd集合。這樣,效率就會呈現線性降低。併發
內核/用戶空間內存拷貝問題:select在解決將fd消息傳遞給用戶空間時採用了內存拷貝的方式。這樣,其處理效率不高。app
三、poll模型
對於poll模型,其雖然解決了select最大併發數的限制,但依然沒有解決掉select的效率問題和內存拷貝問題。socket
四、epoll模型
對比於其餘模型,epoll作了以下改進:ide
支持一個進程打開較大數目的文件描述符(fd)
select模型對一個進程所打開的文件描述符是有必定限制的,其由FD_SETSIZE設置,默認爲1024/2048。這對於那些須要支持上萬鏈接數目的高併發服務器來講顯然太少了,這個時候,能夠選擇兩種方案:一是能夠選擇修改FD_SETSIZE宏而後從新編譯內核,不過這樣作也會帶來網絡效率的降低;二是能夠選擇多進程的解決方案(傳統的Apache方案),不過雖然Linux中建立線程的代價比較小,但仍然是不可忽視的,加上進程間數據同步遠不及線程間同步的高效,因此也不是一種完美的方案。
可是,epoll則沒有對描述符數目的限制,它所支持的文件描述符上限是整個系統最大能夠打開的文件數目,例如,在1GB內存的機器上,這個限制大概爲10萬左右。
IO效率不會隨文件描述符(fd)的增長而線性降低
傳統的select/poll的一個致命弱點就是當你擁有一個很大的socket集合時,不過任一時間只有部分socket是活躍的,select/poll每次調用都會線性掃描整個socket集合,這將致使IO處理效率呈現線性降低。
可是,epoll不存在這個問題,它只會對活躍的socket進行操做,這是由於在內核實現中,epoll是根據每一個fd上面的callback函數實現的。所以,只有活躍的socket纔會主動去調用callback函數,其餘idle狀態socket則不會。在這一點上,epoll實現了一個僞AIO,其內部推進力在內核。
在一些benchmark中,若是全部的socket基本上都是活躍的,如高速LAN環境,epoll並不比select/poll效率高,相反,過多使用epoll_ctl,其效率反而還有稍微降低。可是,一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。
使用mmap加速內核與用戶空間的消息傳遞
不管是select,poll仍是epoll,它們都須要內核把fd消息通知給用戶空間。所以,如何避免沒必要要的內存拷貝就很重要了。對於該問題,epoll經過內核與用戶空間mmap同一塊內存來實現。
內核微調
這一點其實不算epoll的優勢了,而是整個Linux平臺的優勢,Linux賦予開發者微調內核的能力。好比,內核TCP/IP協議棧使用內存池管理sk_buff結構,那麼,能夠在運行期間動態調整這個內存池大小(skb_head_pool)來提升性能,該參數能夠經過使用echo xxxx > /proc/sys/net/core/hot_list_length來完成。再如,能夠嘗試使用最新的NAPI網卡驅動架構來處理數據包數量巨大但數據包自己很小的特殊場景。
2、epoll API
epoll只有epoll_create、epoll_ctl和epoll_wait這三個系統調用。其定義以下:
#include <sys/epoll.h>
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);
1
2
3
4
5
6
7
一、epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
1
2
3
能夠調用epoll_create方法建立一個epoll的句柄。
須要注意的是,當建立好epoll句柄後,它就會佔用一個fd值。在使用完epoll後,必須調用close函數進行關閉,不然可能致使fd被耗盡。
二、epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
1
2
3
epoll的事件註冊函數,它不一樣於select是在監聽事件時告訴內核要監聽什麼類型的事件,而是經過epoll_ctl註冊要監聽的事件類型。
第一個參數epfd:epoll_create函數的返回值。
第二個參數events:表示動做類型。有三個宏來表示:
* EPOLL_CTL_ADD:註冊新的fd到epfd中;
* EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
* EPOLL_CTL_DEL:從epfd中刪除一個fd。
第三個參數fd:須要監聽的fd。
第四個參數event:告訴內核須要監聽什麼事件。
struct epoll_event結構以下所示:
// 保存觸發事件的某個文件描述符相關的數據
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
// 感興趣的事件和被觸發的事件
struct epoll_event {
__uint32_t events; // Epoll events
epoll_data_t data; // User data variable
};
1
2
3
4
5
6
7
8
9
10
11
12
13
如上所示,對於Epoll Events,其能夠是如下幾個宏的集合:
EPOLLIN:表示對應的文件描述符可讀(包括對端Socket);
EPOLLOUT:表示對應的文件描述符可寫;
EPOLLPRI:表示對應的文件描述符有緊急數據可讀(帶外數據);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET:將EPOLL設爲邊緣觸發(Edge Triggered),這是相對於水平觸發(Level Triggered)而言的。
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket,須要再次
三、epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
1
2
3
收集在epoll監控的事件中已經發生的事件。參數events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中(events不能夠是空指針,內核只負責把數據賦值到這個event數組中,不會去幫助咱們在用戶態分配內存)。maxevents告訴內核這個events數組有多大,這個maxevents的值不能大於建立epoll_create時的size。參數timeout是超時時間(毫秒)。若是函數調用成功,則返回對應IO上已準備好的文件描述符數目,若是返回0則表示已經超時。
3、epoll工做模式
1. LT模式(Level Triggered,水平觸發)
該模式是epoll的缺省工做模式,其同時支持阻塞和非阻塞socket。內核會告訴開發者一個文件描述符是否就緒,若是開發者不採起任何操做,內核仍會一直通知。
2. ET模式(Edge Triggered,邊緣觸發)
該模式是一種高速處理模式,當且僅當狀態發生變化時纔會得到通知。在該模式下,其假定開發者在接收到一次通知後,會完整地處理該事件,所以內核將再也不通知這一事件。注意,緩衝區中還有未處理的數據不能說是狀態變化,所以,在ET模式下,開發者若是隻讀取了一部分數據,其將再也得不到通知了。正確的作法是,開發者本身確認讀完了全部的字節(一直調用read/write直到出錯EAGAGIN爲止)。
Nginx默認採用的就是ET(邊緣觸發)。
4、epoll高效性探討
epoll的高效性主要體如今如下三個方面:
(1)select/poll每次調用都要傳遞所要監控的全部fd給select/poll系統調用,這意味着每次調用select/poll時都要將fd列表從用戶空間拷貝到內核,當fd數目不少時,這會形成性能低效。對於epoll_wait,每次調用epoll_wait時,其不須要將fd列表傳遞給內核,epoll_ctl不須要每次都拷貝全部的fd列表,只須要進行增量式操做。所以,在調用epoll_create函數以後,內核已經在內核開始準備數據結構用於存放須要監控的fd了。其後,每次epoll_ctl只是對這個數據結構進行簡單的維護操做便可。
(2)內核使用slab機制,爲epoll提供了快速的數據結構。在內核裏,一切都是文件。所以,epoll向內核註冊了一個文件系統,用於存儲全部被監控的fd。當調用epoll_create時,就會在這個虛擬的epoll文件系統中建立一個file節點。epoll在被內核初始化時,同時會分配出epoll本身的內核告訴cache區,用於存放每一個咱們但願監控的fd。這些fd會以紅黑樹的形式保存在內核cache裏,以支持快速查找、插入和刪除。這個內核高速cache,就是創建連續的物理內存頁,而後在之上創建slab層,簡單的說,就是物理上分配好想要的size的內存對象,每次使用時都使用空閒的已分配好的對象。
(3)當調用epoll_ctl往epfd註冊百萬個fd時,epoll_wait仍然可以快速返回,並有效地將發生的事件fd返回給用戶。緣由在於,當咱們調用epoll_create時,內核除了幫咱們在epoll文件系統新建file節點,同時在內核cache建立紅黑樹用於存儲之後由epoll_ctl傳入的fd外,還會再創建一個list鏈表,用於存儲準備就緒的事件。當調用epoll_wait時,僅僅觀察這個list鏈表中有無數據便可。若是list鏈表中有數據,則返回這個鏈表中的全部元素;若是list鏈表中沒有數據,則sleep而後等到timeout超時返回。因此,epoll_wait很是高效,並且,一般狀況下,即便咱們須要監控百萬計的fd,但大多數狀況下,一次也只返回少許準備就緒的fd而已。所以,每次調用epoll_wait,其僅須要從內核態複製少許的fd到用戶空間而已。那麼,這個準備就緒的list鏈表是怎麼維護的呢?過程以下:當咱們執行epoll_ctl時,除了把fd放入到epoll文件系統裏file對象對應的紅黑樹以外,還會給內核中斷處理程序註冊一個回調函數,其告訴內核,若是這個fd的中斷到了,就把它放到準備就緒的list鏈表中。
如此,一棵紅黑樹、一張準備就緒的fd鏈表以及少許的內核cache,就幫咱們解決了高併發下fd的處理問題。
總結一下:
執行epoll_create時,建立了紅黑樹和就緒list鏈表;
執行epoll_ctl時,若是增長fd,則檢查在紅黑樹中是否存在,存在則當即返回,不存在則添加到紅黑樹中,而後向內核註冊回調函數,用於當中斷事件到來時向準備就緒的list鏈表中插入數據。
執行epoll_wait時當即返回準備就緒鏈表裏的數據便可。
5、epoll源碼分析
eventpoll_init過程:
static int __init eventpoll_init(void)
{
int error;
init_MUTEX(&epsem);
/* Initialize the structure used to perform safe poll wait head wake ups */
ep_poll_safewake_init(&psw);
/* Allocates slab cache used to allocate "struct epitem" items */
epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,
NULL, NULL);
/* Allocates slab cache used to allocate "struct eppoll_entry" */
pwq_cache = kmem_cache_create("eventpoll_pwq",
sizeof(struct eppoll_entry), 0,
EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);
/*
* Register the virtual file system that will be the source of inodes
* for the eventpoll files
*/
error = register_filesystem(&eventpoll_fs_type);
if (error)
goto epanic;
/* Mount the above commented virtual file system */
eventpoll_mnt = kern_mount(&eventpoll_fs_type);
error = PTR_ERR(eventpoll_mnt);
if (IS_ERR(eventpoll_mnt))
goto epanic;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: successfully initialized.\n",
current));
return 0;
epanic:
panic("eventpoll_init() failed\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
其中,epoll用slab分配器kmem_cache_create分配內存用於存放struct epitem和struct eppoll_entry。
當向系統中添加一個fd時,就會建立一個epitem結構體,這是內核管理epoll的基本數據結構:
/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the hash.
*/
struct epitem {
/* RB-Tree node used to link this structure to the eventpoll rb-tree */
struct rb_node rbn; // 用於主結構管理的紅黑樹
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink; // 事件就緒隊列
/* 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; // 該項屬於哪一個主結構體
/* The structure that describe the interested events and the source fd */
struct epoll_event event; // 註冊的感興趣的時間
/*
* Used to keep track of the usage count of the structure. This avoids
* that the structure will desappear from underneath our processing.
*/
atomic_t usecnt;
/* List header used to link this item to the "struct file" items list */
struct list_head fllink;
/* List header used to link the item to the transfer list */
struct list_head txlink;
/*
* This is used during the collection/transfer of events to userspace
* to pin items empty events set.
*/
unsigned int revents;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
對於每一個epfd,其對應的數據結構爲:
/*
* This structure is stored inside the "private_data" member of the file
* structure and rapresent the main data sructure for the eventpoll
* interface.
*/
struct eventpoll {
/* Protect the this structure access */
rwlock_t lock;
/*
* This semaphore is used to ensure that files are not removed
* while epoll is using them. This is read-held during the event
* collection loop and it is write-held during the file cleanup
* path, the epoll file exit code and the ctl operations.
*/
struct rw_semaphore sem;
/* 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; // 用於管理全部fd的紅黑樹(根節點)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
eventpoll在epoll_create時建立:
/*
* It opens an eventpoll file descriptor by suggesting a storage of "size"
* file descriptors. The size parameter is just an hint about how to size
* data structures. It won't prevent the user to store more than "size"
* file descriptors inside the epoll interface. It is the kernel part of
* the userspace epoll_create(2).
*/
asmlinkage long sys_epoll_create(int size)
{
int error, fd;
struct eventpoll *ep;
struct inode *inode;
struct file *file;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n",
current, size));
/*
* Sanity check on the size parameter, and create the internal data
* structure ( "struct eventpoll" ).
*/
error = -EINVAL;
if (size <= 0 || (error = ep_alloc(&ep)) != 0) // ep_alloc爲eventpoll分配內存並初始化
goto eexit_1;
/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure, and inode and a free file descriptor.
*/
error = ep_getfd(&fd, &inode, &file, ep); // 建立於eventpoll相關的數據結構,包括file、inode和fd等信息
if (error)
goto eexit_2;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
current, size, fd));
return fd;
eexit_2:
ep_free(ep);
kfree(ep);
eexit_1:
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
current, size, error));
return error;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
如上,內核中維護了一棵紅黑樹,大體結構以下:
下面是epoll_ctl函數過程:
/*
* The following function implements the controller interface for
* the eventpoll file that enables the insertion/removal/change of
* file descriptors inside the interest set. It represents
* the kernel part of the user space epoll_ctl(2).
*/
asmlinkage long
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{
int error;
struct file *file, *tfile;
struct eventpoll *ep;
struct epitem *epi;
struct epoll_event epds;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
current, epfd, op, fd, event));
error = -EFAULT;
if (ep_op_hash_event(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))
goto eexit_1;
/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd); // 獲取epfd對應的文件
if (!file)
goto eexit_1;
/* Get the "struct file *" for the target file */
tfile = fget(fd); // 獲取fd對應的文件
if (!tfile)
goto eexit_2;
/* The target file descriptor must support poll */
error = -EPERM;
if (!tfile->f_op || !tfile->f_op->poll)
goto eexit_3;
/*
* We have to check that the file structure underneath the file descriptor
* the user passed to us _is_ an eventpoll file. And also we do not permit
* adding an epoll file descriptor inside itself.
*/
error = -EINVAL;
if (file == tfile || !is_file_epoll(file))
goto eexit_3;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = file->private_data;
down_write(&ep->sem);
/* Try to lookup the file inside our hash table */
epi = ep_find(ep, tfile, fd); // 在哈希表中查詢,防止重複添加
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD: // 添加節點,調用ep_insert函數
if (!epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_insert(ep, &epds, tfile, fd);
} else
error = -EEXIST;
break;
case EPOLL_CTL_DEL: // 刪除節點,調用ep_remove函數
if (epi)
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD: // 修改節點,調用ep_modify函數
if (epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &epds);
} else
error = -ENOENT;
break;
}
/*
* The function ep_find() increments the usage count of the structure
* so, if this is not NULL, we need to release it.
*/
if (epi)
ep_release_epitem(epi);
up_write(&ep->sem);
eexit_3:
fput(tfile);
eexit_2:
fput(file);
eexit_1:
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n",
current, epfd, op, fd, event, error));
return error;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
對於ep_insert函數,基本代碼以下:
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
{
int error, revents, pwake = 0;
unsigned long flags;
struct epitem *epi;
struct ep_pqueue epq;
error = -ENOMEM;
// 分配一個epitem結構體來保存每一個加入的fd
if (!(epi = kmem_cache_alloc(epi_cache, SLAB_KERNEL)))
goto eexit_1;
/* Item initialization follow here ... */
// 初始化結構體
ep_rb_initnode(&epi->rbn);
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->txlink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
atomic_set(&epi->usecnt, 1);
epi->nwait = 0;
/* Initialize the poll table using the queue callback */
epq.epi = epi;
// 安裝poll回調函數
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
/*
* Attach the item to the poll hooks and get current event bits.
* We can safely use the file* here because its usage count has
* been increased by the caller of this function.
*/
// 將當前item添加至poll hook中,而後獲取當前event位
revents = tfile->f_op->poll(tfile, &epq.pt);
/*
* We have to check if something went wrong during the poll wait queue
* install process. Namely an allocation for a wait queue failed due
* high memory pressure.
*/
if (epi->nwait < 0)
goto eexit_2;
/* Add the current item to the list of active epoll hook for this file */
spin_lock(&tfile->f_ep_lock);
list_add_tail(&epi->fllink, &tfile->f_ep_links);
spin_unlock(&tfile->f_ep_lock);
/* We have to drop the new item inside our item list to keep track of it */
write_lock_irqsave(&ep->lock, flags);
/* Add the current item to the rb-tree */
ep_rbtree_insert(ep, epi);
/* If the file is already "ready" we drop it inside the ready list */
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
/* Notify waiting tasks that events are available */
if (waitqueue_active(&ep->wq))
wake_up(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)\n",
current, ep, tfile, fd));
return 0;
eexit_2:
ep_unregister_pollwait(ep, epi);
/*
* We need to do this because an event could have been arrived on some
* allocated wait queue.
*/
write_lock_irqsave(&ep->lock, flags);
if (ep_is_linked(&epi->rdllink))
ep_list_del(&epi->rdllink);
write_unlock_irqrestore(&ep->lock, flags);
kmem_cache_free(epi_cache, epi);
eexit_1:
return error;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
其中,init_poll_funcptr和tfile->f_op->poll將ep_ptable_queue_proc註冊到epq.pt中的qproc中。
ep_ptable_queue_proc函數設置了等待隊列的ep_poll_callback回調函數。在設備硬件數據到來時,硬件中斷函數喚醒該等待隊列上等待的進程時,會調用喚醒函數ep_poll_callback。
ep_poll_callback函數主要的功能是將被監視文件的等待事件就緒時,將文件對應的epitem實例添加到就緒隊列中,當用戶調用epoll_wait時,內核會將就緒隊列中的事件報告給用戶。
epoll_wait的實現以下:
/*
* Implement the event wait interface for the eventpoll file. It is the kernel
* part of the user space epoll_wait(2).
*/
asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, int timeout)
{
int error;
struct file *file;
struct eventpoll *ep;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d)\n",
current, epfd, events, maxevents, timeout));
/* The maximum number of event must be greater than zero */
if (maxevents <= 0 || maxevents > MAX_EVENTS) // 檢查maxevents參數
return -EINVAL;
/* Verify that the area passed by the user is writeable */
// 檢查用戶空間傳入的events指向的內存是否可寫
if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {
error = -EFAULT;
goto eexit_1;
}
/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd); // 獲取epfd對應的eventpoll文件的file實例,file結構是在epoll_create中建立的
if (!file)
goto eexit_1;
/*
* We have to check that the file structure underneath the fd
* the user passed to us _is_ an eventpoll file.
*/
error = -EINVAL;
if (!is_file_epoll(file))
goto eexit_2;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = file->private_data;
/* Time to fish for events ... */
// 核心處理函數
error = ep_poll(ep, events, maxevents, timeout);
eexit_2:
fput(file);
eexit_1:
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d) = %d\n",
current, epfd, events, maxevents, timeout, error));
return error;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
其中,調用ep_poll函數,具體流程以下:
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
int res, eavail;
unsigned long flags;
long jtimeout;
wait_queue_t wait;
/*
* Calculate the timeout by checking for the "infinite" value ( -1 )
* and the overflow condition. The passed timeout is in milliseconds,
* that why (t * HZ) / 1000.
*/
jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?
MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;
retry:
write_lock_irqsave(&ep->lock, flags);
res = 0;
if (list_empty(&ep->rdllist)) {
/*
* We don't have any available event to return to the caller.
* We need to sleep here, and we will be wake up by
* ep_poll_callback() when events will become available.
*/
init_waitqueue_entry(&wait, current);
add_wait_queue(&ep->wq, &wait);
for (;;) {
/*
* We don't want to sleep if the ep_poll_callback() sends us
* a wakeup in between. That's why we set the task state
* to TASK_INTERRUPTIBLE before doing the checks.
*/
set_current_state(TASK_INTERRUPTIBLE);
if (!list_empty(&ep->rdllist) || !jtimeout)
break;
if (signal_pending(current)) {
res = -EINTR;
break;
}
write_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout);
write_lock_irqsave(&ep->lock, flags);
}
remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
/* Is it worth to try to dig for events ? */
eavail = !list_empty(&ep->rdllist);
write_unlock_irqrestore(&ep->lock, flags);
/*
* Try to transfer events to user space. In case we get 0 events and
* there's still timeout left over, we go trying again in search of
* more luck.
*/
if (!res && eavail &&
!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)
goto retry;
return res;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
ep_send_events函數用於向用戶空間發送就緒事件。ep_send_events函數將用戶傳入的內存簡單封裝到ep_send_events_data結構中,而後調用ep_scan_ready_list將就緒隊列中的事件傳入用戶空間的內存。
6、參考 Epoll詳解及源碼分析——CSDN博客 ———————————————— 版權聲明:本文爲CSDN博主「KiteRunner24」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/zhaobryant/article/details/80557262