stdin、stdout、stderr 是一些整数,它们索引到一个数据结构中,该数据结构“知道”哪些 I/O 通道将用于该过程。我知道这个数据结构对于每个流程都是独一无二的。I/O 通道只是一些具有动态内存分配的数据数组结构吗?
Tho*_*man 15
在类 Unix 操作系统中,标准输入、输出和错误流由文件描述符0
、1
、标识2
。在 Linux 上,这些proc
在/proc/[pid]/fs/{0,1,2}
. 这些文件实际上是指向目录下伪终端设备的符号链接/dev/pts
。
一个伪终端 (PTY) 是一对虚拟设备,一个伪终端主机(PTM) 和一个伪终端从机(PTS)(统称为伪终端对),它们提供一个 IPC 通道,有点像程序之间的双向管道,它期望连接到终端设备,以及使用伪终端向前一个程序发送输入和接收输入的驱动程序。
一个关键点是伪终端从机看起来就像一个常规终端,例如它可以在非规范和规范模式(默认)之间切换,在这种模式下它解释某些输入字符,例如SIGINT
当 中断字符(通常生成通过按键盘上的Ctrl+ C)被写入伪终端主机或在遇到文件结束字符(通常由+生成)时导致下一个read()
返回。终端支持的其他操作有打开或关闭回显、设置前台进程组等。0
CtrlD
伪终端有多种用途:
它们允许程序ssh
在通过网络连接的另一台主机上运行面向终端的程序。面向终端的程序可以是通常在交互式终端会话中运行的任何程序。此类程序的标准输入、输出和错误不能直接连接到套接字,因为套接字不支持上述与终端相关的功能。
它们允许expect
程序从脚本驱动交互式终端导向程序。
它们由终端模拟器使用,例如xterm
提供与终端相关的功能。
它们被程序使用,例如screen
在多个进程之间多路复用单个物理终端。
它们被程序script
用来记录在 shell 会话期间发生的所有输入和输出。
Linux 中使用的Unix98-style PTYs设置如下:
驱动程序在 处打开伪终端主多路复用器dev/ptmx
,在该处接收 PTM 的文件描述符,并在/dev/pts
目录中创建 PTS 设备。通过打开获得的每个文件描述符/dev/ptmx
都是一个独立的 PTM,具有自己关联的 PTS。
驱动程序调用fork()
创建子进程,子进程依次执行以下步骤:
孩子打电话setsid()
开始一个新的会话,孩子是会话的领导者。这也导致孩子失去其控制终端。
孩子继续打开与驱动程序创建的 PTM 对应的 PTS 设备。由于孩子是会话领导者,但没有控制终端,PTS 成为孩子的控制终端。
子dup()
进程用于在其标准输入、输出和错误上复制从属设备的文件描述符。
最后,子进程调用exec()
以启动要连接到伪终端设备的面向终端的程序。
此时,驱动程序写入 PTM 的任何内容都显示为 PTS 上面向终端的程序的输入,反之亦然。
在规范模式下运行时,PTS 的输入逐行缓冲。换句话说,就像使用常规终端一样,从 PTS 读取的程序只有在将换行符写入 PTM 时才会接收一行输入。当缓冲容量耗尽时,进一步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];
};
Run Code Online (Sandbox Code Playgroud)
该结构包含分成两个相等大小的缓冲区的存储。缓冲区被编号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 驱动的串行端口以典型速度的正常情况下,它们的行为与旧的翻转缓冲区几乎相同;最终分配了两个缓冲区,内核像以前一样在它们之间循环。但是,当出现延迟或速度增加时,新的缓冲区实现会表现得更好,因为缓冲池可以稍微增长。