
printf("hello")
这句话最终怎么出现在屏幕上的?

为什么要这样设计?
如果程序直接操作硬件
结果:
那操作系统作为中间层,他可以做到

虽然说程序不直接访问硬件,但驱动程序最终还是要直接和硬件说话
那驱动是如何做到的,靠的就是: in 和 out
首先来看,端口是什么

从端口读数据:
in al, 60h # 从端口 60h(键盘)读一个字节到 al 寄存器
从端口写数据:
out 378h, al # 把 al 的内容写到端口 378h(打印机)
Windows 为什么限制这两条指令?
所有 Windows 规定:只有内核态的驱动才能用 in/out,普通程序使用直接报错

CPU并不只有一种运行模式,他有两个权限级别

那 用户态程序想用 硬件 怎么办,通过 系统调用,申请内核代劳

为什么分两种模式,简单来说
新问题来了,CPU 通过端口和外设通信,但 CPU 怎么知道外设"准备好了"?
一般想法, 让CPU从端口中查看某某寄存器中是否有值
这没错,理论可行,这种方法也被称为轮询,如果用代码解释
while(true){
//检查键盘端口有没有新数据?
//检查打印机端口有没有新数据?
}
但是有个严重问题:
键盘每秒最多几个字,但CPU 每秒执行几十亿条指令
这会导致一个现象,CPU绝大部分时间都在查找,是否有新数据
那么就需要一个新的解决办法:中断
它和轮询截然相反,就相当于

假如同时有多个设备发出中断,则排队一个个交给CPU处理,由中断控制器负责排列优先级,避免 CPU 同时收到多个中断
问题来了,如果 中断处理程序忘记恢复某个寄存器,会发生什么?
无法回到原先程序?不全对,如果返回地址在栈上,就能回去,但回去之后行为错乱

而且还有个问题,我们知道,一次只处理一个字节,如果我们外设传输量很大,比如1GB,会有什么问题?
这会导致CPU中断次数过于频繁,有多夸张
每中断一次:
看到这个问题的答案能发现,中断也不够用了,那么,轮到 DMA 登场了
DMA:让外设直接和内存交换数据,绕过CPU

补充个我当时的疑惑点:为什么要经过 CPU 中转?
简单来说,外设全都是死的,只有 CPU 是唯一能执行指令和做决策的东西
CPU:
其他所有东西:
显示器并不是从 CPU 那数据,而是专门的一块内存: 显存

在早期 PC 的显存映射:
内存地址:
CPU 要在屏幕上显示字符,只需要
movl $'A', 0xA0000 # 往显存地址写字符,屏幕上就出现 A
现代显卡:
主内存(RAM) 显存(VRAM,显卡上面)
程序的数据 当前帧的画面数据
CPU 操作 GPU操作
数据通过 PCIe 总线传输
CPU -> 显卡驱动 -> GPU -> 显存 -> 屏幕
本质没变,还是 CPU 决定显示什么,写入显存,GPU负责输出到屏幕

所以,画面流程度不够,可能不只是 GPU 不够好,也可能是你的 GPU 上的显存不够用啊
最后,如果只用一句话去理解 程序与硬件的关系,那就是: 程序并不是直接去控制硬件,而是要先经过操作系统这一层,再由操作系统去协调硬件。
这一章最重要的地方,我觉得不是记住 in/out、中断、DMA 这些名字,而是开始明白一件事: 为什么程序不能随便直接碰硬件。因为一旦让所有程序都直接去抢硬件,马上就会出现互相干扰、安全问题和兼容性问题。
所以后面这一整套设计,其实都是围着这个问题展开的。用户态和内核态 是为了隔离权限,系统调用 是为了让程序通过操作系统申请服务,中断 是为了避免 CPU 一直傻等,DMA 则是在数据量太大时,继续减少 CPU 的负担。
如果说前面几章让我知道程序、操作系统和运行环境各自是什么,那这一章更像是在告诉我: 程序最后要真正影响屏幕、键盘、硬盘这些硬件时,中间到底经过了哪些层,又为什么必须经过这些层。