c - 如果从属 pty 上的最终输出未在父级中关闭,则会丢失.为什么



我编写并维护了一个程序 rlwrap,它使用伪终端与子进程进行通信。 伪终端(ptys(存在于所有Unix(类(系统中,但它们在不同平台上的行为略有不同。

举个例子:在rlwrap中,父进程保持从属进程打开,以密切关注孩子的终端设置(在Linux和FreeBSD上,可以使用master,但在Solaris中则不能(。

在 FreeBSD (8.2( (但不是 Linux( 上,这会导致子项最终输出的丢失。例如:

#include <stdio.h>
/* save as test.c and compile with gcc -o test test.c -lutil */
#define BUFSIZE 255
int main(void) {
  int master, slave;
  char buf[BUFSIZE];
  int nread;
  openpty(&master, &slave, NULL, NULL, NULL);
  if (fork()) {       /* parent:                                                      */
    close(slave);     /* leave this out and lose slave's final words ... WHY?         */
    do {
      nread = read(master, buf, BUFSIZE);
      write(STDOUT_FILENO, buf, nread); /* echo child's output to stdout              */
    } while (nread > 0);     
  } else {             /* child:                                                      */
    login_tty(slave);  /* this makes child a session leader and slave a controlling   */
                       /* terminal for it, then dup()s std{in,out,err} to slave       */ 
    printf("Feeling OK :-)n");
    sleep(1);
    printf("Feeling unwell ... Arghhh!n"); /* this line may get lost                 */
  }
  return 0;
}

父进程将按预期回显子进程的输出,但是当我省略close(slave)时(像rlwrap一样保持打开状态(:

  • 在 FreeBSD 上, 父级看不到最终的输出行, 而是读取一个 EOF。(如果有的话,我会期望相反 - 保持从端打开可以防止输出丢失(
  • 另一方面,在 Linux 上,永远不会看到 EOF,即使在孩子死也是如此(无论我们是否关闭奴隶(

这种行为是否记录在某处?有理由吗?我可以在不关闭父进程中的从进程的情况下绕过它吗?

我发现使从站成为控制终端 - 用几个简单的dup()调用替换login_tty调用 - 将解决问题。然而,这不是rlwrap的解决方案:相当多的命令需要一个控制终端(/dev/tty(来通信,所以rlwrap必须为它们提供一个。

我认为 Pty 有独特的单独行为。

  1. 如果写入最后一个数据,系统将终止
  2. 如果子项退出(管道损坏?

代码依赖于存在足够长的管道来发送数据,但退出子通道可能会导致在接收数据之前删除虚拟通道。

这将是 Pty 独有的,对于真正的终端来说不存在。

在 FreeBSD 10-STABLE 上,我确实得到了两条输出线。

(您可以将openptyfork替换为基本上也可以处理login_ttyforkpty

在 FreeBSD 8.0 中, 旧的 pty(4) 驱动程序被 pts(4) 取代。新pty(4)的行为与旧不同。从手册;

与以前的实现不同,当 PTY 未使用时,主设备节点和从设备节点将被销毁。 在不存在的主设备上调用 stat(2( 已经会导致创建一个新的主设备节点。 主设备只能通过打开和关闭来销毁。

8.0-RELEASE和10.0-RELEASE之间很可能有重大变化

您可能还想查看 FreeBSD ports 树中应用的补丁。

printf 根据输出设备的类别进行缓冲输出。 只要试着把fflush(stdout(;在最后一次 printf 之后,看看它是否是缓冲输出的问题。

这是我在 ubuntu Linux 上找到的 注意:始终检查错误

#include <stdio.h>
#include <stdlib.h>
#include <pty.h>    // openpty(), 
#include <utmp.h>   // login_tty()
#include <unistd.h> // read(), write()
/* save as test.c and compile with gcc -o test test.c -lutil */
#define BUFSIZE (255)
int main(void) 
{
    int master, slave;
    char buf[BUFSIZE];
    int nread;
    pid_t pid;
    if( -1 == openpty(&master, &slave, NULL, NULL, NULL) )
    { // then openpty failed 
        perror( "openpty failed" );
        exit( EXIT_FAILURE );
    }
    // implied else, openpty successful
    pid = fork();
    if( -1 == pid ) 
    { // then fork failed
        perror( "fork failed" );
        exit( EXIT_FAILURE );
    }
    // implied else, fork successful
    if( pid ) 
    {    /* parent:                                                      */
        close(slave);     /* leave this out and lose slave's final words ... WHY?         */
        do 
        {
            if( -1 == (nread = read(master, buf, BUFSIZE) ) )
            {// then, error occurred
                perror( "read failed" );
                exit( EXIT_FAILURE );
            }
            // implied else, read successful
            if ( nread )
            {   
                write(STDOUT_FILENO, buf, nread); /* echo child's output to stdout  */
            }
        } while (nread);    /* nread == 0 indicates EOF */     
    } 
    else // pid == 0
    {    /* child:                                                      */
        if( -1 == login_tty(slave) )  /* this makes child a session leader and slave a controlling   */
                       /* terminal for it, then dup()s std{in,out,err} to slave       */ 
        { // then login_tty failed
            perror( "login_tty failed" );
            exit( EXIT_FAILURE );
        }
        // implied else, login_tty successful
        printf("Feeling OK :-)n");
        sleep(1);
        printf("Feeling unwell ... Arghhh!n"); /* this line may get lost */
    } // end if
    return 0;
} // end function: main

当 close(( 语句被注释掉时然后父级永远不会因为 read(( 语句阻塞而退出

当 close(( 语句是源的一部分时然后,当子级退出时,父级退出时尝试从"丢失"的终端读取时出现读取错误

这是关闭(( 注释Pout时的输出

Feeling OK :-)
Feeling unwell ... Arghhh!
then the parent hangs on the read() statement

这是 close(( 未注释掉时的输出

Feeling OK :-)
Feeling unwell ... Arghhh!
read failed: Input/output error

我不确定我是否正确:无论是否与 pty 无关,只要一个进程打开了通道,操作系统就不应该将 EOF 传递给读取器(因为仍然有写入器(。(分叉后有两个开放通道(只有当您关闭父通道时,从站上的关闭才应转发 EOF。

在 PTY 上,您确定正确处理了 NL,因为通常 CR 应该触发换行符。

(只是一个想法:如果它是一个控制tty,事情可能会发生变化,因为操作系统以不同的方式处理单个交付,关闭香奈儿通常会终止孩子的所有子进程。如果父级仍然打开句柄,这会是一个问题吗?)

相关内容

  • 没有找到相关文章

最新更新