2017-06-18 52 views
3

我用read_fds中的一個命名管道fd調用select。這個命名管道沒有寫入器,只能以非阻塞,只讀模式打開。我期望與命名管道FD選擇返回標記爲準備好讀,而且試圖從管道返回0閱讀:爲什麼在沒有寫入程序的命名管道上選擇無限期地阻塞?

從上讀手冊頁:

當試圖從閱讀空管道或FIFO:

  • 如果沒有進程打開管道,read()應該返回0來指示文件結束。

但是,選擇只是無限地阻止。爲什麼會這樣?

#include <fcntl.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <string.h> 

#include <stdexcept> 
#include <thread> 
#include <iostream> 

int main() 
{ 
    char buf[4096]; 

    // Create a named pipe 
    auto err = mkfifo("/tmp/whatever",0666); 
    if(err) { 
     throw std::runtime_error(
        std::string("Failed to create fifo ")+ 
        strerror(errno)); 
    } 

    std::thread reader_thread(
       [&](){ 
     auto fd = open("/tmp/whatever",O_RDONLY|O_NONBLOCK); 
     if(fd < 0) { 
      throw std::runtime_error("Failed to open fifo"); 
     } 

     fd_set fds; 
     while(1) { 
      FD_ZERO(&fds); 
      FD_SET(fd,&fds); 
      std::cerr << "calling select" << std::endl; 
      auto retval = select(fd+1,&fds,nullptr,nullptr,nullptr); 
      if(retval < 0) { 
       std::runtime_error("Failed to call select"); 
      } 

      if(FD_ISSET(fd,&fds)) { 
       auto read_bytes = read(fd,buf,4096); 
       std::cerr << "read " << read_bytes << std::endl; 
       if(read_bytes==0) { 
        break; 
       } 
      } 
     } 

     close(fd); 
    }); 

    reader_thread.join(); 

    return 0; 
} 
+0

不要垃圾郵件標籤! C不是C++不是C! – Olaf

+0

爲什麼要選擇'返回,如果沒有什麼可讀? – Olaf

+0

@Olaf>因爲select的手冊頁明確指出*「一個文件描述符也在文件結束時準備就緒」*而fifo手冊頁也表示*「如果所有文件描述符指向管道的寫入結束已關閉,那麼嘗試從管道讀取(2)將看到文件結尾「*」。這實際上是個好問題。 – spectras

回答

4

從POSIX文檔FO select

描述符應被視爲準備讀取時的輸入函數的調用與O_NONBLOCK明確不遮擋,該函數是否會傳輸數據成功。 (該功能可能會返回數據,文件結束指示或指示其被阻塞的錯誤以外的錯誤,並且在每種情況下,描述符都應被視爲可以讀取。

...

如果沒有選定的描述符準備好進行請求的操作,pselect()或select()函數應該阻塞,直到至少有一個請求的操作準備就緒,直到發生超時,或者直到被信號

pipe(7)聯機幫助頁(這是一個FIFO的底層對象):

如果所有文件描述符參照已經關閉,然後嘗試讀取的管道的寫入端(2)從管將看到結束文件(讀(2)將返回0)。

介意現在完成時的用法!這意味着FIFO必須在雙方打開fiorst,在寫入端關閉(用於您的應用程序)以生成EOF條件。

所以,除非fifo最終被作者關閉,爲什麼要返回select? (fifo-)文件本身的設置無關緊要,原因很多:在使用最有效的方法一次讀取多個字節時,它會在兩側打開時引入競爭條件。這是例如一個命令管道:啓動讀取器進程,然後啓動編寫器(當使用命名管道時,這通常是完全不相關的程序)。

如果您希望select提前返回,請使用timeout參數。但通常情況下,使用一個可以通過信號終止的獨立線程(有關更多信息,請參見select手冊頁)。

作爲旁註:Linux/POSIX的一個好處是,使用FIFO或文件或麥克風驅動程序並不重要。

+0

大壩,我剛剛發現! http://pubs.opengroup.org/onlinepubs/9699919799/ – Stargateur

+0

@ Stargateur:不確定你的意思;我只是得到一個全球頁面。以上信息來自Linux手冊頁(3)。我故意沒有使用特定於Linux的命令(交叉檢查沒有揭示相關的差異)。我希望這是完全正確的,我只是簡短地看一下手冊;這通常不是我工作的地區。 – Olaf

+0

噢,我係統上的man page沒有顯示出來...但是你正確的https://linux.die.net/man/3/select。我並不是真正的意思,只是在你回答的同一時間,我發現了這個文檔(它回答了這個問題)。所以我覺得我沒有找到任何東西:p。 – Stargateur