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驱动层的中转,具有关键作用。因此,驱动层在操作该结构时,需要重点关注。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐