我编写并维护了一个程序 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 有独特的单独行为。
- 如果写入最后一个数据,系统将终止
- 如果子项退出(管道损坏?
代码依赖于存在足够长的管道来发送数据,但退出子通道可能会导致在接收数据之前删除虚拟通道。
这将是 Pty 独有的,对于真正的终端来说不存在。
在 FreeBSD 10-STABLE 上,我确实得到了两条输出线。
(您可以将openpty
和fork
替换为基本上也可以处理login_tty
的forkpty
。
在 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,事情可能会发生变化,因为操作系统以不同的方式处理单个交付,关闭香奈儿通常会终止孩子的所有子进程。如果父级仍然打开句柄,这会是一个问题吗?)