I/O系统 - scanf执行过程

源自26考研王道基础课及强化课(含笔者自己总结部分)

在讨论之前,先补充两个概念:内核缓冲区(就是处于内核空间的缓冲区)和与之对应的用户缓冲区。

在scanf函数执行的第一阶段,就申请了用户缓冲区buf,由于buf位于用户空间中,而接下来需要在内核空间中运行,这两个空间是相互独立的。内核空间用来存放操作系统的代码和数据,是供所有进程共享的。

系统调用函数运行在内核态,因此要先把I/O端口中的数据复制到内核空间中,在系统调用返回前,再将数据从内核空间复制到用户空间。

程序调用“scanf("%c",&d)”时,尝试用键盘输入对变量d进行赋值。scanf()会关联一个用户缓冲区buf,这个缓冲区是C语言函数库在用户空间中管理的。

scanf()的第一阶段的工作是在C语言函数库中完成的:

  • 1.检查与scanf函数关联的用户缓冲区buf,若缓冲区中已有数据,则直接读取。若缓冲区为空,则触发系统调用read,以从内核缓冲区中读取数据。

  • 2.执行系统调用read,read 调用会在执行trap指令前传入本次调用的三个参数

fd:文件描述符,指明输入设备

buf:用户空间的缓冲区,数据从内核缓冲区被复制到这个缓冲区

count:读取的最大字节数

第一阶段都相同,第二阶段开始不同

1.中断方式下的执行过程

第二阶段的工作是系统调用,read调用会展开成一段包含陷阱指令的代码,从用户态陷入内核态以执行相关的操作。

image-20251011213440983

image-20251011213500591

进入内核态后,系统调用服务例程申请一个内核缓冲区,并且最终转到真正执行I/O操作的设备驱动层。在中断方式下,系统调用服务例程执行过程的大致描述如下:

1.进入read系统调用后会进入内核,然后转而执行一段内核程序

read指令会指明从哪个设备读(提供逻辑设备名),而设备无关软件会检查一下当前进程有没有权限读取键盘并把逻辑设备名映射为物理设备,然后就可以找到物理设备对应的驱动程序,进而执行驱动程序

驱动程序等待键盘输入并阻塞本进程,即进程从运行态转为阻塞态,CPU调度其他进程运行

2.用户通过键盘输入字符,字符被送到键盘I/O接口的数据端口

3.键盘I/O接口向CPU发出中断请求(或者说发送一个中断信号),并指明中断号是多少

4.CPU响应中断(根据中断号去查中断向量表从而得到中断处理程序的地址)并执行键盘中断处理程序,但是中断处理程序并不了解输入的数据在哪,所以中断处理程序在背后调用驱动程序将IO接口数据端口的字符送入CPU的寄存器中(比如AL寄存器),驱动程序再进一步调用设备独立性软件再将字符从CPU的AL寄存器送入内核缓冲区(因为只有设备独立性软件才知道内核缓冲区在啥地方)

中断处理程序的作用就是调用了驱动程序

但是注意,中断处理程序是由CPU启动的,不是驱动程序,详情见23年408考题46题第三问

5.设备独立性软件调用操作系统的一些功能检查进程阻塞队列,找到被阻塞的进程p,由于键盘有字符输入,进程P被唤醒,插入就绪队列,等待被调度

6.进程P再次获得CPU后,系统调用服务例程将字符从内核缓冲区复制到用户缓冲区

7.进程P从系统调用返回,之后scanf函数进行字符解析,最终将该字符存储到变量d

2.程序直接控制方式

1.进入read系统调用后会进入内核,然后转而执行一段内核程序

CPU轮询检查,检查IO接口的状态寄存器,看看状态是否已就绪(轮询这段程序是写在键盘驱动程序中的,也就是说轮询检查这个动作是驱动程序来完成的)

2.用户敲击一个字符之后,IO接口的状态寄存器状态改变,然后退出轮询

3.由驱动程序把字符从IO接口的数据寄存器输入到CPU的寄存器中(比如AL寄存器),再把这个字符(其实键盘输入的是二进制比特)映射为ASCII码

4.驱动程序把ASCII码交付给上一层的设备独立性软件,设备独立性软件把它从CPU的寄存器写到内核缓冲区中,

5.系统调用服务例程将字符从内核缓冲区复制到用户缓冲区

6.进程P从系统调用返回,之后scanf函数进行字符解析,最终将该字符存储到变量d

在这个过程中进程没有进入过阻塞态,在CPU忙等的时候是运行态,在时间片到了以后,变为就绪态

3.DMA方式

一、系统调用与DMA初始化(预处理阶段)

1.CPU执行 read系统调用,陷入内核态。

2.内核的设备独立性软件进行权限检查等常规工作。

3.CPU不是去直接控制键盘,而是对DMA控制器进行初始化,由驱动程序完成。

CPU通过指令向DMA控制器发送以下参数:

  • 操作命令:指明是读操作还是写操作。

  • 内存起始地址:数据需要被写入的内存地址(即内核缓冲区的地址),是物理地址不是内存地址。

  • 外设地址:要读取的I/O设备(键盘控制器)的地址。

  • 传输字节数:要读取的数据量。对于键盘输入,这个值通常很小(比如1字节),但这正是DMA用于键盘低效的原因之一。

4.初始化完成后,CPU将后续的传输任务委托给DMA控制器,然后立即返回,继续执行原来的程序或其他程序此时,等待键盘输入的进程P会被阻塞

二、DMA传输阶段

5.等待输入:DMA控制器监控着键盘I/O接口。

6.数据就绪:用户按下按键,字符数据进入键盘控制器的数据寄存器。

7.DMA请求:键盘I/O接口向DMA控制器(而非CPU)发出DMA请求。

8.周期挪用:DMA控制器接收到请求后,向CPU申请总线的使用权。CPU在执行指令的间隙(通常是在一个总线周期结束时),如果不需要使用总线,会批准DMA的请求,将总线控制权暂时交给DMA控制器。这个过程称为周期挪用周期窃取

9.直接传输:DMA控制器获得总线控制权后,直接控制数据从键盘控制器的数据寄存器传输到指定的内核缓冲区中。这个过程完全不需要CPU参与

10.修改参数:DMA控制器完成一个字的传输后,会自动修改内存地址指针和字节计数器。

11.循环判断:DMA控制器检查本次传输是否完成(即字节计数器是否减到0)。由于键盘输入通常一次只传一个字符,所以一次传输后就可能完成。

三、传输结束与中断(后处理阶段)

12.当DMA控制器完成了指定数据量(例如一个字符)的传输后,它向CPU发出一个中断信号

13.CPU响应中断,执行DMA中断服务程序。

14.中断服务程序进行后处理:检查DMA控制器的状态,确认传输成功,并唤醒之前被阻塞的等待键盘输入的进程P

四、数据返回用户空间

15.进程P被调度执行后,从 read系统调用返回。系统调用服务例程将字符从内核缓冲区复制到 scanf用户缓冲区

16.控制权返回用户态,scanf从用户缓冲区中取出字符,解析后存入变量 d

在内核中,DMA缓冲区的分配与回收主要是由设备驱动程序发起和执行的,但它会调用由设备独立性软件提供的一套标准接口来完成实际工作。两者是紧密协作的关系。

总结:

程序直接控制方式和中断驱动方式都需要先把数据从IO接口放到CPU的寄存器,然后再从CPU的寄存器放到内核缓冲区。而DMA方式直接从IO接口到内核缓冲区,减少了CPU的工作量