2013-06-05 23 views
2

我有兩個線程的進程。一個線程(線程A)將設置timerfd定時器,而另一個線程(線程B)將在這些定時器上執行「選擇」。一旦定時器到期,線程B將指示線程A.如何使文件描述符上的選擇塊

要添加定時器,線程A將創建一個新的定時器,然後它將喚醒線程B以在其調用中包含此定時器以進行選擇。我試圖通過使用文件描述符來喚醒線程B,僅用於此目的。然後線程B會調用該FD上的select以及調用timerfd返回的所有FD。

問題是,我無法設法創建一個FD,我可以通過某種方式進行控制,以便在需要時使選擇阻止或返回。

我曾嘗試使用的shm_open與調用了fcntl,我已經試過只使用打開一個文件,但沒有人會導致選擇阻止。我所有的嘗試都會導致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 - 差不多一樣的嘗試2,但使用開放式的,而不是的shm_open。

嘗試4 - 同試試1,但用的fcntl(wakeUpFd,F_SETFL,〜O_NONBLOCK),而不是的fcntl(wakeUpFd,F_SETLK,〜F_RDLCK)

+0

歡迎來到Stack Overflow。請儘快閱讀[常見問題]。你在這裏做了一個很好的工作來解釋你所嘗試過的東西,並且在詢問之前明確地嘗試了相當的數量。做得好。 –

回答

4

閱讀select()規範,尤其是位在那裏說:

與常規文件關聯的文件描述符應始終選擇true以準備讀取,準備寫入和錯誤條件。

你不能對普通文件的文件描述符select()塊。你必須有一個管道,或者套接字,或者沿着這些線的東西,就像你的文件select()

1

使用Unix域套接字對,例如socketpair(AF_UNIX, SOCK_DGRAM, 0, commsd)進行通信。當線程A創建一個新的timerfd時,它只需將新描述符int寫入通信套接字commsd[0]

當線程B注意到通信套接字commsd[1]可讀時,它從中讀取一個或多個int。每個int顯然是一個新的timerd描述符,因此線程B必須將其中的每一個添加到它的設置中。

在線程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()編輯集的代碼。

最後,當你將一個較大的結構轉移到循環中執行select()的線程時,此方法也適用於一般情況。只要確保緩衝區足夠大,至少有兩個結構,然後設置; memmove()可以處理您可能會遇到的任何緩衝區包裝和結構對齊問題。

+0

感謝您的回答Nominal Animal,它非常徹底。在我的情況下,我通過內存來處理通信。 FD僅用於喚醒定時器處理程序。管道是我做這件事的最快方式。 – MikaelZ