1.7 我是一个键盘

把设备事件、控制器缓冲、总线、轮询、中断和 DMA 放进同一个输入传递合同。

学习目标

  • 能沿键盘、控制器、总线、驱动和进程追踪一次输入事件
  • 能比较轮询、中断和 DMA 的 CPU 成本、延迟、缓冲和责任边界
  • 能用正常、边界和单一故障样本定位丢键、重复消费、溢出和通知丢失

1.7 我是一个键盘

本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 1.7 我是一个键盘。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。

键盘不是 CPU 的一个变量,而是一条设备输入链:按键产生事件,控制器暂存,设备通过总线和端口暴露状态,内核驱动搬运并解释,进程最终消费。轮询让 CPU 主动检查,中断在事件到达时通知,DMA 适合批量搬运;三者都不能跳过驱动、权限和缓冲边界。

三个会让设备输入失真的陷阱

五个词组成输入事件合同

<Term def="由键盘或其他设备产生、带有时间和状态的可观察输入变化,而不是应用已经解释后的字符。">设备事件</Term>是链路起点。事件可能丢失、重复或合并,必须有序列和缓冲证据。

<Term def="保存设备状态和待处理输入,并由驱动按协议读取、确认和释放的硬件或内核结构。">控制器缓冲</Term>决定突发输入能否被吸收。缓冲满时要有明确丢弃或背压规则。

<Term def="连接设备、控制器、CPU 和内存的传输路径,以及设备状态可访问的端口或寄存器边界。">总线和端口</Term>定义谁能读写什么。它不是应用可以随意调用的函数。

<Term def="由 CPU 主动重复检查设备状态并读取数据的输入方式;成本取决于检查次数、单次成本和事件密度。">轮询</Term>适合简单或高频场景,但要显式控制忙等和空缓冲。

<Term def="设备完成或需要服务时通知处理器,再由内核驱动确认、搬运和唤醒消费者的机制。">中断与 DMA</Term>把通知和批量搬运分开:DMA 不负责解释应用语义,中断也不自动交付数据给进程。

五个词形成输入链:设备事件进入控制器缓冲,经总线和端口可见,再由轮询或中断触发驱动处理,必要时用 DMA 批量搬运,最后交给进程消费。

输入事件链:每次交接都要留下证据通知不等于消费,搬运不等于解释,所有权必须在边界处交接1设备事件按键与时间生产证据2控制器状态与缓冲生产证据3总线/端口路径与权限生产证据4驱动确认与入队当前交接点5进程消费与回报消费证据轮询主动检查;中断负责通知;DMA 负责批量搬运——三者都不能越过驱动边界
专属图示:把设备、内核和进程放回同一条输入传递合同。

目录节点到机制证据

二等公民

设备在计算机系统中不是“附带的输入源”,它拥有自己的时序、缓冲和故障边界。二等公民的视角要求我们承认设备、驱动和进程各有责任,不把 CPU 的视角当成全系统真相。

总线和端口

总线和端口定义数据、状态和控制如何跨越设备与主机边界。复核时记录端口、寄存器、方向、权限和确认顺序,避免把硬件状态与用户字符混成一层。

轮询(程序式I/O)

轮询(程序式I/O)由 CPU 主动检查设备。它可以简单、可预测,但在事件稀疏时浪费检查,在突发输入时仍需要缓冲和溢出处理;CPU 成本可用检查次数乘单次检查成本估计。

中断

中断在设备需要服务时通知处理器,内核再进入驱动路径。中断不是应用回调,通知到达也不保证数据已被安全消费;驱动要确认、读取、排队并唤醒正确的进程。

DMA

DMA 让设备和内存之间批量搬运数据,减少 CPU 逐字节参与。它要求明确缓冲区地址、长度、方向、完成通知、缓存可见性和所有权交接。

输入传递合同

type InputTransfer = {
  eventId: string;
  controllerBuffer: string;
  transport: "poll" | "interrupt" | "dma";
  driverAction: string;
  consumer: string;
  firstDeviation: string;
  recovery: string;
  decision: "consume" | "backpressure" | "drop";
};
 
function canDeliver(input: InputTransfer) {
  return (
    input.eventId.length > 0 &&
    input.controllerBuffer.length > 0 &&
    input.driverAction.length > 0 &&
    input.consumer.length > 0 &&
    input.firstDeviation.length > 0
  );
}

合同把通知、搬运和消费分开。只要事件身份、缓冲、驱动动作或消费者不清楚,就选择 backpressure 或 drop,并保留恢复证据;不能因为中断发生或 DMA 完成就宣布应用已经收到正确字符。

四步复核设备输入

分步1 / 4

1. 捕获事件并检查控制器

固定按键序列、时间、控制器缓冲和端口状态,预测空缓冲、单事件和突发事件会怎样变化。先确认事件身份,不从最终字符倒推输入。

输入事件链:每次交接都要留下证据通知不等于消费,搬运不等于解释,所有权必须在边界处交接1设备事件按键与时间生产证据2控制器状态与缓冲生产证据3总线/端口路径与权限生产证据4驱动确认与入队当前交接点5进程消费与回报消费证据轮询主动检查;中断负责通知;DMA 负责批量搬运——三者都不能越过驱动边界
专属图示:把设备、内核和进程放回同一条输入传递合同。

Lab

输入事件样本台

只改变一个条件,观察事件身份、缓冲状态、通知和所有权如何改变判定。

三次按键,三次有序消费

event-17 → buffer-17 → driver-17 → process-17

判定

consume:保留顺序和完成记录

当前样本:正常输入;复核时只改变一个条件,并保存首个异常前后的队列记录。

正常、边界与单一故障证据

证据矩阵:从第一处偏差判断处理方式正常样本看顺序,边界样本看停止,故障样本看恢复观察项正常边界故障事件序列连续突发增加重复或丢失通知及时到达合并延迟通知缺失缓冲容量足够接近上限越界或旧读所有权驱动→进程回压等待提前交接先固定首个异常,再决定消费、回压、丢弃或恢复
专属图示:同一条输入链,要分别在正常、边界和故障样本中验证。
样本只改变的变量预期判定必存证据
正常事件、缓冲、通知、驱动和消费完整字符顺序可解释,CPU 成本可估计事件、队列、通知、消费记录
边界突发速率、缓冲容量、轮询间隔或批量大小越过阈值回压或明确丢弃,不假装全收阈值、丢失策略、恢复结果
单一故障一个端口、通知、DMA 批次或驱动确认失效首个输入异常暴露并恢复首差、所有权、回放日志

最小输入证据包与反例

证据包包含设备事件序列、控制器和端口状态、传输方式、CPU 检查或中断记录、DMA 缓冲、驱动版本、进程队列、消费顺序、丢失与恢复策略。最终字符正确或 CPU 占用下降,只能辅助定位,不能独立证明事件链可靠。

反例可以是高频输入下轮询丢键,也可以是 DMA 完成通知先于缓存可见性导致进程读旧数据。发现反例后,保留事件和缓冲轨迹,调整间隔、批量、确认或所有权,再重放;不要用平均吞吐掩盖少数输入损失。

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

设备事件

由设备产生、带有时间和状态的可观察输入变化。

控制器缓冲

保存设备状态和待处理输入,并由驱动按协议读取、确认和释放的结构。

总线和端口

连接设备、控制器、CPU 和内存的传输路径及设备状态边界。

轮询

由 CPU 主动重复检查设备状态并读取数据的输入方式。

中断与 DMA

设备通知处理器并在需要时批量搬运数据的两类协作机制。

练习

练习

问题 1: 低频按键用轮询时 CPU 占用很高,但改成中断后偶尔丢输入。你先看哪些证据?

问题 2: DMA 把一批数据搬完了,但进程读到旧内容。可能缺哪类合同?

问题 3: 控制器缓冲容量小于突发输入,系统不能保证全部按键都保存。如何做出诚实的决定?

资料与写作方式声明

本章以码农翻身权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

本页小结

1.7 我是一个键盘的关键不是记住某个设备名词,而是能从设备事件、控制器缓冲、总线和端口一路追到轮询或中断、DMA、驱动和进程消费。面对突发、溢出和通知故障,保留队列、所有权和首差证据,才能诚实决定消费、回压还是丢弃。

讨论

评论区加载中…