Hostapd的功能就是作为AP的认证服务器,负责控制管理stations(通常可以认为带无线网卡的PC)的接入和认证。
通过Hostapd可以将无线网卡切换为AP/Master模式,通过修改配置文件,可以建立一个开放式的(不加密)的,WEP,WPA或WPA2的无线网络。并且通过修改配置文件可以设置无线网卡的各种参数,包括频率,信号,beacon包时间间隔,是否发送beacon包,如果响应探针请求等等。还可以设置mac地址过滤条件等。
hostapd的main函数
int main(int argc, char *argv[])
{
struct hapd_interfaces interfaces;
int ret = 1;
size_t i, j;
int c, debug = 0, daemonize = 0;
char *pid_file = NULL;
const char *log_file = NULL;
const char *entropy_file = NULL;
char **bss_config = NULL, **tmp_bss;
size_t num_bss_configs = 0;
#ifdef CONFIG_DEBUG_LINUX_TRACING
int enable_trace_dbg = 0;
#endif /* CONFIG_DEBUG_LINUX_TRACING */
int start_ifaces_in_sync = 0;
char **if_names = NULL;
size_t if_names_size = 0;
if (os_program_init())
return -1;
os_memset(&interfaces, 0, sizeof(interfaces));
interfaces.reload_config = hostapd_reload_config;
interfaces.config_read_cb = hostapd_config_read;
interfaces.for_each_interface = hostapd_for_each_interface;
interfaces.ctrl_iface_init = hostapd_ctrl_iface_init;
interfaces.ctrl_iface_deinit = hostapd_ctrl_iface_deinit;
interfaces.driver_init = hostapd_driver_init;
interfaces.global_iface_path = NULL;
interfaces.global_iface_name = NULL;
interfaces.global_ctrl_sock = -1;
dl_list_init(&interfaces.global_ctrl_dst);
for (;;) {
c = getopt(argc, argv, "b:Bde:f:hi:KP:STtu:vg:G:");
if (c < 0)
break;
switch (c) {
case 'h':
usage();
break;
case 'd':
debug++;
if (wpa_debug_level > 0)
wpa_debug_level--;
break;
case 'B':
daemonize++;
break;
case 'e':
entropy_file = optarg;
break;
case 'f':
log_file = optarg;
break;
case 'K':
wpa_debug_show_keys++;
break;
case 'P':
os_free(pid_file);
pid_file = os_rel2abs_path(optarg);
break;
case 't':
wpa_debug_timestamp++;
break;
#ifdef CONFIG_DEBUG_LINUX_TRACING
case 'T':
enable_trace_dbg = 1;
break;
#endif /* CONFIG_DEBUG_LINUX_TRACING */
case 'v':
show_version();
exit(1);
break;
case 'g':
if (hostapd_get_global_ctrl_iface(&interfaces, optarg))
return -1;
break;
case 'G':
if (hostapd_get_ctrl_iface_group(&interfaces, optarg))
return -1;
break;
case 'b':
tmp_bss = os_realloc_array(bss_config,
num_bss_configs + 1,
sizeof(char *));
if (tmp_bss == NULL)
goto out;
bss_config = tmp_bss;
bss_config[num_bss_configs++] = optarg;
break;
case 'S':
start_ifaces_in_sync = 1;
break;
#ifdef CONFIG_WPS
case 'u':
return gen_uuid(optarg);
#endif /* CONFIG_WPS */
case 'i':
if (hostapd_get_interface_names(&if_names,
&if_names_size, optarg))
goto out;
break;
default:
usage();
break;
}
}
if (optind == argc && interfaces.global_iface_path == NULL &&
num_bss_configs == 0)
usage();
wpa_msg_register_ifname_cb(hostapd_msg_ifname_cb);
if (log_file)
wpa_debug_open_file(log_file);
else
wpa_debug_setup_stdout();
#ifdef CONFIG_DEBUG_LINUX_TRACING
if (enable_trace_dbg) {
int tret = wpa_debug_open_linux_tracing();
if (tret) {
wpa_printf(MSG_ERROR, "Failed to enable trace logging");
return -1;
}
}
#endif /* CONFIG_DEBUG_LINUX_TRACING */
interfaces.count = argc - optind;
if (interfaces.count || num_bss_configs) {
interfaces.iface = os_calloc(interfaces.count + num_bss_configs,
sizeof(struct hostapd_iface *));
if (interfaces.iface == NULL) {
wpa_printf(MSG_ERROR, "malloc failed");
return -1;
}
}
if (hostapd_global_init(&interfaces, entropy_file)) {
wpa_printf(MSG_ERROR, "Failed to initialize global context");
return -1;
}
eloop_register_timeout(HOSTAPD_CLEANUP_INTERVAL, 0,
hostapd_periodic, &interfaces, NULL);
if (fst_global_init()) {
wpa_printf(MSG_ERROR,
"Failed to initialize global FST context");
goto out;
}
#if defined(CONFIG_FST) && defined(CONFIG_CTRL_IFACE)
if (!fst_global_add_ctrl(fst_ctrl_cli))
wpa_printf(MSG_WARNING, "Failed to add CLI FST ctrl");
#endif /* CONFIG_FST && CONFIG_CTRL_IFACE */
/* Allocate and parse configuration for full interface files */
for (i = 0; i < interfaces.count; i++) {
char *if_name = NULL;
if (i < if_names_size)
if_name = if_names[i];
interfaces.iface[i] = hostapd_interface_init(&interfaces,
if_name,
argv[optind + i],
debug);
if (!interfaces.iface[i]) {
wpa_printf(MSG_ERROR, "Failed to initialize interface");
goto out;
}
if (start_ifaces_in_sync)
interfaces.iface[i]->need_to_start_in_sync = 1;
}
/* Allocate and parse configuration for per-BSS files */
for (i = 0; i < num_bss_configs; i++) {
struct hostapd_iface *iface;
char *fname;
wpa_printf(MSG_INFO, "BSS config: %s", bss_config[i]);
fname = os_strchr(bss_config[i], ':');
if (fname == NULL) {
wpa_printf(MSG_ERROR,
"Invalid BSS config identifier '%s'",
bss_config[i]);
goto out;
}
*fname++ = '\0';
iface = hostapd_interface_init_bss(&interfaces, bss_config[i],
fname, debug);
if (iface == NULL)
goto out;
for (j = 0; j < interfaces.count; j++) {
if (interfaces.iface[j] == iface)
break;
}
if (j == interfaces.count) {
struct hostapd_iface **tmp;
tmp = os_realloc_array(interfaces.iface,
interfaces.count + 1,
sizeof(struct hostapd_iface *));
if (tmp == NULL) {
hostapd_interface_deinit_free(iface);
goto out;
}
interfaces.iface = tmp;
interfaces.iface[interfaces.count++] = iface;
}
}
/*
* Enable configured interfaces. Depending on channel configuration,
* this may complete full initialization before returning or use a
* callback mechanism to complete setup in case of operations like HT
* co-ex scans, ACS, or DFS are needed to determine channel parameters.
* In such case, the interface will be enabled from eloop context within
* hostapd_global_run().
*/
interfaces.terminate_on_error = interfaces.count;
for (i = 0; i < interfaces.count; i++) {
//根据配置文件设置iface信息
if (hostapd_driver_init(interfaces.iface[i]) ||
hostapd_setup_interface(interfaces.iface[i])) //将配置文件通过写入驱动
goto out;
}
hostapd_global_ctrl_iface_init(&interfaces);
if (hostapd_global_run(&interfaces, daemonize, pid_file)) {
wpa_printf(MSG_ERROR, "Failed to start eloop");
goto out;
}
ret = 0;
out:
hostapd_global_ctrl_iface_deinit(&interfaces);
/* Deinitialize all interfaces */
for (i = 0; i < interfaces.count; i++) {
if (!interfaces.iface[i])
continue;
interfaces.iface[i]->driver_ap_teardown =
!!(interfaces.iface[i]->drv_flags &
WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT);
hostapd_interface_deinit_free(interfaces.iface[i]);
}
os_free(interfaces.iface);
if (interfaces.eloop_initialized)
eloop_cancel_timeout(hostapd_periodic, &interfaces, NULL);
hostapd_global_deinit(pid_file, interfaces.eloop_initialized);
os_free(pid_file);
if (log_file)
wpa_debug_close_file();
wpa_debug_close_linux_tracing();
os_free(bss_config);
for (i = 0; i < if_names_size; i++)
os_free(if_names[i]);
os_free(if_names);
fst_global_deinit();
os_program_deinit();
return ret;
}
该函数主要分为三部分:
第一部是读取命令行参数作相应的处理。
第二部分主要是根据配置文件设置hapd_interface
的参数然后通过一系列的函数调用进入内核态设置相应的内核参数
第三部分是在函数hostapd_global_run
中死循环来检测socket或者timeout或者event的相关量是否发生变化进而调用相应的提前注册到该事件上的函数。
在函数hostapd_global_init
中初始化eloop这个全局变量并进入eloop死循环中:
static int hostapd_global_init(struct hapd_interfaces *interfaces,
const char *entropy_file)
{
int i;
os_memset(&global, 0, sizeof(global)); //重置global变量
hostapd_logger_register_cb(hostapd_logger_cb);
if (eap_server_register_methods()) { //注册eap server的加密方法
wpa_printf(MSG_ERROR, "Failed to register EAP methods");
return -1;
}
if (eloop_init()) {
wpa_printf(MSG_ERROR, "Failed to initialize event loop");
return -1;
}
interfaces->eloop_initialized = 1;
random_init(entropy_file);
#ifndef CONFIG_NATIVE_WINDOWS
eloop_register_signal(SIGHUP, handle_reload, interfaces);
eloop_register_signal(SIGUSR1, handle_dump_state, interfaces);
#endif /* CONFIG_NATIVE_WINDOWS */
eloop_register_signal_terminate(handle_term, interfaces);
#ifndef CONFIG_NATIVE_WINDOWS
openlog("hostapd", 0, LOG_DAEMON);
#endif /* CONFIG_NATIVE_WINDOWS */
for (i = 0; wpa_drivers[i]; i++)
global.drv_count++;
if (global.drv_count == 0) {
wpa_printf(MSG_ERROR, "No drivers enabled");
return -1;
}
global.drv_priv = os_calloc(global.drv_count, sizeof(void *));
if (global.drv_priv == NULL)
return -1;
return 0;
}
使用eap_server_register_methods
函数注册eap server支持的安全模式,并存放在一个链表里面。
调用 eloop_init
函数初始化全局变量eloop结构体。
调用 random_init
对各个事件注册,
void random_init(const char *entropy_file)
{
os_free(random_entropy_file);
if (entropy_file)
random_entropy_file = os_strdup(entropy_file);
else
random_entropy_file = NULL;
random_read_entropy();
#ifdef __linux__
if (random_fd >= 0)
return;
random_fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
if (random_fd < 0) {
wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s",
strerror(errno));
return;
}
wpa_printf(MSG_DEBUG, "random: Trying to read entropy from "
"/dev/random");
eloop_register_read_sock(random_fd, random_read_fd, NULL, NULL);
#endif /* __linux__ */
random_write_entropy();
}
eloop_register_read_sock
函数继续调用,eloop_register_read_sock
,再使用eloop_register_sock
函数来实现注册socket。
int eloop_register_sock(int sock, eloop_event_type type,
eloop_sock_handler handler,
void *eloop_data, void *user_data)
{
struct eloop_sock_table *table;
assert(sock >= 0);
table = eloop_get_sock_table(type);
return eloop_sock_table_add_sock(table, sock, handler,
eloop_data, user_data);
}
从代码中可以看到该函数将相应的handler和data放进sock_table表中,实现注册。
回到main函数中,hostapd_interface_init
读取hostapd配置文件并进行分配和解析:
/**
* hostapd_interface_init - Read configuration file and init BSS data
*
* This function is used to parse configuration file for a full interface (one
* or more BSSes sharing the same radio) and allocate memory for the BSS
* interfaces. No actiual driver operations are started.
*/
static struct hostapd_iface *
hostapd_interface_init(struct hapd_interfaces *interfaces, const char *if_name,
const char *config_fname, int debug)
{
struct hostapd_iface *iface;
int k;
wpa_printf(MSG_ERROR, "Configuration file: %s", config_fname);
iface = hostapd_init(interfaces, config_fname);
if (!iface)
return NULL;
if (if_name) {
os_strlcpy(iface->conf->bss[0]->iface, if_name,
sizeof(iface->conf->bss[0]->iface));
}
iface->interfaces = interfaces;
for (k = 0; k < debug; k++) {
if (iface->bss[0]->conf->logger_stdout_level > 0)
iface->bss[0]->conf->logger_stdout_level--;
}
if (iface->conf->bss[0]->iface[0] == '\0' &&
!hostapd_drv_none(iface->bss[0])) {
wpa_printf(MSG_ERROR,
"Interface name not specified in %s, nor by '-i' parameter",
config_fname);
hostapd_interface_deinit_free(iface);
return NULL;
}
return iface;
}
其中调用hostapd_init()
初始化配置:
/**
* hostapd_init - Allocate and initialize per-interface data
* @config_file: Path to the configuration file
* Returns: Pointer to the allocated interface data or %NULL on failure
*
* This function is used to allocate main data structures for per-interface
* data. The allocated data buffer will be freed by calling
* hostapd_cleanup_iface().
*/
struct hostapd_iface * hostapd_init(struct hapd_interfaces *interfaces,
const char *config_file)
{
struct hostapd_iface *hapd_iface = NULL;
struct hostapd_config *conf = NULL;
struct hostapd_data *hapd;
size_t i;
hapd_iface = hostapd_alloc_iface();
if (hapd_iface == NULL)
goto fail;
hapd_iface->config_fname = os_strdup(config_file);
if (hapd_iface->config_fname == NULL)
goto fail;
conf = interfaces->config_read_cb(hapd_iface->config_fname);//读取配置文件
if (conf == NULL)
goto fail;
hapd_iface->conf = conf;
hapd_iface->num_bss = conf->num_bss;
hapd_iface->bss = os_calloc(conf->num_bss,
sizeof(struct hostapd_data *));
if (hapd_iface->bss == NULL)
goto fail;
//根据配置文件中bss的配置个数conf->num_bss的值通过调用hostapd_alloc_bss_data分配空间及相关设置
for (i = 0; i < conf->num_bss; i++) {
hapd = hapd_iface->bss[i] =
hostapd_alloc_bss_data(hapd_iface, conf,
conf->bss[i]);
if (hapd == NULL)
goto fail;
hapd->msg_ctx = hapd;
}
return hapd_iface;
fail:
wpa_printf(MSG_ERROR, "Failed to set up interface with %s",
config_file);
if (conf)
hostapd_config_free(conf);
if (hapd_iface) {
os_free(hapd_iface->config_fname);
os_free(hapd_iface->bss);
wpa_printf(MSG_DEBUG, "%s: free iface %p",
__func__, hapd_iface);
os_free(hapd_iface);
}
return NULL;
}
通过调用函数hostapd_driver_init
获取配置信息保存在iface[i]
中,然后通过调用函数hostapd_setup_interface
函数将其配置信息写入内核。写入通过依次调用一下函数来实现:
hostapd_setup_interface
------>setup_interface
------>hostapd_set_country
------>setup_interface2
------>hostapd_setup_interface_complete
------>hostapd_set_freq
------>hostapd_set_rts
------>hostapd_set_state
------>hostapd_tx_queue_para,
通过这几个调用进入netlink层。
通过 hostapd_global_run
处理socket事件的过程如下
static int hostapd_global_run(struct hapd_interfaces *ifaces, int daemonize,
const char *pid_file)
{
#ifdef EAP_SERVER_TNC
int tnc = 0;
size_t i, k;
for (i = 0; !tnc && i < ifaces->count; i++) {
for (k = 0; k < ifaces->iface[i]->num_bss; k++) {
if (ifaces->iface[i]->bss[0]->conf->tnc) {
tnc++;
break;
}
}
}
if (tnc && tncs_global_init() < 0) {//调用tncs_global_init完成tnc相关的初始化
wpa_printf(MSG_ERROR, "Failed to initialize TNCS");
return -1;
}
#endif /* EAP_SERVER_TNC */
if (daemonize) {
if (os_daemonize(pid_file)) {//调用os_daemonize函数实现将该程序以后台进程运行
wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
return -1;
}
if (eloop_sock_requeue()) {
wpa_printf(MSG_ERROR, "eloop_sock_requeue: %s",
strerror(errno));
return -1;
}
}
eloop_run();
return 0;
}
其中核心函数是eloop_run
:
void eloop_run(void)
{
#ifdef CONFIG_ELOOP_POLL
int num_poll_fds;
int timeout_ms = 0;
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT
fd_set *rfds, *wfds, *efds;
struct timeval _tv;
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
int timeout_ms = -1;
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUE
struct timespec ts;
#endif /* CONFIG_ELOOP_KQUEUE */
int res;
struct os_reltime tv, now;
#ifdef CONFIG_ELOOP_SELECT
//为三个文件描述符集申请空间
rfds = os_malloc(sizeof(*rfds));
wfds = os_malloc(sizeof(*wfds));
efds = os_malloc(sizeof(*efds));
if (rfds == NULL || wfds == NULL || efds == NULL)
goto out;
#endif /* CONFIG_ELOOP_SELECT */
while (!eloop.terminate &&
(!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
struct eloop_timeout *timeout;
if (eloop.pending_terminate) {
/*
* This may happen in some corner cases where a signal
* is received during a blocking operation. We need to
* process the pending signals and exit if requested to
* avoid hitting the SIGALRM limit if the blocking
* operation took more than two seconds.
*/
// eloop_process_pending_signals函数对发生的信号进行处理
eloop_process_pending_signals();
if (eloop.terminate)
break;
}
timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
list);
if (timeout) {
//对超时时间进行设置timeout,主要是为下面调用的select函数会用到超时时间做准备
os_get_reltime(&now);
if (os_reltime_before(&now, &timeout->time))
os_reltime_sub(&timeout->time, &now, &tv);
else
tv.sec = tv.usec = 0;
#if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL)
timeout_ms = tv.sec * 1000 + tv.usec / 1000;
#endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */
#ifdef CONFIG_ELOOP_SELECT
_tv.tv_sec = tv.sec;
_tv.tv_usec = tv.usec;
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_KQUEUE
ts.tv_sec = tv.sec;
ts.tv_nsec = tv.usec * 1000L;
#endif /* CONFIG_ELOOP_KQUEUE */
}
#ifdef CONFIG_ELOOP_POLL
num_poll_fds = eloop_sock_table_set_fds(
&eloop.readers, &eloop.writers, &eloop.exceptions,
eloop.pollfds, eloop.pollfds_map,
eloop.max_pollfd_map);
res = poll(eloop.pollfds, num_poll_fds,
timeout ? timeout_ms : -1);
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT
//将申请的文件描述符集与eloop对象相结合,并调用select函数对这些文件发生异常进行监听
eloop_sock_table_set_fds(&eloop.readers, rfds);
eloop_sock_table_set_fds(&eloop.writers, wfds);
eloop_sock_table_set_fds(&eloop.exceptions, efds);
res = select(eloop.max_sock + 1, rfds, wfds, efds,
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 */
#ifdef CONFIG_ELOOP_KQUEUE
if (eloop.count == 0) {
res = 0;
} else {
res = kevent(eloop.kqueuefd, NULL, 0,
eloop.kqueue_events, eloop.kqueue_nevents,
timeout ? &ts : NULL);
}
#endif /* CONFIG_ELOOP_KQUEUE */
if (res < 0 && errno != EINTR && errno != 0) {
wpa_printf(MSG_ERROR, "eloop: %s: %s",
#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 */
#ifdef CONFIG_ELOOP_KQUEUE
"kqueue"
#endif /* CONFIG_ELOOP_EKQUEUE */
, strerror(errno));
goto out;
}
eloop.readers.changed = 0;
eloop.writers.changed = 0;
eloop.exceptions.changed = 0;
eloop_process_pending_signals();
/* check if some registered timeouts have occurred */
timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
list);
if (timeout) {
os_get_reltime(&now);
if (!os_reltime_before(&now, &timeout->time)) {
void *eloop_data = timeout->eloop_data;
void *user_data = timeout->user_data;
eloop_timeout_handler handler =
timeout->handler;
eloop_remove_timeout(timeout);
handler(eloop_data, user_data);
}
}
if (res <= 0)
continue;
if (eloop.readers.changed ||
eloop.writers.changed ||
eloop.exceptions.changed) {
/*
* Sockets may have been closed and reopened with the
* same FD in the signal or timeout handlers, so we
* must skip the previous results and check again
* whether any of the currently registered sockets have
* events.
*/
continue;
}
#ifdef CONFIG_ELOOP_POLL
eloop_sock_table_dispatch(&eloop.readers, &eloop.writers,
&eloop.exceptions, eloop.pollfds_map,
eloop.max_pollfd_map);
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT
eloop_sock_table_dispatch(&eloop.readers, rfds);
eloop_sock_table_dispatch(&eloop.writers, wfds);
eloop_sock_table_dispatch(&eloop.exceptions, efds);
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL
eloop_sock_table_dispatch(eloop.epoll_events, res);
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUE
eloop_sock_table_dispatch(eloop.kqueue_events, res);
#endif /* CONFIG_ELOOP_KQUEUE */
}
eloop.terminate = 0;
out:
#ifdef CONFIG_ELOOP_SELECT
os_free(rfds);
os_free(wfds);
os_free(efds);
#endif /* CONFIG_ELOOP_SELECT */
return;
}
eloop_process_pending_signals
函数对发生的信号进行处理
static void eloop_process_pending_signals(void)
{
int i;
if (eloop.signaled == 0)//有没有信号产生,如果有,那么这个标志位将为1,说明有信号需要处理,如果为0,那么没有信号要处理,函数返回
return;
eloop.signaled = 0;//将信号标示为置0,以便下次有信号产生时,置1
if (eloop.pending_terminate) {
//如果不用处理后面将会产生的信号,则立即向进程发送一个SIGALARM信号,然后将这个标志置0
#ifndef CONFIG_NATIVE_WINDOWS
alarm(0);
#endif /* CONFIG_NATIVE_WINDOWS */
eloop.pending_terminate = 0;
}
for (i = 0; i < eloop.signal_count; i++) { //对信号标示进行处理
if (eloop.signals[i].signaled) {
eloop.signals[i].signaled = 0;
//调用处理函数对相应的信号进行处理
eloop.signals[i].handler(eloop.signals[i].sig,
eloop.signals[i].user_data);
}
}
}
eloop_run
函数的核心将要处理的socket事件添加到相应的表中,如下:
eloop_sock_table_set_fds(&eloop.readers, rfds);
eloop_sock_table_set_fds(&eloop.writers, wfds);
eloop_sock_table_set_fds(&eloop.exceptions, efds);
res = select(eloop.max_sock + 1, rfds, wfds, efds,
timeout ? &_tv : NULL);
最后执行相应的提前注册的函数:
eloop_sock_table_dispatch(&eloop.readers, rfds);
eloop_sock_table_dispatch(&eloop.writers, wfds);
eloop_sock_table_dispatch(&eloop.exceptions, efds);