在学完中断、系统调用后,群里有同学提出了一个问题,大家也进行了讨论。

A:刚刚开了一个脑洞,比如 C 里面写了个 scanf 然后开始运行,这个应该算是一个程序运行带来的异常还是 IO 中断。

B:我这么想,C 的 scanf 先系统调用进入内核,然后内核在这个状态等待一个 IO 中断,之后完成系统调用再返回用户态。

C:先是陷入异常 后面可能会有 DMA 传输之类的东西?所以可能还会结合中断处理。

D:感觉中断应该是外部事件,是正在运行的程序所不期望的,但是这个例子里的输入就是正在运行的这个程序所期望的,而且是这个程序内部的函数需要的,应该不算中断?

  我个人第一反应是键盘产生中断,操作系统处理后供 scanf 读取,scanf 本身没有触发中断和异常机制。于是查询资料的时候找到了 Kernel Stdio Theory 这篇文章,在理解还没到位的情况下,瞎翻译一下。

标准输入/输出原理

什么是标准输入/输出?

  标准输入输出以及标准错误都是 C 标准库流实现的一部分。流是访问文件、硬件资源、其它进程的读写接口。

  引用 stdio.h 后,三个流会自动创建并与环境的标准输入输出以及错误流连接。在大部分情况下,进程的标准输出和错误会绑定到打开它的终端上。在 Stdin 没有被重定向情况下,默认的标准输入源是键盘。

  这些效果由 C 库处理,与底层操作系统交互提供流资源的访问。流资源包含以下属性:

  • 读/写
  • 文本/二进制
  • 缓冲/无缓冲

  大部分情况,StdOut 默认是有缓冲,StdErr 无缓冲。这样可以让用户立即看到输出到 StdErr 中的数据。缓冲可以是行缓存或完全缓冲。C 标准库的复杂性不是本文的关注点。

  内核必须提供底层设备的 API 并将它们提供给正在运行的程序。所有程序,为了输入默认将键盘绑定为 StdIn,控制台绑定为 StdOut。

  由于 C 用于开发 Unix 内核,且 Unix 提供将设备抽象成一般文件的方法。进程可以简单的将设备当作资源流打开,将 StdIO 连接到设备上进行读写。

  终端是一个经典的例子,来了解 Unix 的 StdIO 如何工作的基本知识。注意,提供这种抽象不一定要通过文件。Windows API 通过函数调用提供这种抽象。实际上程序只有运行在终端下时才会使用 StdIn、StdOut 和 StdErr。对于 GUI 程序 StdIn、StdOut、StdErr 会由开发人员替换成相关 API,如弹窗用于错误和警告。

  在 Unix 内核中默认的基本输出连接到了用户的终端设备(如 /dev/tty0)。在你的 Unix/Linux 命令提示符中输入 who am i。代表当前终端的标准输出的设备。

  在微型计算机中没有终端连接到大型主机,因此现代 Linux/Unix 微型计算机只是创建了一个终端设备,并将其连接到内核的标准输出。

  执行:echo Hello from std Output! >file

  通过重定向程序的基本输出,将字符串 “Hello from std Output” 打印到名为 file 的文件中。程序获得了 file 的文件句柄作为它的 stdout 设备,并写入文件。

  返回到之前运行 who am i 或简化成 tty 的终端。将 file 替换成 tty 的输出。我的内核报告的终端是 dev/pts/1

  执行:echo Hello from std Output! >/dev/pts/1

  这次会在终端看到 echo 的输出。这是由于我们把 Stdout 的基本输出重定向回了我们的 Stdout 上。也就是例子中的 dev/pts/1,实际上它与当前用户的标准输出同义。

  有意思的是,这个文件也充当着 Stdin 设备:读取键盘输入的字符并报告给读取它的程序。

  Unix cat 命令会读取并输出文件中的内容,键盘输入会被送到程序正在读取的 Stdin。

  执行:cat /dev/pts/1

  输入单词和字符并按下回车。输入的字符会被送到 cat 正在读取的 Stdin。按下 enter 表示到达当前输入的行尾,内核停止读取键盘输入,并将这行输入送入当前被 cat 读取的标准输入。cat 认为它正在读取一个普通的文件,并完成工作:将文件内容输出到终端。

  输入完行后按下 Ctl + d。*Unix 中表示“没有更多输入/输入结尾”,处理文件时,是“文件结束”的含义。内核收到没有更多输入的指示,并转发到 Stdin,cat 识别到“文件结束”信号,认为到达当前读取文件的末尾。与所有 Unices 一样,对待,将一切当作普通文件,停止从内核读取。

  实际上动态性质的终端 StdIO 文件并不是硬盘上的普通文件,而是由内核 API 管理的 Stdin 和 Stdout 链接。

如何在内核中实现 StdIO?

简短论述设计考虑(待续)

  大多数早期开发者并没有考虑他们的内核需要为程序实现某种形式的标准输入输出。操作系统开发者最早开发的控制台驱动和键盘驱动应当成为标准输入输出的驱动程序的一部分。

  Stdin 和 Stdout 不一定必须和键盘设备、控制台屏幕或者其它屏幕输出。一个程序可能打开一个文件句柄,将其作为标准输出流写入。

  目前绝大部分内核包含作者的,在引导时实现任何形式的 Stdout 是没有意义的:除了内核没有任何程序可以使用。除此之外内核需要支持将 Stdout 绑定到多个流。

  尽管屏幕不是唯一可以绑定 StdOut 的设备或资源,但必须注意的是,从所有迹象来看,所有的 OS 开发者都会编写一个在引导时直接引用 VGA 文本模式帧缓冲区的驱动程序。这不是错误或者有害的,但当内核能够运行程序时,必须存在一个资源支持将控制台屏幕作为标准输出资源。当然,由于缺乏适当的设计,在引导后内核图形部分必须起作用时,很多人仍然没有编写 Stdout 接口。

  通常可以认为 Stdout 是程序可以访问的读写流。

  让我们做一个简单的实验来了解 *Nix 中 StdIO 终端文件会发生什么:在 *Nix 中打开两个终端窗口。一个终端输入:cat /dev/pts/1(你实际的当前终端设备)并回车确认。会发现 Cat 开始从终端中读取。

  在第二个终端输入 echo <输入随机单词>。注意当 shell 从键盘读取键盘输入时,允许通过转义来输入不可打印字符。
当输入:

  echo hello <enter> Jane<enter> I'm glad to meet you