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

Nee*_*asu 3 c linux pty

我已经(/dev/pts/N)从进程A 创建了一个伪终端,并且我在一定的时间间隔内写了随机整数.我可以打开那个pts 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);
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

Nom*_*mal 7

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


答案很长:改变你的方法,你会得到更好的结果.

考虑您可以执行的以下任务,以便了解伪终端行为:

  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"是nano命令的文件名,第二个是argv[0]命令看到的参数.它不提供任何实际的命令行参数;它就像你nano在你最喜欢的shell中运行一样.)

  4. 在父进程中,您现在可以读取和写入伪终端的主端.

    请注意,您可能必须同时执行此操作; 没有办法知道何时可以/需要/必须阅读(更多),以及何时写作可能会阻止.

    我不能强调这里全双工或非阻塞的重要性.如果你从未读过你的伪终端,也不要指望它能够正常工作.

  5. 在父进程中,删除文件foobar.txt.

    (使用remove()unlink().)

    这样nano就不会弹出"文件已存在"对话框.

  6. 在父进程中,在读取任何输出时,从进程可能会写入伪终端,

    • 等待几分之一秒(纳米绘制编辑器屏幕时)

    • Some text和回车\r,

    • 等一会儿,

    • Ctrl+O(\017通常可视化^O)

    • foobar.txt和回车\r,

    • 等一会儿,

    • Ctrl+X(\030通常可视化为^X),

    • 等待

    nano应该退出.

  7. 在父进程中,等待child(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>"
Run Code Online (Sandbox Code Playgroud)

在哪里\e是简写\033\x1B,即.ASCII ESC字符.

特别注意奴隶nano进程如何喷出各种输出,只是为了绘制一个奇特的编辑器屏幕.如果有一个时钟或某些定期更改的时钟,它基本上会每秒发出这些更新.

Master-> Slave使用\r而不是\n换行的原因是默认的termios设置.

  • @NeelBasu:不,你没有.但是,你正在以一种毫无意义的方式使用它,并寻找你得到意想不到的结果的原因.您不是试图了解潜在原因是什么,而是对代码进行随机更改,直到您看到的效果符合您的期望,并且您可以声明问题"已解决".如果这让你开心,那就去吧.但是,不要指望那些必须与您或您的代码合作的人对您的学习或解决问题的态度有信心.我只是想帮助你学习如何以有效的方式使用伪终结. (4认同)