如何在文件描述符上创建选择块

时间:2013-06-05 16:05:57

标签: c linux

我有一个包含两个线程的进程。一个线程,线程A,将设置timerfd定时器,另一个线程,线程B,将对这些定时器执行“选择”。一旦计时器到期,线程B将向线程A指示这一点。

要添加定时器,线程A将创建一个新的定时器,然后它将唤醒线程B以在其调用select中包含此定时器。我正试图通过使用文件描述符来唤醒线程B只是为了这个目的。然后,线程B将调用该FD上的select以及通过调用timerfd返回的所有FD。

问题在于我无法设法创建一个我可以控制的FD,以便在我想要时它会导致select阻塞或返回。

我试过使用shm_open来调用fcntl,我试图在文件上使用open,但是没有一个会导致select阻塞。我的所有尝试都会导致select立即返回。有没有办法创建一个FD,导致select阻塞,直到我以某种方式更新FD?

尝试1 - 使用shm_open创建FD并使用fcntl设置读锁定:

从线程A创建FD。

if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
   printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
   fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}

从线程A添加计时器。

#create a timer and add it to a list
/* wake up timer thread */
fcntl(wakeUpFd, F_SETLK, ~F_RDLCK);

唤醒线程B

#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
   fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}
#check all other timer FD's

尝试2 - 使用shm_open并读取/写入数据:

从线程A创建FD。

if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
   printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
   if(ftruncate(wakeUpFd, 2) == -1)
   {
      printf("Failed with ftruncate, Errno = %d\n", errno);
   }
}

从线程A添加计时器。

#create a timer and add it to a list
/* wake up timer thread */
if(write(wakeUpFd, wakeUpStr, 1) != 1)
   printf("Failed to write to wakeUpFd\n");

唤醒线程B

#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
   read(wakeUpFd, wakeUpBuf, 10);
}
#check all other timer FD's

尝试3 - 与Try 2非常相似,但使用open而不是shm_open。

尝试4 - 与Try 1相同,但使用fcntl(wakeUpFd,F_SETFL,~O_NONBLOCK)而不是fcntl(wakeUpFd,F_SETLK,~F_RDLCK)

2 个答案:

答案 0 :(得分:4)

阅读select()规范,特别是它所说的位:

  

与常规文件关联的文件描述符应始终为准备读取,准备写入和错误条件选择为true。

您无法对常规文件的文件描述符进行select()阻止。你必须有一个管道,或套接字,或那些沿着这些行的东西,作为select()所在的文件。

答案 1 :(得分:1)

使用Unix域套接字对,例如socketpair(AF_UNIX, SOCK_DGRAM, 0, commsd)进行通信。当线程A创建一个新的timerfd时,它只是将新的描述符int写入通信套接字commsd[0]

当线程B注意到通信套接字commsd[1]是可读的时,它会从中读取一个或多个int。每个int显然都是一个新的timerd描述符,因此线程B必须将每一个添加到它select()的集合中。

在线程B中,我建议在循环中使用非阻塞读取bytes = recv(commfd, ptr, len, MSG_DONTWAIT)来从通信套接字读取:

    char     buffer[8 * sizeof(int)];
    size_t   head = 0, tail = 0;
    ssize_t  bytes;
    int      new_timerfd;

    while (1) {

        if (head >= tail)
            head = tail = 0;
        else
        if (head > 0) {
            memmove(buffer, buffer + head, tail - head);
            tail -= head;
            head = 0;
        }

        if (tail < sizeof buffer) {
            bytes = recv(commsd[1], buffer + head, sizeof buffer - tail, MSG_DONTWAIT);
            if (bytes > (ssize_t)0)
                head += bytes;
        }

        if (head >= tail)
            break;

        while (head + sizeof (int) <= tail) {
            /* Unaligned version of new_timerfd = *(int *)(buffer + head); */
            memmove(&new_timerfd, buffer + head, sizeof new_timerfd);
            head += sizeof (int);

            /*
             * Add new_timerfd to select()ed set
            */
        }
    }

上面的循环确实进行了额外的非阻塞recv()调用,以检测它已经读取了所有立即挂起的内容,但这种方式非常强大。它甚至不会假设你总是可以阅读完整的int。 (由于_POSIX_PIPE_BUF始终是sizeof int的倍数,您可以假设您始终阅读完整int。)

只要有可用数据,上述循环就会从套接字执行非阻塞接收,循环体将逐个提取描述符new_timerfd。我省略了将其添加到select() ed集的代码。

最后,当您将更大的结构转移到循环中select()的线程时,此方法也适用于一般情况。只需确保缓冲区足够大,至少有两个结构,然后就可以了。 memmove()可以处理任何缓冲区打包和结构对齐问题。