下一个 上一个 目录

3. 硬件如何传输字节

以下是对此主题的介绍,但要进行更高级的处理,请参阅 FIFO

3.1 发送

发送是指将字节从串口发送到计算机外部(输出)。一旦你理解了发送,接收(输入)就很容易理解了,因为它很相似。这里给出的第一个解释将被大大简化。然后在后续的解释中将添加更多细节。当计算机想要通过串口(到外部电缆)发送一个字节时,CPU 将字节通过计算机内部的总线发送到串口的 I/O(输入输出)地址。I/O 通常简写为 IO。串口接收该字节,并将其一次一位(串行比特流)地在串行电缆连接器的发送引脚上发送出去。有关比特(和字节)在电气上的样子,请参阅 电压波形

以下是对上述内容的更详细(但仍然很不完整)的重述。串口的大部分工作是由 UART 芯片(或类似芯片)完成的。要发送一个字节,串行设备驱动程序(在 CPU 上运行)将一个字节发送到串口的 I/O 地址。该字节进入串口中 1 字节的“发送移位寄存器”。从这个移位寄存器中,字节的位被逐个取出,并在串行线上逐位发送出去。然后,当最后一个比特被发送出去,并且移位寄存器需要另一个字节来发送时,它可以直接请求 CPU 向其发送另一个字节。这样做很简单,但可能会引入延迟,因为 CPU 可能无法立即获取字节。毕竟,CPU 通常在做除了处理串口之外的其他事情。

消除这种延迟的一种方法是进行安排,使 CPU 在移位寄存器需要字节之前获取字节,并将其存储在串口缓冲区(在硬件中)。然后,当移位寄存器发送完其字节并立即需要新字节时,串口硬件只需将下一个字节从其自身的缓冲区传输到移位寄存器。无需调用 CPU 来获取新字节。

串口缓冲区的大小最初只有一个字节,但今天通常是 16 个字节(在价格更高的串口中更多)。现在仍然存在如何保持这个缓冲区充分供应字节的问题,以便当移位寄存器需要一个字节来发送时,它总能在那里找到一个字节(除非没有更多的字节要发送)。这是通过使用中断联系 CPU 来完成的。

首先,我们将解释旧式的单字节缓冲区的情况,因为 16 字节缓冲区的工作方式类似(但更复杂)。当移位寄存器从缓冲区中抓取字节并且缓冲区需要另一个字节时,它会通过在计算机总线上的专用导线上施加电压来向 CPU 发送中断。除非 CPU 正在做非常重要的事情,否则中断会强制它停止正在做的事情,并开始运行一个程序,该程序将向端口的缓冲区提供另一个字节。此缓冲区的目的是在硬件中保持一个额外的字节(等待发送)排队,以便从串口电缆发送字节时不会出现间隙。

一旦 CPU 收到中断,它就会知道是谁发送了中断,因为每个串口都有一条专用的中断线(除非中断是共享的)。然后,CPU 将开始运行串行设备驱动程序,该程序检查 I/O 地址处的寄存器以 выяснить 发生了什么。它 выясняет 串口的发送缓冲区为空,正在等待另一个字节。因此,如果有更多字节要发送,它会将下一个字节发送到串口的 I/O 地址。当上一个字节仍在发送移位寄存器中并仍在逐位传输时,下一个字节应该到达。

回顾一下,当一个字节已完全从串口的发送线发送出去,并且移位寄存器现在为空时,以下 3 件事会快速连续发生

  1. 下一个字节从发送缓冲区移动到发送移位寄存器
  2. 这个新字节的传输(逐位)开始
  3. 发出另一个中断,告诉设备驱动程序向现在为空的发送缓冲区发送又一个字节

因此,我们说串口是中断驱动的。每次串口发出中断时,CPU 都会向其发送另一个字节。一旦 CPU 将一个字节发送到发送缓冲区,CPU 就可以自由地进行其他活动,直到它收到下一个中断。串口以用户(或应用程序)选择的固定速率传输比特。它有时被称为波特率。串口还在每个字节中添加额外的比特(起始位、停止位,可能还有奇偶校验位),因此通常每个字节发送 10 个比特。在 19,200 比特每秒 (bps) 的速率(也称为速度)下,因此有 1,920 字节/秒(以及 1,920 个中断/秒)。

完成所有这些工作对于 CPU 来说是很大的工作量。这是因为很多原因。首先,仅仅通过 32 位数据总线(甚至 64 位)一次发送一个 8 位字节并不是非常有效地利用总线宽度。此外,处理每个中断也有很多开销。当收到中断时,设备驱动程序只知道串口发生了中断,但不知道是因为发送了字符。设备驱动程序必须进行各种检查以 выяснить 发生了什么。同一个中断可能意味着收到了一个字符,某个控制线改变了状态等等。

一个主要的改进是将串口的缓冲区大小从 1 字节扩大到 16 字节。这意味着当 CPU 收到中断时,它会给串口最多 16 个新字节来发送。这样可以减少需要处理的中断次数,但数据仍然必须一次一个字节地通过宽总线传输。16 字节缓冲区实际上是一个 FIFO(先进先出)队列,通常称为 FIFO。有关 FIFO 的详细信息以及对上述某些信息的重复,请参阅 FIFO

3.2 接收

串口接收字节类似于发送字节,只是方向相反。它也是中断驱动的。对于具有 1 字节缓冲区的过时类型的串口,当从外部电缆完全接收到一个字节时,它会进入 1 字节的接收缓冲区。然后,端口向 CPU 发送中断,告诉它获取该字节,以便串口有空间存储当前正在接收的下一个字节。对于具有 16 字节缓冲区的较新串口,此中断(用于获取字节)可能会在接收缓冲区中有 14 个字节后发送。然后,CPU 停止正在做的事情,运行中断服务例程,并从端口获取 14 到 16 个字节。对于在收到第 14 个字节时发送的中断,如果自中断以来又到达了 2 个字节,则可能会获得 16 个字节。但是,如果应该到达更多 3 个字节(而不是 2 个),则 16 字节缓冲区将溢出。它也可能通过设置或由于超时而获取少于 14 个字节。有关更多详细信息,请参阅 FIFO

3.3 大型串口缓冲区

我们已经讨论了小型 16 字节串口硬件缓冲区,但主内存中也有更大的缓冲区。当 CPU 从硬件的接收缓冲区中取出一些字节时,它会将它们放入主内存中更大的(例如 8k 字节)接收缓冲区中。然后,一个从串口获取字节的程序从该大型缓冲区中取出它正在接收的字节(在程序中使用“读取”语句)。

对于要发送的字节,情况类似。当 CPU 需要获取一些要发送的字节时,它会从主内存中的大型(8k 字节)发送缓冲区中取出它们,并将它们放入硬件中的小型 16 字节发送缓冲区中。当程序想要通过串口发送字节时,它会将它们写入到这个大型发送缓冲区。

这两个缓冲区都由串行驱动程序管理。但是驱动程序不仅仅处理这些缓冲区。它还对通过这些缓冲区的数据进行有限的过滤(小的修改),并监听某些控制字符。所有这些都可以使用 Stty 程序进行配置。


下一个 上一个 目录