1. IO控制器
- CPU无法直接控制IO设备,IO设备由IO控制器来控制,但CPU可以控制IO控制器
- CPU可以控制IO控制器,IO控制器来控制IO设备
1.1 IO控制器的组成
- CPU与控制器之间的接口
- IO逻辑
- 控制器与设备之间的接口(一个控制器可以连接多个设备)
1.2 IO控制器的功能
- 接收设备CPU指令:CPU的读写指令和参数存储在控制寄存器中。
- 向CPU报告设备的状态:IO控制器中会有相应的状态寄存器,用于记录IO设备的状态。(如某位置为1表示忙碌,置为0表示就绪)
- 数据交换:数据寄存器,暂存CPU发来的数据和设备发来的数据
- 地址识别:给每个寄存器编址,IO控制器可以通过CPU提供的地址来判断CPU是要读写哪个就寄存器。
2. 程序直接控制IO方式
- 通过系统调用write进入内核态
- CPU向IO控制器发出读指令,于是IO控制器启动具体设备,并将状态寄存器设置为1(表示数据未就绪)
- CPU不断轮询检查状态寄存器是否已经变成0
- IO设备准备好数据后,将数据传递控制器,并报告自身状态
- 控制器将设备输入的数据放到数据寄存器中,然后将状态变为0(已就绪)
- 此时CPU发现状态寄存器已经变为0,表明数据已经就绪,则CPU会将数据寄存器中的数据读入CPU的寄存器中。
时间 | CPU | IO控制器 |
1 | CPU向IO控制器发送读命令(将控制指令写入IO控制器的控制寄存器中) | |
2 | CPU不断轮询IO控制器的状态寄存器是否改为0 | IO控制器向具体的设备发出读请求,并将自身的状态寄存器设为1,表示未就绪 |
3 | 输入设备准备好数据后将数据传递给控制器,并报告自身的状态 | |
4 | IO控制器将输入的数据放到数据寄存器中,同时将状态寄存器改为0,表示数据已就绪 | |
5 | CPU检测的IO控制器的状态寄存器改为0,得知数据已就绪,则将IO控制器中的数据寄存器中的数据读入CPU的寄存器 |
2.1 特点
- 实现简单,在读写指令之后,加上实现循环检查的一系列指令即可
- CPU不断轮寻,长期处于忙等,CPU利用率低。
- 一次传输一字的数据,如键盘。
3. 中断驱动IO
时间 | CPU | IO控制器 |
1 | CPU向IO控制器发送读命令(将控制指令写入IO控制器的控制寄存器中) | |
2 | CPU切换线程/进程,处理其他事情。 | IO控制器向具体的设备发出读请求,并将自身的状态寄存器设为1,表示未就绪 |
3 | 输入设备准备好数据后将数据传递给控制器,并报告自身的状态 | |
4 | IO控制器将输入的数据放到数据寄存器中,同时将状态寄存器改为0,表示数据已就绪 | |
5 | IO控制器向CPU发出IO中断请求 | |
6 | CPU处理IO中断请求,检查IO控制器的状态,得知数据已经就绪,则将IO控制器中的数据寄存器中的数据读入CPU的寄存器 |
3.1 特点
- CPU不用轮寻忙等,在数据未准备好可以去处理其他任务。CPU与IO控制器可以并行工作。
- 一次还是只能传输一个字的数据
- 用于字符设备的输入
4. DMA控制IO
4.1 当有了DMA控制器之后
- CPU不直接控制IO控制器了,CPU控制DMA控制器
- DMA控制器去控制IO控制器
- IO控制器再去控制具体IO设备
4.2 DMA控制器的组成
- DR:暂存从设备到内存或从内存到设备的数据
- MAR:输入时表示数据应存放内存的什么地方,输出时表示要输出的内容在内存的什么地方
- DC:数据计数器,表示还有多少字节的数据要读写
- CR:命令/状态寄存器,用于存放CPU发来的IO命令或设备的状态
4.3 DMA控制IO的流程
时间 | CPU | DMA控制器 |
1 | CPU向DMA控制器发送读命令,并告知DMA要读入的数据放在内存的起始地址,以及要读入多少字节(要满足每块字节的倍数) | |
2 | CPU切换线程/进程,处理其他事情。 | DMA控制IO控制器,进行相关的读取工作 |
5 | DMA控制器完成工作后向CPU发出IO中断请求 | |
6 | CPU根据IO中断请求得知数据已经读入内存。然后根据需要再继续下一轮数据读取 |
4.4 特点
- 数据直接从IO设备读入内存,不需要借助CPU中转
- 一次可以读入一块或连续多块
- 用于块设备的IO读取。如读写磁盘。
5. IO缓冲区
5.1 从一次read操作分析IO缓冲区
- 用户进程第一次调用fgetc读一个字节
- fgetc函数通过系统调用read进入内核态
- cpu在内核态通过给DMA发送读取数据命令(然后CPU可以切换到其他任务上去执行)
- DMA利用IO控制器从磁盘读入1k字节的数据到内核缓冲区中
- DMA通过中断告知CPU读取数据已经完成
- CPU得知数据读取已经完成,它会将内核缓冲区中的1k数据复制C标准库为其打开的文件分配的IO缓冲区中。该IO缓冲区处于用户态(CPU完成从内核态到用户态切换)
- 返回给用户进程该IO缓冲区的第一个字节,并将读写位置移动到第二个字节
- 用户进程第一次调用fgetc函数完成。
- 用户第二次调用fgetc函数读取1个字节
- fgetc函数直接从该文件分配的IO缓冲区中取出第二个字节,并将读写位置移动到第三个字节处
- 用户进程第二次调用fgetc函数完成。(第二次调用过程中没有发生模式切换)
5.2 上述过程中用到的缓冲区
- 内核缓冲区
- 磁盘文件读取时,首先会将部分数据读入内核缓冲区。不能直接读入用户空间的IO缓冲区
- 由磁盘数据拷入内核缓冲区,这个过程CPU可以交给DMA来完成,DMA只需要完成时给CPU一个通知即可。
- C标准库的IO缓冲区
- 他是属于用户空间的缓冲区
- 它需要CPU将内核缓冲区的内容拷贝到用户空间的IO缓冲区
- 由内核缓冲区到C标准库的IO缓冲区发生了模式切换
- 还有一种用户程序定义的缓冲区
- 程序中定义的用于接收数据的数组等
- 他们也算一种缓冲区。用于从C标准库的IO缓冲区中复制多个字节到用户定义到数组中。
- 由C标准库的IO缓冲区到用户自定义的缓冲区之间没有发生模式切换。
5.3 C标准库的IO缓冲区的缓冲区性质
- 全缓冲
- 如果缓冲区写满了就写入内核缓冲区。
- 常规文件通常都是全缓冲
- 行缓冲
- 如果用户程序写的IO缓冲区的数据有换行,就把这一行写入内核缓冲区;或者用户IO缓冲区写满了,也会将IO缓冲区的数据写入内核缓冲区
- 标准输入和标准输出对应终端设备时通常是行缓冲的
- 无缓冲
- 用户程序每次调用标准库函数做写操作时,都要通过系统调用写会内核。
- 标准错误输出通常是无缓冲的
- 补充
- 调用标准库函数的写操作,一般都是写入内核缓冲区。当写入到内核缓冲区完成,则写操作就会返回。
- 内核缓冲区的数据什么时候写入到磁盘是由操作系统控制的。
- sync函数:只能保证标准库IO缓冲区数据刷新到内核缓冲区
- fsync函数:保证将标准库IO缓冲区数据刷新到磁盘文件
- flush函数:java中的,是对sync函数的封装,只保证刷新到内核缓冲区。