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

时间:2015-04-07 07:03:01

标签: c linux pty

我已经从进程A创建了一个伪终端(/dev/pts/N),并且我在一定的时间间隔内写了随机整数。我可以从screen打开那个pts并检查它的输出 但是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);
      }
  }
}

2 个答案:

答案 0 :(得分:6)

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


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

考虑一下你可以做的以下任务,让自己了解伪终端行为:

  1. 创建一个伪终端主设备,并允许从设备访问它

    (使用posix_openpt()grantpt()unlockpt()创建伪终端。使用ptsname()查找从端的设备名称。)

  2. 分叉子进程。

    (使用fork()分叉子进程,然后setsid()从控制终端分离。它还创建一个新的进程组,因此您的主进程可以向由该进程启动的所有进程发送信号。通过向整个组发送信号来进行子进程。)

  3. 在子进程中,打开标准输入(STDIN_FILENO),用于从从属伪终端读取,标准输出(STDOUT_FILENO)和标准错误(STDERR_FILENO)用于写入伪终端的奴隶端。执行nano

    (使用dup2()将描述符复制到正确的位置,close()关闭额外的位置,例如execlp("nano", "nano", NULL)执行nano。请注意第一个remove() 1}}是nano命令的文件名,第二个是命令看到的"nano"参数。它不提供任何实际的命令行参数;它就像你运行argv[0]一样你最喜欢的外壳。)

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

    请注意,您可能必须同时这样做;没有办法知道何时可以/需要/必须阅读(更多),以及何时写作可能会阻止。

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

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

    (使用unlink()waitpid()。)

    这只是为了foobar.txt不会弹出“文件已存在”对话框。

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

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

    • 撰写nano和回车Some text

    • 等一下,

    • Ctrl + O \r,通常可视化为\017

    • 撰写^O和回车foobar.txt

    • 等一下,

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

    • 等待

    ^X应退出。

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

    (为此使用循环和termios。)

  8. 如果您完成上述操作,您的主终端控制程序只会模拟一个本地或远程“人”,运行一个非常短的nano会话,只写nano和换行,将其保存到{{ 1}},并退出。 (该文件应包含Some text,因为这是foobar.txt的工作方式。)

    如果您创建一个除了从主伪终端文件描述符读取任何内容之外什么都不做的帮助程序线程,则步骤6最容易实现。从一个非常明确的意义上说,它就像一个自动排水。毕竟,我们对这里的"Some text\n\n"输出终端并不感兴趣。在步骤7之后,您只需关闭该描述符,导致帮助程序线程出错(nano返回-1并带有nano)并返回,因此主线程可以使用read()来获取它

    当然,您可以使用非阻塞I / O实现步骤6。无论你采用何种方式,都必须始终errno == EBADF来自主伪终端,并且在从属进程也写入终端时不会被pthread_join()死锁。这是OP正在努力的情况,我敢打赌。

    在上述场景中流经伪终端的典型通信序列是:

    read()

    其中write()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的简写,即。 ASCII ESC字符。

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

    使用\x1B代替nano作为换行符的Master-> Slave的原因是默认的{{3}}设置。

答案 1 :(得分: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);