mirror of
https://github.com/Aexiar/c.git
synced 2024-10-22 14:05:45 +02:00
164 lines
11 KiB
Markdown
164 lines
11 KiB
Markdown
# 第一章:输入输出模型
|
||
|
||
## 1.1 回顾冯·诺依曼体系结构
|
||
|
||
* `冯·诺依曼`体系结构的理论要点如下:
|
||
- ① **存储程序**:`程序指令`和`数据`都存储在计算机的内存中,这使得程序可以在运行时修改。
|
||
- ② **二进制逻辑**:所有`数据`和`指令`都以`二进制`形式表示。
|
||
- ③ **顺序执行**:指令按照它们在内存中的顺序执行,但可以有条件地改变执行顺序。
|
||
- ④ **五大部件**:计算机由`运算器`、`控制器`、`存储器`、`输入设备`和`输出设备`组成。
|
||
- ⑤ **指令结构**:指令由操作码和地址码组成,操作码指示要执行的操作,地址码指示操作数的位置。
|
||
- ⑥ **中心化控制**:计算机的控制单元(CPU)负责解释和执行指令,控制数据流。
|
||
|
||
![img](./assets/1.png)
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 上述的组件协同工作,构成了一个完整的计算机系统:
|
||
>
|
||
> - `运算器`和`控制器`通常被集成在一起,组成中央处理器(CPU),负责数据处理和指令执行。
|
||
> - `存储器`(内存)保存数据和程序,是计算机运作的基础。
|
||
> - `输入设备`和`输出设备`负责与外界的交互,确保用户能够输入信息并接收计算机的处理结果。
|
||
>
|
||
> 直到今天,即使硬件的发展日新月异,但是现代计算机的硬件理论基础还是《冯·诺依曼体系结构》。
|
||
|
||
## 1.2 冯·诺依曼体系结构的瓶颈
|
||
|
||
* 计算机是有性能瓶颈的:如果 CPU 有每秒处理 1000 个服务请求的能力,各种总线的负载能力能达到 500 个, 但网卡只能接受 200个请求,而硬盘只能负担 150 个的话,那这台服务器得处理能力只能是 150 个请求/秒,有 85% 的处理器计算能力浪费了,在计算机系统当中,`硬盘`的读写速率已经成为影响系统性能进一步提高的瓶颈。
|
||
|
||
![](./assets/2.jpg)
|
||
|
||
* 计算机的各个设备部件的延迟从高到低的排列,依次是机械硬盘(HDD)、固态硬盘(SSD)、内存、CPU 。
|
||
|
||
![](./assets/3.png)
|
||
|
||
* 从上图中,我们可以知道,CPU 是最快的,一个时钟周期是 0.3 ns ,内存访问需要 120 ns ,固态硬盘访问需要 50-150 us,传统的硬盘访问需要 1-10 ms,而网络访问是最慢,需要 40 ms 以上。
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 时间的单位换算如下:
|
||
>
|
||
> * ① 1 秒 = 1000 毫秒,即 1 s = 1000 ms。
|
||
> * ② 1 毫秒 = 1000 微妙,即 1 ms = 1000 us 。
|
||
> * ③ 1 微妙 = 1000 纳秒,即 1 us = 1000 ns。
|
||
|
||
* 如果按照上图,将计算机世界的时间和人类世界的时间进行对比,即:
|
||
|
||
```txt
|
||
如果 CPU 的时钟周期按照 1 秒计算,
|
||
那么,内存访问就需要 6 分钟;
|
||
那么,固态硬盘就需要 2-6 天;
|
||
那么,传统硬盘就需要 1-12 个月;
|
||
那么,网络访问就需要 4 年以上。
|
||
```
|
||
|
||
> [!NOTE]
|
||
>
|
||
> * ① 这就中国古典修仙小说中的“天上一天,地上一年”是多么的相似!!!
|
||
> * ② 对于 CPU 来说,这个世界真的是太慢了!!!
|
||
|
||
* 其实,中国古代中的文人,通常以`蜉蝣`来表示时间的短暂(和其他生物的寿命比),也是类似的道理,即:
|
||
|
||
```txt
|
||
鹤寿千岁,以极其游,蜉蝣朝生而暮死,尽其乐,盖其旦暮为期,远不过三日尔。
|
||
--- 出自 西汉淮南王刘安《淮南子》
|
||
```
|
||
|
||
```txt
|
||
寄蜉蝣于天地,渺沧海之一粟。 哀吾生之须臾,羡长江之无穷。
|
||
挟飞仙以遨游,抱明月而长终。 知不可乎骤得,托遗响于悲风。
|
||
--- 出自 苏轼《赤壁赋》
|
||
```
|
||
|
||
> [!NOTE]
|
||
>
|
||
> * ① 从`蜉蝣`的角度来说,从早到晚就是一生;但是,从`人类`角度来说,从早到晚却仅仅只是一天。
|
||
> * ② 这和“天上一天,地上一年”是多么的相似,即:如果`蜉蝣`是`人类`的话,那`我们`就是`仙人`了。
|
||
|
||
* 存储器的层次结构(CPU 中也有存储器,即:寄存器、高速缓存 L1、L2 和 L3),如下所示:
|
||
|
||
![img](./assets/4.png)
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 上图以层次化的方式,展示了价格信息,揭示了一个真理,即:鱼和熊掌不可兼得。
|
||
>
|
||
> - ① 存储器越往上速度越快,但是价格越来越贵, 越往下速度越慢,但是价格越来越便宜。
|
||
> - ② 正是由于计算机各个部件的速度不同,容量不同,价格不同,导致了计算机系统/编程中的各种问题以及相应的解决方案。
|
||
|
||
* 正是由于 CPU、内存以及 IO 设备之间的速度差异,从而导致了计算机的性能瓶颈,即所谓的`“冯·诺依曼体系结构的瓶颈”`。
|
||
|
||
![](./assets/5.svg)
|
||
|
||
* 因为 CPU 的处理速度远远快于内存和 IO 设备,导致在等待数据处理和传输的时候,CPU 大部分处于空闲状态。就是这种显著的速度差异就导致了计算机的性能瓶颈,限制了整个计算机系统的效率。
|
||
|
||
> [!NOTE]
|
||
>
|
||
> * 对于硬件的这种显著的速度差异,我们程序员是无法解决的。
|
||
> * 但是,为了平衡三者之间的速度鸿沟,我们可以通过引入`缓冲区`技术,来降低系统的 IO 次数,降低系统的开销。
|
||
|
||
* 其实,在硬件上也是有`缓冲区`的,即:CPU 内部集成了缓存,将经常使用到的数据从内存中加载到缓存中。
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 对于缓存和内存中数据的同步解决方案,会有各种各样的算法,如:LRU 等。
|
||
|
||
![](./assets/6.svg)
|
||
|
||
## 1.3 缓冲区
|
||
|
||
### 1.3.1 如果存在缓冲区,键盘输入的数据是怎么到达程序的?
|
||
|
||
* 当我们在键盘上输入数据并传递给程序时,通常会经历如下的几个步骤:
|
||
* ① `键盘生成输入信号`:当我们在键盘上按下某个键的时候,键盘会将这个动作转换为对应的电信号,传递给键盘控制器。
|
||
* ② `键盘控制器发送中断信号`:计算机的`键盘控制器`会检测到按键动作,向 CPU 发送中断请求。
|
||
* ③ `CPU 执行中断处理程序`:CPU 暂停当前任务,进入中断处理状态,操作系统的中断处理程序接收并处理键盘输入。
|
||
* ④ `操作系统将输入存入缓冲区`:键盘输入的数据被存入`内存缓冲区`,操作系统会将这些数据暂时存放在缓冲区中,等待程序从缓冲区中读取数据。
|
||
* ⑤ `程序读取数据`:程序通过读取函数从缓冲区读取数据进行处理。
|
||
|
||
|
||
* 其对应的图示,如下所示:
|
||
|
||
![](./assets/7.png)
|
||
|
||
> [!IMPORTANT]
|
||
>
|
||
> 其实,C 语言中的 `printf` 函数和 `scanf` 函数,其内部就使用了缓冲区。
|
||
>
|
||
> * ① 当我们使用 `printf` 函数输出数据的时候,数据并不会立即就写出到输出设备(如:屏幕等)。而是先将其放置到 `stdout 缓冲区`中,然后在满足条件的时候,再从缓冲区中刷新到输出设备。
|
||
> * ② 当我们使用 `scanf` 函数输入数据的时候,数据并不会立即就从输入设备中读取(如:键盘等)。而是先将其放置到 `stdin 缓冲区`中,然后在满足条件的时候,再从缓冲区中加载数据。
|
||
|
||
### 1.3.2 如果没有缓冲区,键盘输入的数据是怎么到达程序的?
|
||
|
||
* 当我们在键盘上输入数据并传递给程序时,通常会经历如下的几个步骤:
|
||
* ① `键盘生成输入信号`:当我们在键盘上按下某个键的时候,键盘会将这个动作转换为对应的电信号,传递给键盘控制器。
|
||
* ② `键盘控制器发送中断信号`:键盘控制器检测到按键动作,向 CPU 发送`中断请求`,通知操作系统有输入数据。
|
||
* ③ `操作系统处理输入`:操作系统接收到`中断信号`后,立即获取键盘数据并处理。由于没有缓冲区,操作系统必须将数据立即传递给程序。
|
||
* ④ `程序直接读取数据`:程序必须在键盘每次输入后立即读取数据,并且处理这个输入,不会有任何数据被暂存或积累。
|
||
|
||
> [!NOTE]
|
||
>
|
||
> 如果没有缓冲区,键盘输入的数据将无法有效地被程序管理和处理,系统的工作效率会显著下降,具体影响体现在以下几个方面:
|
||
>
|
||
> * ① `程序与设备的频繁交互`:在没有缓冲区的情况下,程序需要直接与键盘设备进行交互。这意味着每次按键输入,操作系统都必须立即将数据传递给程序处理。这样会带来以下问题:
|
||
> * **频繁的 I/O 操作**:每一次键盘输入都会触发一个 I/O 操作,将数据直接传输给程序。程序必须每次都立即响应输入设备,执行读操作,导致程序处理器频繁被中断。
|
||
> * **实时响应要求**:程序需要时刻等待并响应输入,哪怕是输入非常小的数据(比如一个字符),程序都必须立即读取并处理。这对程序的设计提出了很高的实时性要求,可能会降低程序的运行效率。
|
||
> * ② `处理效率低下`:由于没有缓冲区,程序无法积累多个输入数据再进行批量处理。每一次输入必须立即处理,程序执行的效率会受到影响:
|
||
> - **I/O 阻塞**:程序可能会因为等待输入设备的响应而阻塞。没有缓冲区的情况下,程序不能继续执行其他任务,必须等待每一次输入完成后才能继续执行其他操作。
|
||
> - **浪费系统资源**:程序频繁地切换到处理I/O操作,导致处理器资源被大量占用。在处理较大数据量时,这种方式的效率极低,容易造成资源浪费。
|
||
> * ③ `用户体验差`:从用户角度来看,程序对键盘输入的响应会显得非常僵硬,无法处理多个输入操作的积累:
|
||
> - **输入延迟**:程序必须实时处理每个键盘输入,用户输入数据的速度一旦超过程序的处理能力,可能导致输入延迟或丢失输入。
|
||
> - **无法处理复杂输入**:如果用户需要输入多个字符或进行复杂的输入操作(比如连续输入多个命令),程序可能难以一次性正确处理,因为它只能逐一处理每一个输入,而无法一次性获取多个输入进行批量处理。
|
||
|
||
* 其对应的图示,如下所示:
|
||
|
||
![](./assets/8.png)
|
||
|
||
> [!IMPORTANT]
|
||
>
|
||
> 其实,C 语言中的 `printf` 函数和 `scanf` 函数,其内部就使用了缓冲区。
|
||
>
|
||
> * ① 当我们使用 `printf` 函数输出数据的时候,数据并不会立即就写出到输出设备(如:屏幕等)。而是先将其放置到 `stdout 缓冲区`中,然后在满足条件的时候,再从缓冲区中刷新到输出设备。
|
||
> * ② 当我们使用 `scanf` 函数输入数据的时候,数据并不会立即就从输入设备中读取(如:键盘等)。而是先将其放置到 `stdin 缓冲区`中,然后在满足条件的时候,再从缓冲区中加载数据。
|
||
|