1. select接口简介

1.1 select接口使用用例

select是操作系统多路I/O复用技术实现的方式之一。
多路I/O复用技术大致使用场景为:构造一张感兴趣的文件描述符列表,然后调用多路复用的IO接口,在接口中进行阻塞,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。
select在应用中使用的例子如下段代码所示。

#include <sys/select.h>
int main (int argc, char **argv)
{  
    fd_set       fdset;
    struct timeval  timeout;

    timeout.tv_sec  = 10;
    timeout.tv_usec = 0;

    int fd = open("/dev/htm2", O_RDWR, 0666);

    for (;;) {
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);
        select(fd + 1, &fdset, &fdset, NULL, &timeout);
        sleep(1);
    }
    return  (0);
}

1.2 select函数原型分析

LW_API INT   select(INT                   iWidth, 
                   fd_set               *pfdsetRead,
                   fd_set               *pfdsetWrite,
                   fd_set               *pfdsetExcept,
                   struct timeval        *ptmvalTO);
  • iWidth为设置的文件集中,最大的文件号 + 1;
  • pfdsetRead为关心的可读文件集;
  • pfdsetWrite为关心的可写文件集;
  • pfdsetExcept为关心的异常文件集;
  • ptmvalTO为等待超时时间,LW_NULL表示永远等待。
  • 返回值:正常返回等待到的文件数量,错误返回 PX_ERROR。

2. 驱动中的select实现

2.1 驱动的ioctl实现

SylixOS的select接口实现中,系统会调用到每一个fd对应的设备驱动的ioctl接口,并会调用到如下表所示的两个命令。

命令 说明
FIOSELECT 添加SEL_WAKE_NODE节点
FIOUNSELECT 移除SEL_WAKE_NODE节点

2.2 SylixOS的select等待链

添加与移除SEL_WAKE_NODE的操作实际都是对SylixOS的select等待链进行操作,
对应调用如SEL_WAKE_NODE_ADD与SEL_WAKE_NODE_DELETE的系统接口。
等待链的作用就是将一堆阻塞待唤醒的线程组成集合,当需要被唤醒时可以通过调用系统的SEL_WAKE_UP系列函数实现对线程的唤醒。
SylixOS提供的唤醒命令如下表所示。

命令 说明
SEL_WAKE_UP 唤醒一个等待线程
SEL_WAKE_UP_ALL 唤醒等待某一类型操作的所有线程
SEL_WAKE_UP_TYPE 获取节点的等待类型
SEL_WAKE_UP_ERROR 由于产生了错误,唤醒一个等待的线程
SEL_WAKE_UP_TERM 由于产生了错误,唤醒所有等待某一类型操作的所有线程

2.3 SylixOS中的select上下文

需要注意的是:select阻塞操作使用的信号量为select上下文之中的,并不需要在驱动的FIOSELECT里再实现一个信号量。
select的上下文如下段程序所示。

typedef struct {
    LW_OBJECT_HANDLE       SELCTX_hSembWakeup;              /*  唤醒信号量              */
    BOOL                   SELCTX_bPendedOnSelect;          /*  是否阻塞在 select() 上  */
    fd_set                *SELCTX_pfdsetReadFds;            /*  阻塞的读文件集指针       */
    fd_set                *SELCTX_pfdsetWriteFds;           /*  阻塞的写文件集指针       */
    fd_set                *SELCTX_pfdsetExceptFds;          /*  阻塞的异常文件集指针     */
    fd_set                 SELCTX_fdsetOrigReadFds;         /*  原始的读文件集          */
    fd_set                 SELCTX_fdsetOrigWriteFds;        /*  原始的写文件集          */
    fd_set                 SELCTX_fdsetOrigExceptFds;       /*  原始的异常文件集        */
    INT                    SELCTX_iWidth;                   /*  select() 第一个参数    */
} LW_SEL_CONTEXT;
typedef LW_SEL_CONTEXT     *PLW_SEL_CONTEXT;

3. 阻塞与唤醒实现

3.1 阻塞操作

select的阻塞操作是在其内部调用的pselect函数中调用二进制信号量的pend操作实现的。但是在调用pend之前,pselect会首先调用ioctl,传递FIOSELECT参数,此接口中会判断当前是否满足select的唤醒条件,若满足则先调用post,以使之后调用的pend不会被阻塞。
其流程如下图所示。

select内核处理原理

3.2 唤醒操作

在需要进行唤醒的地方调用SEL_WAKE_UP系列接口,如产生中断的地方、检测的线程中。