select 和 epoll
最近有朋友在面試的時候被問了select 和epoll效率差的原因,和一般人一樣,大部分都會回答select是輪詢、epoll是觸發式的,所以效率高。這個答案聽上去很完美,大致也說出了二者的主要區別。
今天閒來無事,翻看了下核心程式碼,結合核心程式碼和大家分享下我的觀點。
一、連線數
我本人也曾經在專案中用過select和epoll,對於select,感觸最深的是linux下select最大數目限制(windows 下似乎沒有限制),每個程序的select最多能處理FD_SETSIZE個FD(檔案控制代碼),
如果要處理超過1024個控制代碼,只能採用多程序了。
常見的使用slect的多程序模型是這樣的: 一個程序專門accept,成功後將fd通過unix socket傳遞給子程序處理,父程序可以根據子程序負載分派。曾經用過1個父程序+4個子程序 承載了超過4000個的負載。
這種模型在我們當時的業務執行的非常好。epoll在連線數方面沒有限制,當然可能需要使用者呼叫API重現設定程序的資源限制。
二、IO差別
1、select的實現
這段可以結合linux核心程式碼描述了,我使用的是2.6.28,其他2.6的程式碼應該差不多吧。
先看看select:
select系統呼叫的程式碼在fs/Select.c下,
asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timeval __user *tvp)
{
struct timespec end_time, *to = NULL;
struct timeval tv;
int ret;
if (tvp) {
if (copy_from_user(&tv, tvp, sizeof(tv)))
return -EFAULT;
to = &end_time;
if (poll_select_set_timeout(to,
tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
return -EINVAL;
}
ret = core_sys_select(n, inp, outp, exp, to);
ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
return ret;
}
前面是從使用者控制元件拷貝各個fd_set到核心空間,接下來的具體工作在core_sys_select中,
core_sys_select->do_select,真正的核心內容在do_select裡:
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
ktime_t expire, *to = NULL;
struct poll_wqueues table;
poll_table *wait;
int retval, i, timed_out = 0;
unsigned long slack = 0;
rcu_read_lock();
retval = max_select_fd(n, fds);
rcu_read_unlock();
if (retval < 0)
return retval;
n = retval;
poll_initwait(&table);
wait = &table.pt;
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
wait = NULL;
timed_out = 1;
}
if (end_time && !timed_out)
slack = estimate_accuracy(end_time);
retval = 0;
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
set_current_state(TASK_INTERRUPTIBLE);
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
const struct file_operations *f_op = NULL;
struct file *file = NULL;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
if (all_bits == 0) {
i += __NFDBITS;
continue;
}
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
int fput_needed;
if (i >= n)
break;
if (!(bit & all_bits))
continue;
file = fget_light(i, &fput_needed);
if (file) {
f_op = file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll)
mask = (*f_op->poll)(file, retval ? NULL : wait);
fput_light(file, fput_needed);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
}
}
}
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
cond_resched();
}
wait = NULL;
if (retval || timed_out || signal_pending(current))
break;
if (table.error) {
retval = table.error;
break;
}
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
}
if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
timed_out = 1;
}
__set_current_state(TASK_RUNNING);
poll_freewait(&table);
return retval;
}
上面的程式碼很多,其實真正關鍵的程式碼是這一句:
mask = (*f_op->poll)(file, retval ? NULL : wait);
這個是呼叫檔案系統的 poll函式,不同的檔案系統poll函式自然不同,由於我們這裡關注的是tcp連線,而socketfs的註冊在 net/Socket.c裡。
register_filesystem(&sock_fs_type);
socket檔案系統的函式也是在net/Socket.c裡:
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
};
從sock_poll跟隨下去,
最後可以到 net/ipv4/tcp.c的
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
這個是最終的查詢函式,
也就是說select 的核心功能是呼叫tcp檔案系統的poll函式,不停的查詢,如果沒有想要的資料,主動執行一次排程(防止一直佔用cpu),直到有一個連線有想要的訊息為止。
從這裡可以看出select的執行方式基本就是不同的呼叫poll,直到有需要的訊息為止,如果select 處理的socket很多,這其實對整個機器的效能也是一個消耗。
2、epoll的實現
epoll的實現程式碼在 fs/EventPoll.c下,
由於epoll涉及到幾個系統呼叫,這裡不逐個分析了,僅僅分析幾個關鍵點,
第一個關鍵點在
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
這是在我們呼叫sys_epoll_ctl 新增一個被管理socket的時候呼叫的函式,關鍵的幾行如下:
epq.epi = epi;
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. Note that after
* this operation completes, the poll callback can start hitting
* the new item.
*/
revents = tfile->f_op->poll(tfile, &epq.pt);
這裡也是呼叫檔案系統的poll函式,不過這次初始化了一個結構,這個結構會帶有一個poll函式的callback函式:ep_ptable_queue_proc,
在呼叫poll函式的時候,會執行這個callback,這個callback的功能就是將當前程序新增到 socket的等待程序上。
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq;
if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
/* We have to signal that an error occurred */
epi->nwait = -1;
}
}
注意到引數 whead 實際上是 sk->sleep,其實就是將當前程序新增到sk的等待佇列裡,當該socket收到資料或者其他事件觸發時,會呼叫
sock_def_readable 或者sock_def_write_space 通知函式來喚醒等待程序,這2個函式都是在socket建立的時候填充在sk結構裡的。
從前面的分析來看,epoll確實是比select聰明的多、輕鬆的多,不用再苦哈哈的去輪詢了。
相關推薦
Python網絡編程篇之select和epoll
unix cat 必須 inpu 結束 新的 eno {} 提升 1. select 原理 在多路復?的模型中, ?較常?的有select模型和epoll模型。 這兩個都是系統接?, 由操作系統提供。 當然, Python的select模塊進?了更?級的封裝。 ?絡通信被U
select 和 epoll 的區別總結
在Linux中,select 和epoll函式,都是為了監控大量的描述符,是一種I/O多路複用技術。下面總結它們的區別: select 與 epoll區別 1、開啟的最大描述符數量限制 select 檔案描述符使用的是linux ext3,因此開啟數量受限制,一
多路複用IO模型中的select和epoll
多路複用IO模型中的select和epoll 一,前提知識——檔案描述符fd 1、檔案描述符簡介 首先從檔案描述符開始講起。因為,對於核心而言,所有開啟的檔案都是通過檔案描述符引用的。那麼檔案描述符到底是什麼? 檔案描述符(file descriptor)通常是一個小的非負整
linux中的select和epoll
select select能監控的描述符個數由核心中的FD_SETSIZE限制,僅為1024,即使能重新編譯核心改變FD_SETSIZE的值(比如poll),但不能提高select的效能。 每次呼叫都掃描所有描述符的狀態,在高併發下有可能有未處理的連線等待超時,此時效能較低。 epo
Select和Epoll底層實現的區別
JAVA的NIO技術從1.5開始,一直到現在的JDK8,這套JDK自帶的API幾乎填充了了整個java端伺服器的程式碼實現,人們都是大談特談這些介面,但是很少有人深究作業系統實現的底層細節,這篇文章帶你簡單瀏覽一下這些底層的細節。 JDK 1.5 中NIO出來後
select和epoll的區別,以及epoll的優勢所在
select的缺點: 支援的fd數量有限: 單個程序能夠監視的檔案描述符的數量存在最大限制,通常是1024,當然可以更改數量,但由於select採用輪詢的方式掃描檔案描述符,檔案描述符數量越多,效能
select和epoll的區別
流 首先我們來定義流的概念。一個流可以是檔案,socket,pipe等等可以進行I/O操作的核心物件。不管是檔案,還是套接字,還是管道,我們都可以把他們看作流。 之後我們來討論I/O的操作,通過read,我們可以從流中讀入資料;通過write,我們可以往流寫入
select 和 epoll 區別
select 和 epol 都是用來監聽套接字上是否有事情發生, select 採用輪詢方式,epoll 是觸發方式,用回撥把資訊賦給event機構體 select : 輪詢檢查檔案描述符集合, 實現方式: fd_set fdRead; FD_ZERO(
事件觸發機制:Poll,Select和Epoll實現原理分析
Poll和Select和Epoll都是事件觸發機制,當等待的事件發生就觸發進行處理,多用於linux實現的伺服器對客戶端連線的處理。 Poll和Select都是這樣的機制:可以阻塞地同時探測一組支援非阻塞的IO裝置,是否有事件發生(如可讀,可寫,有高優先順序的錯誤輸出,出現
select和epoll 原理概述&優缺點比較
這個問題在面試跟網路程式設計相關的崗位的時候基本都會被問到,剛剛看到一個很好的比喻: 就像收本子的班長,以前得一個個學生地去問有沒有本子,如果沒有,它還得等待一段時間而後又繼續問,現在好了,只走一次,如果沒有本子,班長就告訴大家去那裡交本子,當班長想起要取本子
select 和 epoll
最近有朋友在面試的時候被問了select 和epoll效率差的原因,和一般人一樣,大部分都會回答select是輪詢、epoll是觸發式的,所以效率高。這個答案聽上去很完美,大致也說出了二者的主要區別。 今天閒來無事,翻看了下核心程式碼,結合核心程式碼和大家分享下我
python實現select和epoll模型socket網路程式設計
select目前幾乎在所有的平臺上支援,其良好跨平臺支援也是它的一個優點,事實 上從現在看來,這也是它所剩不多的優點之一,現在其實更多的人用epoll,在 python下epoll文件有點少,就先講究搞搞select ~ select的一個缺點在於單個程序能夠監視
Nginx 伺服器 select 和epoll的區別
epoll為什麼這麼快 epoll是多路複用IO(I/O Multiplexing)中的一種方式,但是僅用於linux2.6以上核心,在開始討論這個問題之前,先來解釋一下為什麼需要多路複用IO. 以一個生活中的例子來解釋. 假設你在大學中讀書,要等待一個朋
select和epoll模型
利用select或者epoll實現I/O多路複用。 select的缺陷: 高併發的核心解決方案是一個執行緒處理所有連線的“等待訊息準備好”,當有數十萬併發連線存在時,可能每一毫秒只有數百個連線是活躍的。其餘的在這一毫秒都是非活躍的。select使用的方法是: 返回活
select 和 epoll的區別
epoll都是用來監聽套接字上是否有事件發生,簡單來講,select是輪詢方式,而epoll是觸發方式的,用回撥把資訊賦給event結構體。 select:輪詢檢查檔案描述符集合,實現方法如下: fd_set fdRead; //將檔案描述符集合清零 FD_ZER
棒槌的工作第11天-----------------------單詞(select和epoll)
epo 不知道 http baidu add aid 什麽 https nbsp https://baike.baidu.com/item/epoll/10738144?fr=aladdin epoll百科 https://baike.baidu.com/item/se
select、poll和epoll
time 應用 使用場合 seconds const 方式 文件描述符 div inux I/O復用: 在一個進程或者多個進程的需要多個I/O,不能阻塞在一個I/O上而停止不前,而是用到I/O復用。進程預先告知內核需要哪些I/O描述符,內核一旦發現指定的一個或多個I/O
IO復用: select 和poll 到epoll
fan shuf margin info epoll app lec select doc 釁洗翰迸鞘亟鶴橙號帽被父徘http://weibo.com/u/6347152344 燃從棧導榷登僭漣兩怕倏哦硬誄http://huiyi.docin.com/sina_63558
select poll和epoll區別
tex 可能 font 是否 這也 epo 拷貝 帶來 poll (1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是
select,poll 和 epoll ??
配對 epo spa 基本 style 都是 速度慢 16px span 其實所有的 I/O 都是輪詢的方法,只不過實現的層面不同罷了. 其中 tornado 使用的就是 epoll 的. selec,poll 和 epoll 區別總結 基本上 select 有 3