- 遇到一个使用lwip协议栈中的select函数的问题。
- 在使用lwip中的select时发现,当socket的负荷上升以后,select经常会等到timeout时间之后才会返回socket可用。那究竟是怎么一回事呢?我们先简单了解几个概念。
1.lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈
2.select函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
int lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout)
{
u32_t waitres = 0;
int nready;
fd_set lreadset, lwriteset, lexceptset;
u32_t msectimeout;
struct lwip_select_cb select_cb;
err_t err;
int i;
SYS_ARCH_DECL_PROTECT(lev);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select(%d, %p, %p, %p, tvsec=%"S32_F" tvusec=%"S32_F")\n",
maxfdp1, (void *)readset, (void *) writeset, (void *) exceptset,
timeout ? (s32_t)timeout->tv_sec : (s32_t)-1,
timeout ? (s32_t)timeout->tv_usec : (s32_t)-1));
/* Go through each socket in each list to count number of sockets which
currently match */
nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset); //根据每个socket发生的事件更新文件描述符
/* If we don't have any current events, then suspend if we are supposed to */
if (!nready)
{
if (timeout && timeout->tv_sec == 0 && timeout->tv_usec == 0)
{
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: no timeout, returning 0\n"));
/* This is OK as the local fdsets are empty and nready is zero,
or we would have returned earlier. */
goto return_copy_fdsets;
}
/* None ready: add our semaphore to list:
We don't actually need any dynamic memory. Our entry on the
list is only valid while we are in this function, so it's ok
to use local variables. */
select_cb.next = NULL;
select_cb.prev = NULL;
select_cb.readset = readset;
select_cb.writeset = writeset;
select_cb.exceptset = exceptset;
select_cb.sem_signalled = 0;
err = sys_sem_new(&select_cb.sem, 0);
if (err != ERR_OK)
{
/* failed to create semaphore */
set_errno(ENOMEM);
return -1;
}
/* Protect the select_cb_list */
SYS_ARCH_PROTECT(lev);
/* Put this select_cb on top of list */
select_cb.next = select_cb_list;
if (select_cb_list != NULL)
{
select_cb_list->prev = &select_cb;
}
select_cb_list = &select_cb;
/* Increasing this counter tells even_callback that the list has changed. */
select_cb_ctr++;
/* Now we can safely unprotect */
SYS_ARCH_UNPROTECT(lev);
/* Increase select_waiting for each socket we are interested in */
for(i = 0; i < maxfdp1; i++) //遍历每个socket
{
if ((readset && FD_ISSET(i, readset)) ||
(writeset && FD_ISSET(i, writeset)) ||
(exceptset && FD_ISSET(i, exceptset)))
{
struct lwip_sock *sock = tryget_socket(i);
LWIP_ASSERT("sock != NULL", sock != NULL);
SYS_ARCH_PROTECT(lev);
sock->select_waiting++;
LWIP_ASSERT("sock->select_waiting > 0", sock->select_waiting > 0);
SYS_ARCH_UNPROTECT(lev);
}
}
/* Call lwip_selscan again: there could have been events between
the last scan (whithout us on the list) and putting us on the list! */
nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset);
if (!nready)
{
/* Still none ready, just wait to be woken */
if (timeout == 0)
{
/* Wait forever */
msectimeout = 0;
}
else
{
msectimeout = ((timeout->tv_sec * 1000) + ((timeout->tv_usec + 500)/1000));
if (msectimeout == 0)
{
/* Wait 1ms at least (0 means wait forever) */
msectimeout = 1;
}
}
waitres = sys_arch_sem_wait(&select_cb.sem, msectimeout);
}
/* Increase select_waiting for each socket we are interested in */
for(i = 0; i < maxfdp1; i++)
{
if ((readset && FD_ISSET(i, readset)) ||
(writeset && FD_ISSET(i, writeset)) ||
(exceptset && FD_ISSET(i, exceptset))) {
struct lwip_sock *sock = tryget_socket(i);
LWIP_ASSERT("sock != NULL", sock != NULL);
SYS_ARCH_PROTECT(lev);
sock->select_waiting--;
LWIP_ASSERT("sock->select_waiting >= 0", sock->select_waiting >= 0);
SYS_ARCH_UNPROTECT(lev);
}
}
/* Take us off the list */
SYS_ARCH_PROTECT(lev);
if (select_cb.next != NULL)
{
select_cb.next->prev = select_cb.prev;
}
if (select_cb_list == &select_cb)
{
LWIP_ASSERT("select_cb.prev == NULL\n", select_cb.prev == NULL);
select_cb_list = select_cb.next;
}
else
{
LWIP_ASSERT("select_cb.prev != NULL\n", select_cb.prev != NULL);
select_cb.prev->next = select_cb.next;
}
/* Increasing this counter tells even_callback that the list has changed. */
select_cb_ctr++;
SYS_ARCH_UNPROTECT(lev);
sys_sem_free(&select_cb.sem);
if (waitres == SYS_ARCH_TIMEOUT)
{
/* Timeout */
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: timeout expired\n"));
/* This is OK as the local fdsets are empty and nready is zero,
or we would have returned earlier. */
goto return_copy_fdsets;
}
/* See what's set */
nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset);
}
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: nready=%d\n", nready));
return_copy_fdsets:
set_errno(0);
if (readset)
{
*readset = lreadset;
}
if (writeset)
{
*writeset = lwriteset;
}
if (exceptset)
{
*exceptset = lexceptset;
}
return nready;
}
可以分析得到lwip协议栈对socket状态的监控只有两次一次是刚开始进入select的时候另一次就是timeout超时的时候。这是一种简化的协议栈。所以需要我们将timeout配置为合适的时间。
内核协议栈
所以在使用select的时候,如果没有指定timeout的话,在执行do_select函数的时候,会主动poll所有监听的fd,如果在第一次poll的时候,没有任何事件发生,那么用户态进程会睡眠,在有新的事件到来的时候,一般在write函数中使用wake_up()来唤醒等待队列上所有的waiter,即执行waiter的唤醒函数(pollwake),睡眠的进程会被唤醒,然后接着执行for循环,接着主动调用poll函数,来查看是否有对应的事件产生。所以可以看出来,select函数是一直在轮询,即使对应fd上产生了事件,将进程环境后,依然要再主动调用poll函数,来查看产生了什么事件。
引用自 : https://blog.csdn.net/sinat_33822516/article/details/113736601