vxWorks Select 接口分析
·
int select (int width, FAST fd_set *pReadFds, FAST fd_set *pWriteFds, fd_set *pExcFds, struct timeval *pTimeOut)
作为异步流数据处理过程中常见的操作接口,主要用来提高CPU的利用率。下来以vxWorks操作系统为例,分析该接口的执行流程。
int select (int width, FAST fd_set *pReadFds, FAST fd_set *pWriteFds, fd_set *pExcFds, struct timeval *pTimeOut)
{
FAST int fd;
_Vx_fd_mask partMask;
SEL_WAKEUP_NODE wakeupNode;
size_t widthInBytes;
int status;
STATUS result;
int tempErrno;
int numFound = 0;
int quitTime = 0;
SEL_CONTEXT *pSelectContext = taskIdCurrent->pSelectContext;
int maxFd = (int)iosFdMaxFiles();
BOOL badfd;
/*获取当前线程 select 上下文,上下文在创建任务时通过父线程所得*/
/*selLibInit 接口实现 usrRoot 线程的上线文,即系统初始的 select 上下文*/
if (pSelectContext == NULL)
{
errno = S_selectLib_NO_SELECT_CONTEXT;
return (ERROR);
}
/*获取当前线程可支持的文件描述符*/
/*建议使用(外部描述符+1),多线程运行下,可能发生描述符不匹配现象*/
if (width == FD_SETSIZE && maxFd < FD_SETSIZE)
width = maxFd;
if (width < 0 || width > maxFd)
{
errno = S_selectLib_WIDTH_OUT_OF_RANGE;
return (ERROR);
}
/*当文件描述符不一致时,需要重新初始 select 上下文*/
/*origxxFds 用于存储已有的文件描述符状态*/
if (width > pSelectContext->maxFd)
{
size_t nBytes = howmany ((size_t) maxFd, NFDBITS) * sizeof (fd_mask);
char *pSavedArea = (char *) pSelectContext->pOrigReadFds;
pSelectContext->maxFd = 0;
pSelectContext->pOrigReadFds = NULL;
free(pSavedArea);
pSavedArea = malloc (3 * nBytes);
if (pSavedArea == NULL)
{
return ERROR;
}
pSelectContext->pOrigReadFds = (fd_set *)pSavedArea;
pSelectContext->pOrigWriteFds = (fd_set *)(pSavedArea + nBytes);
pSelectContext->pOrigExcFds = (fd_set *) (pSavedArea + nBytes + nBytes);
pSelectContext->maxFd = maxFd;
}
widthInBytes = howmany ((size_t)width, NFDBITS) * sizeof (fd_mask);
/*将外部配置的readFds文件描述符集合拷贝到origReadFds文件描述符集合中, 无则清零*/
if (pReadFds != NULL)
bcopy ((char*)pReadFds, (char *)pSelectContext->pOrigReadFds, widthInBytes);
else
bzero ((char *)pSelectContext->pOrigReadFds, widthInBytes);
if (pWriteFds != NULL)
bcopy ((char*)pWriteFds, (char *)pSelectContext->pOrigWriteFds, widthInBytes);
else
bzero ((char *)pSelectContext->pOrigWriteFds, widthInBytes);
if (pExcFds != NULL)
bcopy ((char*)pExcFds, (char *)pSelectContext->pOrigExcFds, widthInBytes);
else
bzero ((char *)pSelectContext->pOrigExcFds, widthInBytes);
if (pTimeOut != NULL)
{
UINT64 ticks;
if (pTimeOut->tv_sec == 0 && pTimeOut->tv_usec == 0)
{
quitTime = NO_WAIT;
}
else
{
TIMEVAL_CONVERT_TO_TICK (ticks, *pTimeOut);
if (ticks > (UINT64) INT_MAX)
{
quitTime = INT_MAX;
}
else
{
quitTime = (int) ticks;
}
}
}
else
{
quitTime = WAIT_FOREVER;
}
pSelectContext->pReadFds = pReadFds;
pSelectContext->pWriteFds = pWriteFds;
pSelectContext->pExcFds = pExcFds;
pSelectContext->badfd = FALSE;
tempErrno = errno;
/*获取信号量,注意该信号量初始状态为不可用,若不释放semGive,则无法获取semTake*/
/*这里直接跳过是否成功获取*/
(void) semTake(&pSelectContext->wakeupSem, NO_WAIT);
/*忽略信号量获取失败*/
errno = tempErrno;
/*创建唤醒节点,该节点作为核心结构,将被驱动接口与系统内核共用*/
/*唤醒节点任务即当前线程*/
wakeupNode.taskId = taskIdCurrent;
wakeupNode.ready = FALSE;
/*清空文件描述符集合*/
if (pReadFds != NULL)
bzero ((char *)pReadFds, widthInBytes);
if (pWriteFds != NULL)
bzero ((char *)pWriteFds, widthInBytes);
if (pExcFds != NULL)
bzero ((char *)pExcFds, widthInBytes);
status = OK;
TASK_SAFE();
/*执行 selDoIoctls 接口,跳转至驱动层执行,主要目的为将唤醒节点添加到唤醒队列中*/
/*注意,此时CMD为FIOSELECT*/
/*尾部参数为TRUE,即文件描述符集合中若出现任一错误,立刻退出*/
if (pReadFds != NULL)
{
wakeupNode.type = SELREAD;
wakeupNode.returnSet = pReadFds;
if (selDoIoctls (pSelectContext->pOrigReadFds, width, FIOSELECT, &wakeupNode, TRUE) != OK)
{
status = ERROR;
}
}
if (status != ERROR && pWriteFds != NULL)
{
wakeupNode.type = SELWRITE;
wakeupNode.returnSet = pWriteFds;
if (selDoIoctls (pSelectContext->pOrigWriteFds, width, FIOSELECT, &wakeupNode, TRUE) != OK)
{
status = ERROR;
}
}
if (status != ERROR && pExcFds != NULL)
{
wakeupNode.type = SELEXCEPT;
wakeupNode.returnSet = pExcFds;
if (selDoIoctls (pSelectContext->pOrigExcFds, width, FIOSELECT, &wakeupNode, TRUE) != OK)
{
status = ERROR;
}
}
if (status != OK)
{
status = errnoGet ();
if (pReadFds != NULL)
{
wakeupNode.type = SELREAD;
(void)selDoIoctls (pSelectContext->pOrigReadFds, width, FIOUNSELECT, &wakeupNode, FALSE);
}
if (pWriteFds != NULL)
{
wakeupNode.type = SELWRITE;
(void)selDoIoctls (pSelectContext->pOrigWriteFds, width, FIOUNSELECT, &wakeupNode, FALSE);
}
if (pExcFds != NULL)
{
wakeupNode.type = SELEXCEPT;
(void) selDoIoctls (pSelectContext->pOrigExcFds, width, FIOUNSELECT, &wakeupNode, FALSE);
}
TASK_UNSAFE();
if (status == S_ioLib_UNKNOWN_REQUEST)
errnoSet (S_selectLib_NO_SELECT_SUPPORT_IN_DRIVER);
return (ERROR);
}
pSelectContext->width = width;
pSelectContext->pendedOnSelect = TRUE;
TASK_UNSAFE();
/*select接口将被阻塞,超时时间为quitTime*/
/*该互斥信号量由BSP驱动层释放,否则当quitTime为0时,出现线程持续挂起现象*/
status = semTake(&pSelectContext->wakeupSem, quitTime);
tempErrno = (status == OK) ? OK : errnoGet();
status = OK;
TASK_SAFE();
/*当完成上述操作后,再次由驱动层将唤醒节点从队列中删除*/
if (pReadFds != NULL)
{
wakeupNode.type = SELREAD;
if (selDoIoctls (pSelectContext->pOrigReadFds, width, FIOUNSELECT, &wakeupNode, FALSE) != OK)
{
status = ERROR;
}
}
if (pWriteFds != NULL)
{
wakeupNode.type = SELWRITE;
if (selDoIoctls (pSelectContext->pOrigWriteFds, width, FIOUNSELECT, &wakeupNode, FALSE) != OK)
{
status = ERROR;
}
}
if (pExcFds != NULL)
{
wakeupNode.type = SELEXCEPT;
if (selDoIoctls (pSelectContext->pOrigExcFds, width, FIOUNSELECT, &wakeupNode, FALSE) != OK)
{
status = ERROR;
}
}
badfd = pSelectContext->badfd;
pSelectContext->pendedOnSelect = FALSE;
TASK_UNSAFE();
if (tempErrno == EINTR)
{
errnoSet (tempErrno);
return ERROR;
}
if (badfd || status != OK)
{
errnoSet (EBADF);
return (ERROR);
}
/*统计当前文件描述符集合中满足条件的对象个数*/
if (pReadFds != NULL)
for (fd = 0; fd < width; fd++)
{
partMask = pReadFds->fds_bits[((unsigned)fd) / NFDBITS];
if (partMask == 0)
{
fd += (int)(NFDBITS - 1);
}
else if (partMask & ((_Vx_fd_mask)1 << (((unsigned) fd) % NFDBITS)))
{
numFound++;
}
}
if (pWriteFds != NULL)
for (fd = 0; fd < width; fd++)
{
partMask = pWriteFds->fds_bits[((unsigned)fd) / NFDBITS];
if (partMask == 0)
fd += (int)(NFDBITS - 1);
else if (partMask & ((_Vx_fd_mask)1 << (((unsigned) fd) % NFDBITS)))
{
numFound++;
}
}
if (pExcFds != NULL)
for (fd = 0; fd < width; fd++)
{
partMask = pExcFds->fds_bits[((unsigned)fd) / NFDBITS];
if (partMask == 0)
fd += (int)(NFDBITS - 1);
else if (partMask & ((_Vx_fd_mask)1 << (((unsigned) fd) % NFDBITS)))
{
numFound++;
}
}
return (numFound);
}
根据上边的分析,可知,系统在线程任务创建阶段会为其创建 select 上下文并进行关联,该上下文主要用于记录文件描述符集合,互斥信号量等信息。
在 select 工作期间,系统内核为当前线程创建唤醒节点并进行关联,BSP则通过操作唤醒节点,比如:添加到唤醒队列,从唤醒队列中删除等操作与系统内核交互,从而实现在驱动层对阻塞线程的唤醒。
|------------| |-----------| |-----------|
|BSP驱动接口| -----> |weakUpNode| -------> | 系统内核 |
|------------| |-----------| |-----------|
接下来对 selDoIoctl 接口进行分析,该接口主要工作为跳转至驱动接口进行操作并返回。
LOCAL STATUS selDoIoctls(fd_set *pFdSet, int fdSetWidth, int ioctlFunc, SEL_WAKEUP_NODE *pNode, BOOL stopOnErr)
{
FAST int fd;
_Vx_fd_mask partMask;
int status = OK;
volatile SEL_WAKEUP_NODE *pWakeupNode;
pWakeupNode = (volatile SEL_WAKEUP_NODE *) pNode;
pWakeupNode->ready = FALSE;
for (fd = 0; fd < fdSetWidth; fd++)
{
partMask = pFdSet->fds_bits[((unsigned)fd)/NFDBITS];
if (partMask == 0)
fd += (int)(NFDBITS - 1);
else if (partMask & ((_Vx_fd_mask)1 << (((unsigned) fd) % NFDBITS)))
{
pWakeupNode->fd = (unsigned short)fd;
/*调用驱动层接口执行,具体见驱动层*/
if (ioctl (fd, ioctlFunc, (long)pWakeupNode) != OK)
{
status = ERROR;
/*若为TRUE,则出现任一错误,立刻退出,并返回ERROR*/
if (stopOnErr)
break;
}
/*BSP驱动层需要将该标志位置1,否则无法写入文件描述符*/
if (pWakeupNode->ready != FALSE)
{
FD_SET (fd, pWakeupNode->returnSet);
pWakeupNode->ready = FALSE;
}
}
}
return (status);
}
综上,便是 select 接口完整的执行过程。其中唤醒节点作为系统内核与BSP驱动层的中转,具有关键作用。因此,驱动层在操作该结构时,需要重点关注。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)