Linux内核中I/O通道是如何实现的?

Linux内核中I/O通道是如何实现的?

stdin、stdout、stderr 是一些整数,它们索引到数据结构中,这些数据结构“知道”进程将使用哪些 I/O 通道。我知道这个数据结构对于每个流程来说都是唯一的。 I/O 通道只是一些具有动态内存分配的数据数组结构吗?

答案1

在类 Unix 操作系统中,标准输入、输出和错误流由文件描述符01、标识2。在 Linux 上,这些proc/proc/[pid]/fs/{0,1,2}.这些文件实际上是指向伪终端目录下的设备/dev/pts

伪终端(PTY)是一对虚拟设备,一个伪终端主机(PTM)和伪终端从机(PTS)(统称为asa假终端对),提供一个 IPC 通道,有点像希望连接到某个程序的程序之间的双向管道终端设备,以及使用伪终端将输入发送到前​​一个程序并从前一个程序接收输入的驱动程序。

一个关键点是伪终端从机看起来就像常规终端一样,例如它可以在之间切换非规范和规范模式(默认),它解释某些输入字符,例如SIGINT打断字符(通常通过按键盘上的Ctrl+生成C)被写入伪终端主机,或者在遇到文件结束字符(通常由+生成)时导致下一个read()返回。终端支持的其他操作包括打开或关闭回显、设置前台进程组等。0CtrlD

伪终端有多种用途:

  • 它们允许程序ssh在通过网络连接的另一台主机上运行面向终端的程序。面向终端的程序可以是通常在交互式终端会话中运行的任何程序。这种程序的标准输入、输出和错误不能直接连接到套接字,因为套接字不支持上述与终端相关的功能。

  • 它们允许程序expect从脚本驱动面向交互式终端的程序。

  • 它们由终端仿真器使用,例如xterm提供与终端相关的功能。

  • 它们被程序使用,例如screen在多个进程之间复用单个物理终端。

  • 它们被诸如scriptto 之类的程序用来记录 shell 会话期间发生的所有输入和输出。

Unix98 风格的 PTY,在Linux下使用,设置如下:

  • 驱动程序在 处打开伪终端主多路复用器dev/ptmx,接收 PTM 的文件描述符,并在/dev/pts目录中创建 PTS 设备。每个打开得到的文件描述符/dev/ptmx都是一个独立的PTM,有自己关联的PTS。

  • 驱动程序调用fork()创建子进程,该子进程依次执行以下步骤:

    • 孩子调用setsid()开始一个新的会话,该孩子是会话的领导者。这也导致孩子失去了控制终端

    • 子进程继续打开与驱动程序创建的 PTM 相对应的 PTS 设备。由于子进程是会话领导者,但没有控制终端,因此 PTS 成为子进程的控制终端。

    • 子级用于dup()在其标准输入、输出和错误上复制从属设备的文件描述符。

    • 最后,子进程调用exec()启动要连接到伪终端设备的面向终端的程序。

此时,驱动程序写入 PTM 的任何内容都将显示为 PTS 上面向终端的程序的输入,反之亦然。

当在规范模式下运行时,PTS 的输入是逐行缓冲的。换句话说,就像常规终端一样,仅当换行符写入 PTM 时,从 PTS 读取的程序才会接收一行输入。当缓冲容量耗尽时,进一步write()调用block,直到部分输入被消耗。

在Linux内核中,文件相关的系统调用open()read()都是write() stat()在虚拟文件系统(VFS)层实现的,它为用户空间程序提供了统一的文件系统接口。 VFS 允许不同的文件系统实现在内核中共存。当用户空间程序调用上述系统调用时,VFS 会将调用重定向到适当的文件系统实现。

下面的 PTS 设备由中定义的文件系统实现/dev/pts管理,而提供 Unix98 风格设备的 TTY 驱动程序则在 中定义。devpts/fs/devpts/inode.cptmxdrivers/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_numchar_buf_ptrflag_buf_ptr 被设置为由 标识的缓冲区的开头buf_num,并被count设置为0

从内核版本 3.7 开始TTY 翻转缓冲区已替换为通过kmalloc()按环组织分配的对象。在正常情况下,IRQ 驱动的串行端口在典型速度下的行为与旧的翻转缓冲区几乎相同;两个缓冲区最终被分配,并且内核像以前一样在它们之间循环。但是,当出现延迟或速度增加时,新的缓冲区实现会表现得更好,因为缓冲池可以增长一点。

答案2

从这三个中任何一个的手册页中,它解释了答案:

   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>.

相关内容