记录学习自考课程编码13015学习过程,献给每一位拥有梦想的"带专人",今天是最后一次更新本课程啦,我也终于学完了🎉
ps:有不正确的地方麻烦更新在评论区,我会一一修复 😅
第六章 程序中的 I/O 操作实现
I/O 子系统概述
I/O 子系统主要解决信息的输入和输出问题
I/O 子系统包含 I/O 软件和 I/O 硬件两大部分。I/O 软件包括最上层提出 I/O 请求的用户空间 I/O 软件(用户I/O 软件)和在底层操作系统中对 I/O 进行具体管理和控制的内核 I/O 软件(系统 I/O 软件)
I/O子系统的三个特性:
- 共享性:I/O 子系统被多个进程共享,因此必须由操作系统对共享的 I/O 资源统一调度管理,以保证用户程序只能访问自己有权访问的那部分 I/O 设备或文件,并使系统的吞吐率达到最佳
- 复杂性:I/O 设备控制的细节比较复杂,如果由最上层的用户程序直接控制,会给广大的应用程序开发人员带来麻烦,因而需操作系统提供专门的驱动程序进行控制,这样可以对应用程序开发人员屏蔽控制设备的细节,简化应用程序的开发
- 异步性:I/O 子系统的速度较慢,而且不同设备之间的速度也相差较大,因而,I/O 设备与主机之间的信息交换方式通常使用异步中断 I/O 方式,中断导致从用户态向内核态转移,因此,I/O 处理须在内核态完成,通常由操作系统提供中断服务程序来处理 I/O
I/O 操作的执行过程
假定用户程序中调用了库函数 printf,在 printf 函数中有通过一系列的函数调用,最终转到调用 write 函数。在 write 函数对应的指令序列中,一定有一条用于系统调用的陷阱指令,在 IA-32+Linux 系统中就是指令 int $0x80或sysenter。该陷阱指令执行后,进程就从用户态陷入内核态执行。Linux 中有一个系统调用的统一入口,即系统调用处理程序 system_call。CPU 执行陷阱指令后,便转到 system_call 的第一条指令执行。在system_call中, 将根据 EAX 寄存器中的系统调用号跳转到当前系统调用对应的系统调用服务例程sys_write去执行。system_call 执行结束时,从内核态返回到用户态的陷阱指令后面一条指令继续执行
用户空间的 I/O 函数
- 标准 I/O 库函数(fread、fwrite 等)
- 系统级函数(read、write 等)
- 标准 I/O 库函数比系统调用封装函数抽象层次更高,后者属于系统级 I/O 函数,前者是基于后者实现的
- 通常情况下,C 程序员大多使用较高层次的标准 I/O 库函数,而很少使用底层的系统级I/O 函数。使用标准 I/O 库函数得到的程序
移植性较好,可以在不同体系结构和操作系统平台下运行,而且,因为标准 I/O 库函数中的文件操作使用了在内存中的文件缓冲区,使得系统调用以及 I/O 次数明显减少,所以使用标准 I/O 库函数能提高程序执行效率。不过也存在以下不足:- I/O 为同步操作,即程序必须等待 I/O操作真正完成后才能继续执行
- 在一些情况下不适合甚至完全无法使用标准 I/O 库函数实现 I/O 功能,入 C 标准 I/O 库中不提供读取文件元数据的函数
- 标准 I/O 库函数还存在一些问题,用它进行网络编程容易造成缓冲区溢出等风险,同时它也不提供对文件进行加锁和解锁功能
- 文件基本操作:创建、打开、设置读/写位置、读/写、关闭文件
使用 C 语言实现的文件复制功能
void filecopy (FILE *infp, File *outfp) {
ssize_t len;
while((len = fread(buf,1,BUFSIZ, infp)) > 0) {
fwrite(buf,1,len,outfp);
}
}
系统级 I/O 函数
- create 创建文件
- open 打开文件
- read 读文件
- write 写文件
- lseek 调整文件当前读写位置
- stat/fstat 查看文件元数据
- close 关闭文件函数
标准 I/O 库函数
-
输出缓冲区属性
- 全缓冲__IOFBF:即使遇到换行符也不会写文件,只有当缓冲区满时才会将缓冲区内容真正写入文件 fd 中
- 行缓冲__IOLBF:遇到换行符或缓冲区满就将缓冲区内容写入文件 fd
- 非缓冲__IONBF:直接写到文件 fd
普通文件的缓冲区属性为全缓冲
-
标准文件
- 标准输入 stdin
- 标准输出 stdout
- 标准错误 stderr
内核空间 I/O 软件
- 文件系统:给上层应用提供文件的抽象,提供文件创建、打开、读写、关闭需要的操作接口,将抽象文件标识与具体硬件设备关联起来,通过驱动实现系统调用接口规定的操作
- 虚拟文件系统(VFS):Linux 在
逻辑文件系统的层次上增加了一个虚拟文件系统(VFS)提供基于索引节点(inode)的一系列内存数据结构,实现对逻辑文件系统层的抽象和封装并为上层应用提供统一的文件操作接口 - inode(索引节点):
虚拟文件系统提供的索引节点,VFS中的inode用于保存每个文件的元数据信息,每个文件对应一个 inode,系统中所有打开的文件对应的组成一张所有进程可共享的 inode 表 - 系统文件表:
VFS为系统中所有打开的文件维护了一张系统文件表,因此该表也成为系统打开文件表,该表为所有进程共享,每个表项对应一个打开的文件,维护的是文件的动态信息,包括inode 指针 - 文件描述符:VFS 为每个进程维护了一个打开文件描述符表,进程打开的每个文件对应一个表项,其索引就是打开文件的
文件描述符 fd,每个表项中有一个指针指向文件系统表中对应的表项
例题:
在 Linux 系统中,假设当前文件目录中硬盘文件 test.txt 由4 个 ASCII 码字符 test 组成,下列程序的输出结果是什么
#include <stdio.h>
#include <fcnlt.h>
#include <unistd.h>
int main () {
int fd1,fd2;
char c;
fd1 = open("test.txt",O_RDONLY,0);
fd2 = open("test.txt",O_RDONLY,0);
read(fd1,&c,1);
read(fd2,&c,1);
printf("fd1=%d,fd2=%d,c=%c\n",fd1,fd2,c);
return 0;
}
Linux 中前3 个文件描述符 0、1、2 分别分配给自动打开的三种标准设备文件 stdin、stdout、stderr,而 open 函数的返回值从 3 开始分配,因此 fd1 和 fd2 分别为 3 和 4。每次打开一个文件时,Linux 的 VFS 通过路径解析找到该文件的 inode 后,除了会分配一个文件描述符以外,还会分配一个对应的系统文件表项,并对其进行初始化,将 inode 指针、打开模式等信息填入相应的字段,将当前读/写位置设为 0。因此,fd1 和 fd2 对应的系统文件表项中当前读/写位置都为 0,都指向字符串 test中的字符t。所以该程序的输出结果为 fd1 = 3,fd2 = 4,c = t
设备驱动程序
设备的 I/O 控制的三种方式
- 程序直接控制 I/O:直接通过查询程序控制主机和外设的数据交换,因此,也称为
查询或轮询方式 - 中断控制 I/O 方式:当需要进行 I/O 操作时,首先启动外设进行第一个数据的 I/O 操作,然后阻塞请求 I/O 的用户进程,并调度其他进程到 CPU 上执行,期间,外设在设备控制器的控制下工作
- DMA 控制 I/O:**直接存储器访问(DMA)**控制 I/O 方式使用专门的 DMA 接口硬件直接控制在外设和主存之间交换数据,此时数据不经过 CPU。通常把该接口硬件称为 DMA 控制器。
输入/输出设备
I/O 设备又称外围设备、外部设备,简称外设
- 字符设备:以字符为单位向主机发送或接收字符流的设备。不能形成数据块无法定位和寻址
- 输入设备:把数据、命令、字符、图像、声音等以二进制编码形式输入到计算机中
- 输出设备:把计算结果以文字、图像、声音等可以被人理解的形式输出到显示器、打印机等 I/O 设备
- 块设备:以固定大小的数据块单位与主机交换信息。块设备中的数据块大小通常在 512 字节以上,通常按照某种组织方式对其进行读/写,每个数据块都有唯一的位置信息,因而是
可寻址的。典型的块设备是外部存储器
总线互联
-
处理器总线
- 前端总线:早期 Intel 微处理器的处理器总线称为
前端总线(FSB),它是主板上最快的总线,主要用于处理器与北桥芯片之间交换信息 - QPI 总线:在 Intel 退出 Core i7时,北桥芯片的功能被集成到了 CPU 芯片内。
QPI总线是一种基于包传输的高速点对点连接协议,采用差分信号与专门的时钟信号进行传输
- 前端总线:早期 Intel 微处理器的处理器总线称为
-
存储器总线:
-
I/O 总线:
PCI-e 1.0 带宽 =
I/O 接口功能
I/O 接口的主要只能包括以下几个方面:
- 数据缓冲:主存和 CPU 寄存器的存取速度都非常快,而外设一般涉及机械操作,其速度较低,在设备控制器中引入
数据缓冲寄存器后,输出数据时,CPU 只需把数据送到数据缓冲寄存器即可;在输入数据时,CPU 只需从数据缓冲寄存器取数即可。在设备控制器控制外设与数据缓冲寄存器进行数据交换时,CPU 可以执行其他任务 - 错误和就绪检测:提供错误和就绪检测逻辑,并将结果保存在
状态寄存器,供 CPU 查用。状态信息包括各类就绪和错误信息 - 控制和定时:接受主机送来的控制信息和定时信号,根据相应的定时和控制逻辑,向外设发送控制信号,控制外设工作。主机送来的控制信息存放在
控制寄存器中 - 数据格式转换:提供数据格式的转换部件(如进行串-并转换的移位寄存器),将从外部接口接受的数据转换为内部接口所需格式,或进行反向的数据格式转换。例如,以二进制位的形式读/写磁盘驱动器后,磁盘控制器将对从磁盘读出的数据进行
串-并转换或对主机写入的数据进行并-串转换
I/O 端口及其编址
通常把设备控制器中的数据缓冲寄存器、状态/控制寄存器等统称为 I/O 端口。数据缓冲寄存器简称数据端口,状态/控制寄存器简称状态/控制端口
编址方式:
- 独立编址方式:对所有 I/O 端口单独编号,使它们成为一个与主存地址空间独立的 I/O 地址空间
- 统一编址方式(存储器映射):将 I/O 端口映射到主存空间的某段地址,所以也称为
存储器映射方式
中断系统
- 及时记录中断请求,通常用一个
中断请求寄存器来记录 - 自动响应中断请求。CPU 在
开中断状态下,执行一条指令后会自动检测中断请求引脚,发现有中断请求后会自动响应中断 - 同时有多个中断请求时,能自动选择并相应优先级最高的中断请求
- 保护被打断程序的断点和现场。
断点指被打断程序中将要执行的下一条指令的地址,由 CPU 保存,现场指被打断程序在断点处各通用寄存器的内容,由中断服务程序保存 - 通过中断屏蔽实现多重中断的嵌套执行
中断系统中有两种中断优先级:
- 中断响应优先级:由
中断查询程序或中断判优电路决定优先权,它决定多个中断同时请求先响应哪个 - 中断处理优先级:由各自的
中断屏蔽字来动态设定,决定本中断与其他所有中断之间的处理优先关系
例题:
假设在IA-32+Linux 系统中,某用户程序 P 代码:
int len,n,buf[BUFSIZ];
FILE *fp;
……
fp = fopen("bin_file.txt","r");
n = fread(buf,sizeof(int),BUFSIZ,fp);
假设文件 bin_file.txt 已经存在磁盘上且存有足够多的数据,以前未被读取过。回答下列问题或完成下列任务
-
执行第 4 行语句时,从用户程序的执行调出内核中的 I/O 软件执行的过程是怎样的?划出函数之间的调用关系,并用自然语言描述
在用户程序中调用 fopen时会转到 C 语言标准库函数 fopen 中,在标准库函数 fopen 中会调用 open 封装函数,在执行 open 封装函数中有一条自陷指令int $0x80(或sysenter),执行该指令由用户态转到内核态中执行,进入内核态首先执行 system_call 程序,该程序根据系统调用号转到对应的 open 系统调用服务例程 sys_open 执行,文件打开的具体工作由 sys_open完成,因为将要打开的文件已经存在,所以,fopen 函数将能成功执行
-
执行第五行语句时,从用户程序的执行到调出内核中的 I/O 软件执行的过程是怎样的?要求画出函数之间的调用关系
在用户程序中调用 fread 时会转到 C 语言标准库函数 fread 中,在标准库函数 fread 中会调用 read 封装函数,在执行 read 封装函数中有一条自陷指令int $0x80(或sysenter),执行该指令由用户态转到内核态中执行,进入内核态首先执行 system_call 程序,该程序根据系统调用号转到对应的 fread 系统调用服务例程 sys_read 执行,文件打开的具体工作由 sys_read 完成,因为将要打开的文件已经存在,所以,fread 函数将能成功执行
-
执行第 5 行语句时,通过陷阱指令陷入内核后,底层的内核 I/O 软件的大致处理过程是怎样的
- 根据文件描述符 fd 找到文件描述信息,根据文件描述信息找到磁盘驱动程序,根据当前文件指针确定在设备中的逻辑块号检查数据是否在缓存中,来判断是否需要读磁盘,因为文件没有被读取过,所以不会再缓存或用户缓冲区中,调用磁盘驱动程序读磁盘
- 将逻辑块号转换为磁盘物理地址,对要接受磁盘数据的主存进行初始化,对 I/O 端口进行初始化;然后发送
启动 DMA 传送命令启动具体的 I/O 操作;调用处理器调度程序挂起当前用户进程 P,并使CPU 去执行其他用户进程 - 当 DMA 控制器完成 I/O 操作后,向 CPU 发送一个
DMA 结束中断信号,CPU 调出相应的中断服务程序。CPU 在中断服务程序中,解除用户进程 P 的阻塞状态使其进入就绪队列,然后中断返回,在回到被打断的进程继续执行
捏捏捏捏捏捏捏捏捏捏捏
由于后续视频课程为付费课程,观看方式为萝卜 Bro 发送的腾讯会议链接无法分享给大家,所以还是建议大家去购买正版课程 😊😊
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉