2015-04-07 58 views
2

我已經創建了一個來自進程A的僞終端(/dev/pts/N),並且我正在以特定的時間間隔向隨機整數寫入。我可以從screen打開這個點並檢查它的輸出。
cat /dev/pts/N失敗:它無限地阻止並且不返回。爲什麼從我的僞終端讀取失敗?

我試圖從另一個使用open()/read()函數的函數讀取該函數,並且還有read()永不返回。

int main(){ 
    int source_fd = open("/dev/pts/4", O_RDONLY); 

    while(1){ 
     char buffer[READ_BUFFER_SIZE] = {0}; 
     char* buff_ptr = buffer; 
     int r = read(source_fd, (void*)buff_ptr, 1); 
     // !!!! never comes here 
     while(r > 0){ 
     ++buff_ptr; 
     r = read(source_fd, (void*)buff_ptr, 1); 
     } 
    } 
} 
+1

1)C++不是C,你錯了標籤在您的文章。刺激。 2)第二個'fcntl()'覆蓋第一個。你也可以把標誌提供給'open()',你知道的。 3)僞終端不是文件,它是僞終端,並且像僞終端一樣行爲。閱讀['man 7 pty'](http://man7.org/linux/man-pages/man7/pty.7.html)。您可以使用它來運行nano或一些基於Curses的應用程序;用「貓」來試試它是愚蠢的。 –

+0

是的,我知道C++不是C,但爲什麼我只能嚴格遵守C函數只是爲了測試和準備一個片段發佈?現在問題變成了僞終端不能作爲普通文件打開? –

+0

我刪除了我所做的C++部分,使其更易於理解和測試,並用'C –

回答

5

簡答:您沒有正確處理僞終端。通過從僞終端讀取外部進程來觀察奇怪甚至隨機的結果是正常的; 你不應該那樣做。這就像有兩個人同時在同一個鍵盤上寫字一樣。 (因爲你可以看到,在一些電視節目,這並不意味着它使任何任何意義。)


龍答:改變你的方法,你就會有更好的結果。

考慮下面的任務可以做,以acquint自己與僞終端行爲:

  1. 創建一個僞終端主,並允許它從接入

    (使用posix_openpt()grantpt()unlockpt()到創建僞終端,使用ptsname()找出從終端的設備名稱。)

  2. 分叉子進程。

    (使用fork()到餐桌子進程,然後setsid()從控制終端分離。它還創建一個新的進程組,這樣你的主進程可以通過發送信號,發送信號由子進程啓動的所有進程整個組。)

  3. 在子進程,開放標準輸入(STDIN_FILENO),用於從從僞終端,和標準輸出(STDOUT_FILENO)和標準誤差(STDERR_FILENO)讀取用於寫入到僞終端的從屬端。執行nano

    (使用dup2()來描述複製到其正確的位置,close()關閉多餘的,並且如execlp("nano", "nano", NULL)執行nano。需要注意的是,第一"nano"是納米命令的文件名,第二個是argv[0]參數它不提供任何實際的命令行參數;它的行爲就好像您在您喜歡的shell中運行nano)。

  4. 在父進程中,您現在可以讀取和寫入主結束僞終端。

    請注意,您可能必須同時這樣做;沒有辦法知道什麼時候可以/需要/必須閱讀(更多),什麼時候寫作可能會阻止。

    我不能強調在這裏全雙工或非阻塞的重要性。如果你從來沒有讀過你的僞終端,不要指望它也能工作。

  5. 在父進程中,刪除文件foobar.txt

    (使用remove()unlink()。)

    這只是讓nano不會彈出一個「文件已存在」對話框。

  6. 在父進程,同時讀取任何輸出的從屬進程可能寫入僞終端,

    • 等待第二個的一小部分(而納米繪製編輯器屏幕)

    • Some text和一個回車\r

    • 等待第二個的一小部分,

    • Ctrl + O鍵\017,經常顯現爲^O

    • foobar.txt和一個回車\r

    • 等待第二個的一小部分,

    • Ctrl鍵+ X\030,通常可視爲^X),

    • 等待

    nano應該退出。

  7. 在父進程中,等待子進程(nano)進程退出。

    (使用這個循環和waitpid()。)

如果完成上述,您的主終端的控制程序只是模擬一個本地或遠程的「人」的運行很短的nano會議,只是寫Some text和一個換行符,將它保存到foobar.txt,然後退出。 (該文件應包含"Some text\n\n",因爲這是如何nano的作品。)

第6步是最容易實現的,如果你創建一個輔助線程,什麼也不做,但是從主僞終端文件描述符讀取。從非常明顯的意義上講,它就像一個自動排水管。畢竟,我們並不真正對nano輸出到此處的終端感興趣。在步驟7之後,您只需關閉該描述符,導致助手線程出錯(read()返回-1並返回errno == EBADF)並返回,因此主線程可以使用pthread_join()來獲得它。

當然,您可以使用非阻塞I/O來實現步驟6。無論你如何做,都必須始終從主僞終端登錄read(),並且在從屬進程也正在寫入終端時,不會被write()死鎖。我敢打賭,這是OP正在努力的情況。

流經在上述方案中的僞終端通信的一個典型的序列是:

Slave -> Master: "\e[?1049h\e[1;24r\e(B\e[m\e[4l\e[?7h\e[?12l\e[?25h" 
Slave -> Master: "\e[?1h\e=\e[?1h\e=\e[?1h\e=" 
Slave -> Master: "\e[39;49m\e[39;49m\e(B\e[m\e[H\e[2J\e(B\e[0;7m" 
       " GNU nano 2.2.6    " 
       " New Buffer          " 
       "\e[23;1H^G\e(B\e[m Get Help " 
       "\e(B\e[0;7m^O\e(B\e[m WriteOut " 
       "\e(B\e[0;7m^R\e(B\e[m Read File " 
       "\e(B\e[0;7m^Y\e(B\e[m Prev Page " 
       "\e(B\e[0;7m^K\e(B\e[m Cut Text " 
       "\e(B\e[0;7m^C\e(B\e[m Cur Pos" 
       "\015\e[24d\e(B\e[0;7m^X\e(B\e[m Exit" 
       "\e[14G\e(B\e[0;7m^J\e(B\e[m Justify " 
       "\e(B\e[0;7m^W\e(B\e[m Where Is " 
       "\e(B\e[0;7m^V\e(B\e[m Next Page " 
       "\e(B\e[0;7m^U\e(B\e[m UnCut Text" 
       "\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[3d" 
Master -> Slave: "Some text\015" 
Slave -> Master: "\e[1;71H\e(B\e[0;7mModified\015\e[3d\e(B\e[mSome text\015\e[4d" 
Master -> Slave: "\017" 
Slave -> Master: "\e[22d\e(B\e[0;7mFile Name to Write: " 
       "        " 
       "        " 
       "\e[23;14H\e(B\e[m  " 
       "\e(B\e[0;7mM-D\e(B\e[m DOS Format  " 
       "\e(B\e[0;7mM-A\e(B\e[m Append   " 
       "\e(B\e[0;7mM-B\e(B\e[m Backup File" 
       "\e[24;2H\e(B\e[0;7mC\e(B\e[m Cancel   " 
       "\e(B\e[0;7mM-M\e(B\e[m Mac Format  " 
       "\e(B\e[0;7mM-P\e(B\e[m Prefix\e[K\e[22;21H" 
Master -> Slave: "foobar.txt\015" 
Slave -> Master: "\e[1;31H\e[39;49m\e(B\e[0;7mFile: foobar.txt" 
       "\e[1;71H  \e[22;31H\e(B\e[m\e[1K " 
       "\e(B\e[0;7m[ Wrote 2 lines ]" 
       "\e(B\e[m\e[K\e[23;14H\e(B\e[0;7m^O\e(B\e[m WriteOut " 
       "\e(B\e[0;7m^R\e(B\e[m Read File " 
       "\e(B\e[0;7m^Y\e(B\e[m Prev Page " 
       "\e(B\e[0;7m^K\e(B\e[m Cut Text " 
       "\e(B\e[0;7m^C\e(B\e[m Cur Pos" 
       "\e[24;2H\e(B\e[0;7mX\e(B\e[m Exit  " 
       "\e(B\e[0;7m^J\e(B\e[m Justify " 
       "\e(B\e[0;7m^W\e(B\e[m Where Is " 
       "\e(B\e[0;7m^V\e(B\e[m Next Page " 
       "\e(B\e[0;7m^U\e(B\e[m UnCut Text" 
       "\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[4d" 
Master -> Slave: "\030" 
Slave -> Master: "\e[23d\e[J\e[24;80H" 
Slave -> Master: "\e[24;1H\e[?1049l\015\e[?1l\e>" 

其中\e是簡寫\033\x1B,即。 ASCII ESC字符。

特別注意如何從屬nano進程噴出各種輸出,只是畫一個花哨的編輯器屏幕。如果有一個時鐘或一些定期改變的時鐘,它基本上會每秒發送一次這些更新。

Master-> Slave使用\r而不是\n作爲換行符的原因是默認的termios設置。

+0

我沒有要求解釋如何創建僞終端 –

+3

@ NeelBasu:不,你沒有。但是,您正在以一種毫無意義的方式使用一種方法,並尋找您獲得意想不到的結果的原因。與其試圖瞭解其根本原因是什麼,不然你會隨機更改代碼,直到看到的效果符合您的期望,並且您可以聲稱自己的問題「已解決」。如果那讓你開心,就去做吧。但不要指望那些必須和你一起工作或你的代碼對你對學習或解決問題的態度有信心。我只想幫助你學習如何以有效的方式使用pseudoterminals。 –

1

只設置F_SETFL,做cfmakeraw工作

int source_fd = open("/dev/pts/4", O_RDONLY | O_NOCTTY | O_NDELAY); 
    fcntl(source_fd, F_SETFL, 0); 
    tcgetattr(source_fd, &options); 
    cfmakeraw(&options); 
    tcflush(source_fd, TCIFLUSH); 
    tcsetattr(source_fd, TCSANOW, &options); 
+0

爲什麼要清除輸入,輸出和控制標誌? – edmz

+0

另外,錯誤的答案。默認工作正常;你*不必*設置波特率*。根本不需要設置termios設置。只是如果你這樣做,你應該用符合你期望的有效值填充它們。僞終端和大多數非硬件串行設備通常都會忽略波特率(除了'B0',表示掛斷)。 –