为什么从我的伪终端读取失败?

问题描述:

我已经创建了一个来自进程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 –

简答:您没有正确处理伪终端。通过从伪终端读取外部进程来观察奇怪甚至随机的结果是正常的; 你不应该那样做。这就像有两个人同时在同一个键盘上写字一样。 (因为你可以看到,在一些电视节目,这并不意味着它使任何任何意义。)


龙答:改变你的方法,你就会有更好的结果。

考虑下面的任务可以做,以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。 –

只设置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',表示挂断)。 –