Answers:
在类似Unix的操作系统,标准输入,输出和错误流由文件描述符标识的0
,1
,2
。在Linux上,这些proc
文件在中的文件系统下可见/proc/[pid]/fs/{0,1,2}
。这些文件实际上是指向目录下的伪终端设备的符号链接/dev/pts
。
伪终端(PTY)是一对虚拟设备,它们是伪终端主设备(PTM)和伪终端从设备(PTS)(统称为伪终端对),它们提供IPC通道,有点像程序之间的双向管道。连接到终端设备的驱动程序,以及使用伪终端向前一个程序发送输入和从前一个程序接收输入的驱动程序。
关键是伪终端从站就像普通终端一样出现,例如可以在非规范和规范模式之间切换(默认),在这种情况下,它解释某些输入字符,例如在产生中断字符SIGINT
时产生信号 (通常是通过按键盘上的+ 键)写入到伪终端主机,或者在遇到文件结尾字符(通常由+ 生成)时使下一个主机返回。终端支持的其他操作是打开或关闭回显,设置前台进程组等。CtrlCread()
0
CtrlD
伪终端有多种用途:
它们允许程序像ssh
在通过网络连接的另一台主机上操作面向终端的程序。面向终端的程序可以是通常在交互式终端会话中运行的任何程序。此类程序的标准输入,输出和错误不能直接与套接字连接,因为套接字不支持上述与终端相关的功能。
它们允许程序像expect
通过脚本来驱动交互式的,面向终端的程序。
终端仿真器使用它们xterm
来提供与终端相关的功能。
它们被程序使用,例如screen
在多个进程之间复用单个物理终端。
程序可以使用它们script
来记录在Shell会话期间发生的所有输入和输出。
Linux中使用的Unix98风格的PTY设置如下:
驱动程序在处打开伪终端主多路复用器dev/ptmx
,在其上接收PTM的文件描述符,并在/dev/pts
目录中创建PTS设备。通过打开获得的每个文件描述符/dev/ptmx
都是一个具有自己关联的PTS的独立PTM。
驱动程序编写调用fork()
以创建一个子进程,该子进程依次执行以下步骤:
子代呼叫setsid()
开始一个新的会话,该子代为会话的领导者。这也导致孩子失去控制终端。
孩子继续打开与驱动程序创建的PTM相对应的PTS设备。由于孩子是会话领导者,但没有控制终端,因此PTS成为孩子的控制终端。
子级用于dup()
在其标准输入,输出和错误上复制从属设备的文件描述符。
最后,孩子调用exec()
以启动将要连接到伪终端设备的面向终端的程序。
此时,驱动程序写入PTM的所有内容都会显示为PTS上面向终端程序的输入,反之亦然。
在规范模式下运行时,PTS的输入会逐行缓冲。换句话说,就像常规终端一样,仅当将换行符写入PTM时,从PTS读取的程序才接收一行输入。当缓冲容量用完时,write()
将阻止进一步的调用,直到消耗了一些输入。
在Linux内核,文件相关的系统调用open()
,read()
,write()
stat()
等在虚拟文件系统(VFS)层,它提供一个统一的文件系统接口为用户空间程序被实现。VFS允许不同的文件系统实现在内核中共存。当用户空间程序调用上述系统调用时,VFS会将调用重定向到适当的文件系统实现。
下面的PTS设备/dev/pts
由中devpts
定义的文件系统实现管理/fs/devpts/inode.c
,而提供Unix98风格ptmx
设备的TTY驱动程序在中定义drivers/tty/pty.c
。
TTY设备和TTY 线路规范(例如伪终端)之间的缓冲提供了为每个tty设备维护的缓冲区结构,该结构在include/linux/tty.h
在内核版本3.7之前,该缓冲区是翻转缓冲区:
#define TTY_FLIPBUF_SIZE 512
struct tty_flip_buffer {
struct tq_struct tqueue;
struct semaphore pty_sem;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int count;
int buf_num;
unsigned char char_buf[2*TTY_FLIPBUF_SIZE];
char flag_buf[2*TTY_FLIPBUF_SIZE];
unsigned char slop[4];
};
该结构包含分为两个相等大小的缓冲区的存储。缓冲区编号为的0
前半部分char_buf/flag_buf
和1
后半部分。驱动程序将数据存储到由标识的缓冲区中buf_num
。另一个缓冲区可以刷新到线路规则。
通过buf_num
在0
和之间切换来“翻转”缓冲区1
。当buf_num
改变,char_buf_ptr
并flag_buf_ptr
设置为查明的缓冲区的开始buf_num
,并count
设置为0
。
从内核版本3.7开始,TTY翻转缓冲区已替换为通过kmalloc()
按环组织的对象分配的对象。在正常情况下,以IRQ驱动的串行端口以典型速度运行时,其行为与旧的翻转缓冲区几乎相同。最终分配了两个缓冲区,它们之间的内核循环像以前一样。但是,当存在延迟或速度增加时,新的缓冲实现会更好,因为缓冲池可以增加一点。
在这三个手册中的任何一个的手册页中,它解释了答案:
Under normal circumstances every UNIX program has three streams opened
for it when it starts up, one for input, one for output, and one for
printing diagnostic or error messages. These are typically attached to
the user's terminal but might instead refer to files or
other devices, depending on what the parent process chose to set up.
The input stream is referred to as "standard input"; the output stream
is referred to as "standard output"; and the error stream is referred
to as "standard error". These terms are abbreviated to form the sym-
bols used to refer to these files, namely stdin, stdout, and stderr.
Each of these symbols is a stdio(3) macro of type pointer to FILE, and
can be used with functions like fprintf(3) or fread(3).
Since FILEs are a buffering wrapper around UNIX file descriptors, the
same underlying files may also be accessed using the raw UNIX file
interface, that is, the functions like read(2) and lseek(2).
On program startup, the integer file descriptors associated with the
streams stdin, stdout, and stderr are 0, 1, and 2, respectively. The
preprocessor symbols STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are
defined with these values in <unistd.h>.