eloop: Add epoll option for better performance

This patch adds epoll option for the eloop implementation. This can be
selected with the CONFIG_ELOOP_EPOLL=y build option.

[merit]
See Table1.

Table1. comparison table
+--------+--------+-----------+------------+-------------+
|        | add fd | remove fd | prepare fd | dispatch fd |
+--------+--------+-----------+------------+-------------+
| select | O(1)   | O(1)      | O(N)       | O(N)        |
+--------+--------+-----------+------------+-------------+
| poll   | O(1)   | O(1)      | O(N)       | O(N)        |
+--------+--------+-----------+------------+-------------+
| epoll  | O(1)   | O(1)      | 0          | O(M)        |
+--------+--------+-----------+------------+-------------+
"add fd" is addition of fd by eloop_sock_table_add_sock().
"remove fd" is removal of fd by eloop_sock_table_remove_sock().
"prepare fd" is preparation of fds before wait in eloop_run().
"dispatch fd" is dispatchment of fds by eloop_sock_table_dispatch().
"N" is all watching fds.
"M" is fds which could be dispatched after waiting.

As shown in Table1, epoll option has better performance on "prepare fd" column.
Because select/poll option requires setting fds before every select()/poll().
But epoll_wait() doesn't need it.

And epoll option has also better performance on "dispatch fd" column.
Because select/poll option needs to check all registered fds to find out
dispatchable fds. But epoll option doesn't require checking all registered fds.
Because epoll_wait() returns dispatchable fd set.

So epoll option is effective for GO/AP functionality.

[demerit]
The epoll option requires additional heap memory. In case of P2P GO, it is
about 8K bytes.

Signed-off-by: Masashi Honma <masashi.honma@gmail.com>
This commit is contained in:
Masashi Honma 2014-05-13 09:35:48 +09:00 committed by Jouni Malinen
parent da96a6f793
commit f0356ec85c
5 changed files with 174 additions and 18 deletions

View file

@ -14,14 +14,21 @@
#include "list.h" #include "list.h"
#include "eloop.h" #include "eloop.h"
#ifndef CONFIG_ELOOP_POLL #if defined(CONFIG_ELOOP_POLL) && defined(CONFIG_ELOOP_EPOLL)
#error Do not define both of poll and epoll
#endif
#if !defined(CONFIG_ELOOP_POLL) && !defined(CONFIG_ELOOP_EPOLL)
#define CONFIG_ELOOP_SELECT #define CONFIG_ELOOP_SELECT
#endif /* CONFIG_ELOOP_POLL */ #endif
#ifdef CONFIG_ELOOP_POLL #ifdef CONFIG_ELOOP_POLL
#include <poll.h> #include <poll.h>
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_EPOLL
#include <sys/epoll.h>
#endif /* CONFIG_ELOOP_EPOLL */
struct eloop_sock { struct eloop_sock {
int sock; int sock;
@ -54,7 +61,11 @@ struct eloop_signal {
struct eloop_sock_table { struct eloop_sock_table {
int count; int count;
struct eloop_sock *table; struct eloop_sock *table;
#ifdef CONFIG_ELOOP_EPOLL
eloop_event_type type;
#else /* CONFIG_ELOOP_EPOLL */
int changed; int changed;
#endif /* CONFIG_ELOOP_EPOLL */
}; };
struct eloop_data { struct eloop_data {
@ -67,6 +78,13 @@ struct eloop_data {
struct pollfd *pollfds; struct pollfd *pollfds;
struct pollfd **pollfds_map; struct pollfd **pollfds_map;
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_EPOLL
int epollfd;
int epoll_max_event_num;
int epoll_max_fd;
struct eloop_sock *epoll_table;
struct epoll_event *epoll_events;
#endif /* CONFIG_ELOOP_EPOLL */
struct eloop_sock_table readers; struct eloop_sock_table readers;
struct eloop_sock_table writers; struct eloop_sock_table writers;
struct eloop_sock_table exceptions; struct eloop_sock_table exceptions;
@ -79,7 +97,6 @@ struct eloop_data {
int pending_terminate; int pending_terminate;
int terminate; int terminate;
int reader_table_changed;
}; };
static struct eloop_data eloop; static struct eloop_data eloop;
@ -132,6 +149,17 @@ int eloop_init(void)
{ {
os_memset(&eloop, 0, sizeof(eloop)); os_memset(&eloop, 0, sizeof(eloop));
dl_list_init(&eloop.timeout); dl_list_init(&eloop.timeout);
#ifdef CONFIG_ELOOP_EPOLL
eloop.epollfd = epoll_create1(0);
if (eloop.epollfd < 0) {
wpa_printf(MSG_ERROR, "%s: epoll_create1 failed. %s\n",
__func__, strerror(errno));
return -1;
}
eloop.readers.type = EVENT_TYPE_READ;
eloop.writers.type = EVENT_TYPE_WRITE;
eloop.exceptions.type = EVENT_TYPE_EXCEPTION;
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef WPA_TRACE #ifdef WPA_TRACE
signal(SIGSEGV, eloop_sigsegv_handler); signal(SIGSEGV, eloop_sigsegv_handler);
#endif /* WPA_TRACE */ #endif /* WPA_TRACE */
@ -143,6 +171,11 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
int sock, eloop_sock_handler handler, int sock, eloop_sock_handler handler,
void *eloop_data, void *user_data) void *eloop_data, void *user_data)
{ {
#ifdef CONFIG_ELOOP_EPOLL
struct eloop_sock *temp_table;
struct epoll_event ev, *temp_events;
int next;
#endif /* CONFIG_ELOOP_EPOLL */
struct eloop_sock *tmp; struct eloop_sock *tmp;
int new_max_sock; int new_max_sock;
@ -178,6 +211,33 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
eloop.pollfds = n; eloop.pollfds = n;
} }
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_EPOLL
if (new_max_sock >= eloop.epoll_max_fd) {
next = eloop.epoll_max_fd == 0 ? 16 : eloop.epoll_max_fd * 2;
temp_table = os_realloc_array(eloop.epoll_table, next,
sizeof(struct eloop_sock));
if (temp_table == NULL)
return -1;
eloop.epoll_max_fd = next;
eloop.epoll_table = temp_table;
}
if (eloop.count + 1 > eloop.epoll_max_event_num) {
next = eloop.epoll_max_event_num == 0 ? 8 :
eloop.epoll_max_event_num * 2;
temp_events = os_realloc_array(eloop.epoll_events, next,
sizeof(struct epoll_event));
if (temp_events == NULL) {
wpa_printf(MSG_ERROR, "%s: malloc for epoll failed. "
"%s\n", __func__, strerror(errno));
return -1;
}
eloop.epoll_max_event_num = next;
eloop.epoll_events = temp_events;
}
#endif /* CONFIG_ELOOP_EPOLL */
eloop_trace_sock_remove_ref(table); eloop_trace_sock_remove_ref(table);
tmp = os_realloc_array(table->table, table->count + 1, tmp = os_realloc_array(table->table, table->count + 1,
@ -194,9 +254,38 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
table->table = tmp; table->table = tmp;
eloop.max_sock = new_max_sock; eloop.max_sock = new_max_sock;
eloop.count++; eloop.count++;
#ifndef CONFIG_ELOOP_EPOLL
table->changed = 1; table->changed = 1;
#endif /* CONFIG_ELOOP_EPOLL */
eloop_trace_sock_add_ref(table); eloop_trace_sock_add_ref(table);
#ifdef CONFIG_ELOOP_EPOLL
os_memset(&ev, 0, sizeof(ev));
switch (table->type) {
case EVENT_TYPE_READ:
ev.events = EPOLLIN;
break;
case EVENT_TYPE_WRITE:
ev.events = EPOLLOUT;
break;
/*
* Exceptions are always checked when using epoll, but I suppose it's
* possible that someone registered a socket *only* for exception
* handling.
*/
case EVENT_TYPE_EXCEPTION:
ev.events = EPOLLERR | EPOLLHUP;
break;
}
ev.data.fd = sock;
if (epoll_ctl(eloop.epollfd, EPOLL_CTL_ADD, sock, &ev) < 0) {
wpa_printf(MSG_ERROR, "%s: epoll_ctl(ADD) for fd=%d "
"failed. %s\n", __func__, sock, strerror(errno));
return -1;
}
os_memcpy(&eloop.epoll_table[sock], &table->table[table->count - 1],
sizeof(struct eloop_sock));
#endif /* CONFIG_ELOOP_EPOLL */
return 0; return 0;
} }
@ -223,8 +312,18 @@ static void eloop_sock_table_remove_sock(struct eloop_sock_table *table,
} }
table->count--; table->count--;
eloop.count--; eloop.count--;
#ifndef CONFIG_ELOOP_EPOLL
table->changed = 1; table->changed = 1;
#endif /* CONFIG_ELOOP_EPOLL */
eloop_trace_sock_add_ref(table); eloop_trace_sock_add_ref(table);
#ifdef CONFIG_ELOOP_EPOLL
if (epoll_ctl(eloop.epollfd, EPOLL_CTL_DEL, sock, NULL) < 0) {
wpa_printf(MSG_ERROR, "%s: epoll_ctl(DEL) for fd=%d "
"failed. %s\n", __func__, sock, strerror(errno));
return;
}
os_memset(&eloop.epoll_table[sock], 0, sizeof(struct eloop_sock));
#endif /* CONFIG_ELOOP_EPOLL */
} }
@ -410,6 +509,23 @@ static void eloop_sock_table_dispatch(struct eloop_sock_table *table,
#endif /* CONFIG_ELOOP_SELECT */ #endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
static void eloop_sock_table_dispatch(struct epoll_event *events, int nfds)
{
struct eloop_sock *table;
int i;
for (i = 0; i < nfds; i++) {
table = &eloop.epoll_table[events[i].data.fd];
if (table->handler == NULL)
continue;
table->handler(table->sock, table->eloop_data,
table->user_data);
}
}
#endif /* CONFIG_ELOOP_EPOLL */
static void eloop_sock_table_destroy(struct eloop_sock_table *table) static void eloop_sock_table_destroy(struct eloop_sock_table *table)
{ {
if (table) { if (table) {
@ -787,6 +903,9 @@ void eloop_run(void)
fd_set *rfds, *wfds, *efds; fd_set *rfds, *wfds, *efds;
struct timeval _tv; struct timeval _tv;
#endif /* CONFIG_ELOOP_SELECT */ #endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
int timeout_ms = -1;
#endif /* CONFIG_ELOOP_EPOLL */
int res; int res;
struct os_reltime tv, now; struct os_reltime tv, now;
@ -810,9 +929,9 @@ void eloop_run(void)
os_reltime_sub(&timeout->time, &now, &tv); os_reltime_sub(&timeout->time, &now, &tv);
else else
tv.sec = tv.usec = 0; tv.sec = tv.usec = 0;
#ifdef CONFIG_ELOOP_POLL #if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL)
timeout_ms = tv.sec * 1000 + tv.usec / 1000; timeout_ms = tv.sec * 1000 + tv.usec / 1000;
#endif /* CONFIG_ELOOP_POLL */ #endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */
#ifdef CONFIG_ELOOP_SELECT #ifdef CONFIG_ELOOP_SELECT
_tv.tv_sec = tv.sec; _tv.tv_sec = tv.sec;
_tv.tv_usec = tv.usec; _tv.tv_usec = tv.usec;
@ -826,12 +945,6 @@ void eloop_run(void)
eloop.max_pollfd_map); eloop.max_pollfd_map);
res = poll(eloop.pollfds, num_poll_fds, res = poll(eloop.pollfds, num_poll_fds,
timeout ? timeout_ms : -1); timeout ? timeout_ms : -1);
if (res < 0 && errno != EINTR && errno != 0) {
wpa_printf(MSG_INFO, "eloop: poll: %s",
strerror(errno));
goto out;
}
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT #ifdef CONFIG_ELOOP_SELECT
eloop_sock_table_set_fds(&eloop.readers, rfds); eloop_sock_table_set_fds(&eloop.readers, rfds);
@ -839,12 +952,29 @@ void eloop_run(void)
eloop_sock_table_set_fds(&eloop.exceptions, efds); eloop_sock_table_set_fds(&eloop.exceptions, efds);
res = select(eloop.max_sock + 1, rfds, wfds, efds, res = select(eloop.max_sock + 1, rfds, wfds, efds,
timeout ? &_tv : NULL); timeout ? &_tv : NULL);
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
if (eloop.count == 0) {
res = 0;
} else {
res = epoll_wait(eloop.epollfd, eloop.epoll_events,
eloop.count, timeout_ms);
}
#endif /* CONFIG_ELOOP_EPOLL */
if (res < 0 && errno != EINTR && errno != 0) { if (res < 0 && errno != EINTR && errno != 0) {
wpa_printf(MSG_INFO, "eloop: select: %s", wpa_printf(MSG_ERROR, "eloop: %s: %s",
strerror(errno)); #ifdef CONFIG_ELOOP_POLL
"poll"
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT
"select"
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
"epoll"
#endif /* CONFIG_ELOOP_EPOLL */
, strerror(errno));
goto out; goto out;
} }
#endif /* CONFIG_ELOOP_SELECT */
eloop_process_pending_signals(); eloop_process_pending_signals();
/* check if some registered timeouts have occurred */ /* check if some registered timeouts have occurred */
@ -876,6 +1006,9 @@ void eloop_run(void)
eloop_sock_table_dispatch(&eloop.writers, wfds); eloop_sock_table_dispatch(&eloop.writers, wfds);
eloop_sock_table_dispatch(&eloop.exceptions, efds); eloop_sock_table_dispatch(&eloop.exceptions, efds);
#endif /* CONFIG_ELOOP_SELECT */ #endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
eloop_sock_table_dispatch(eloop.epoll_events, res);
#endif /* CONFIG_ELOOP_EPOLL */
} }
eloop.terminate = 0; eloop.terminate = 0;
@ -928,6 +1061,11 @@ void eloop_destroy(void)
os_free(eloop.pollfds); os_free(eloop.pollfds);
os_free(eloop.pollfds_map); os_free(eloop.pollfds_map);
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_EPOLL
os_free(eloop.epoll_table);
os_free(eloop.epoll_events);
close(eloop.epollfd);
#endif /* CONFIG_ELOOP_EPOLL */
} }
@ -951,7 +1089,12 @@ void eloop_wait_for_read_sock(int sock)
poll(&pfd, 1, -1); poll(&pfd, 1, -1);
#endif /* CONFIG_ELOOP_POLL */ #endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT #if defined(CONFIG_ELOOP_SELECT) || defined(CONFIG_ELOOP_EPOLL)
/*
* We can use epoll() here. But epoll() requres 4 system calls.
* epoll_create1(), epoll_ctl() for ADD, epoll_wait, and close() for
* epoll fd. So select() is better for performance here.
*/
fd_set rfds; fd_set rfds;
if (sock < 0) if (sock < 0)
@ -960,7 +1103,7 @@ void eloop_wait_for_read_sock(int sock)
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(sock, &rfds); FD_SET(sock, &rfds);
select(sock + 1, &rfds, NULL, NULL, NULL); select(sock + 1, &rfds, NULL, NULL, NULL);
#endif /* CONFIG_ELOOP_SELECT */ #endif /* defined(CONFIG_ELOOP_SELECT) || defined(CONFIG_ELOOP_EPOLL) */
} }
#ifdef CONFIG_ELOOP_SELECT #ifdef CONFIG_ELOOP_SELECT

View file

@ -140,6 +140,10 @@ ifdef CONFIG_ELOOP_POLL
L_CFLAGS += -DCONFIG_ELOOP_POLL L_CFLAGS += -DCONFIG_ELOOP_POLL
endif endif
ifdef CONFIG_ELOOP_EPOLL
L_CFLAGS += -DCONFIG_ELOOP_EPOLL
endif
ifdef CONFIG_EAPOL_TEST ifdef CONFIG_EAPOL_TEST
L_CFLAGS += -Werror -DEAPOL_TEST L_CFLAGS += -Werror -DEAPOL_TEST
endif endif

View file

@ -138,6 +138,9 @@ ifdef CONFIG_ELOOP_POLL
CFLAGS += -DCONFIG_ELOOP_POLL CFLAGS += -DCONFIG_ELOOP_POLL
endif endif
ifdef CONFIG_ELOOP_EPOLL
CFLAGS += -DCONFIG_ELOOP_EPOLL
endif
ifdef CONFIG_EAPOL_TEST ifdef CONFIG_EAPOL_TEST
CFLAGS += -Werror -DEAPOL_TEST CFLAGS += -Werror -DEAPOL_TEST

View file

@ -237,7 +237,7 @@ CONFIG_BACKEND=file
# main_none = Very basic example (development use only) # main_none = Very basic example (development use only)
#CONFIG_MAIN=main #CONFIG_MAIN=main
# Select wrapper for operatins system and C library specific functions # Select wrapper for operating system and C library specific functions
# unix = UNIX/POSIX like systems (default) # unix = UNIX/POSIX like systems (default)
# win32 = Windows systems # win32 = Windows systems
# none = Empty template # none = Empty template
@ -251,6 +251,9 @@ CONFIG_ELOOP=eloop
# Should we use poll instead of select? Select is used by default. # Should we use poll instead of select? Select is used by default.
#CONFIG_ELOOP_POLL=y #CONFIG_ELOOP_POLL=y
# Should we use epoll instead of select? Select is used by default.
#CONFIG_ELOOP_EPOLL=y
# Select layer 2 packet implementation # Select layer 2 packet implementation
# linux = Linux packet socket (default) # linux = Linux packet socket (default)
# pcap = libpcap/libdnet/WinPcap # pcap = libpcap/libdnet/WinPcap

View file

@ -253,7 +253,7 @@ CONFIG_BACKEND=file
# main_none = Very basic example (development use only) # main_none = Very basic example (development use only)
#CONFIG_MAIN=main #CONFIG_MAIN=main
# Select wrapper for operatins system and C library specific functions # Select wrapper for operating system and C library specific functions
# unix = UNIX/POSIX like systems (default) # unix = UNIX/POSIX like systems (default)
# win32 = Windows systems # win32 = Windows systems
# none = Empty template # none = Empty template
@ -267,6 +267,9 @@ CONFIG_BACKEND=file
# Should we use poll instead of select? Select is used by default. # Should we use poll instead of select? Select is used by default.
#CONFIG_ELOOP_POLL=y #CONFIG_ELOOP_POLL=y
# Should we use epoll instead of select? Select is used by default.
#CONFIG_ELOOP_EPOLL=y
# Select layer 2 packet implementation # Select layer 2 packet implementation
# linux = Linux packet socket (default) # linux = Linux packet socket (default)
# pcap = libpcap/libdnet/WinPcap # pcap = libpcap/libdnet/WinPcap