【程序是怎样跑起来的】第11章:硬件控制方法

I/O控制器,I/O地址,IRQ,DMA,VRAM

Posted by x-jeff on October 2, 2024

博客为参考《程序是怎样跑起来的》一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.应用和硬件无关?

👉第11章热身问答:

  1. 在汇编语言中,是用什么指令来同外围设备进行输入输出操作的?
    • IN指令和OUT指令。在x86系列CPU用的汇编语言中,通过IN指令来实现I/O输入,OUT指令来实现I/O输出。
  2. I/O是什么的缩写?
    • Input/Output。用来实现计算机主机和外围设备输入输出交互的IC称为I/O控制器或简称为I/O。
  3. 用来识别外围设备的编号称为什么?
    • I/O地址或I/O端口号。所有连接到计算机的外围设备都会分配一个I/O地址编号。
  4. IRQ是什么的缩写?
    • Interrupt Request。IRQ指的是用来执行硬件中断请求的编号。
  5. DMA是什么的缩写?
    • Direct Memory Access。DMA指的是,不经过CPU中介处理,外围设备直接同计算机的主内存进行数据传输。
  6. 用来识别具有DMA功能的外围设备的编号称为什么?
    • DMA通道。像磁盘这样用来处理大量数据的外围设备都具有DMA功能。

在用C语言等高级编程语言开发的Windows应用中,大家很少能接触到直接控制硬件的指令。这是因为硬件的控制是由Windows全权负责的。

不过,Windows提供了通过应用来间接控制硬件的方法。利用操作系统提供的系统调用功能就可以实现对硬件的控制。在Windows中,系统调用称为API(图11-1)。各API就是应用调用的函数。这些函数的实体被存储在DLL文件中。

下面让我们来看一个利用系统调用来间接控制硬件的示例。例如,假设要在窗口中显示字符串,就可以使用Windows API中的TextOut函数$^1$。TextOut的语法如代码清单11-1所示。在这段代码中,确实没有能让大家意识到硬件的参数。带有“设备描述表的句柄”这一注释的参数hdc,是用来指定字符串及图形等绘制对象的识别值,表示的也不是直接硬件设备。

  1. 在向窗口和打印机输出字符串时,可以使用Windows提供的TextOut函数作为API。C语言提供的printf函数,是用来在命令提示符中显示字符串的函数。使用printf函数,是无法向窗口和打印机输出字符串的。

那么,在处理TextOut函数的内容时,Windows做了什么呢?从结果来看,Windows直接控制了作为硬件的显示器。但Windows本身也是软件,由此可见,Windows应该向CPU传递了某些指令,从而通过软件控制了硬件。

2.支撑硬件输入输出的IN指令和OUT指令

Windows控制硬件时借助的是输入输出指令。其中具有代表性的两个输入输出指令就是IN和OUT。这些指令也是汇编语言的助记符

IN指令和OUT指令的语法如图11-2所示。这是Pentium等x86系列CPU用的IN指令和OUT指令的语法。IN指令通过指定端口号的端口输入数据,并将其存储在CPU内部的寄存器中。OUT指令则是把CPU寄存器中存储的数据,输出到指定端口号的端口。

在计算机主机中,附带了用来连接显示器及键盘等外围设备的连接器。而各连接器的内部,都连接有用来交换计算机主机同外围设备之间电流特性的IC。这些IC,统称为I/O控制器。由于电压不同,数字信号及模拟信号的电流特性也不同,计算机主机和外围设备是无法直接连接的。为了解决这个问题,I/O控制器就很有必要了。

I/O是Input/Output的缩写。显示器、键盘等外围设备都有各自专用的I/O控制器。I/O控制器中有用于临时保存输入输出数据的内存。这个内存就是端口。I/O控制器内部的内存,也称为寄存器。虽然都是寄存器,但它和CPU内部的寄存器在功能上是不同的。CPU内部的寄存器是用来进行数据运算处理的,而I/O寄存器则主要是用来临时存储数据的。

在实现I/O控制器功能的IC中,会有多个端口。由于计算机中连接着很多外围设备,所以就会有多个I/O控制器,当然也会有多个端口。一个I/O控制器既可以控制一个外围设备,也可以控制多个外围设备。各端口之间通过端口号进行区分。端口号也称为I/O地址。IN指令和OUT指令在端口号指定的端口和CPU之间进行数据的输入输出。这和通过内存地址来进行主内存的读写是一样的道理(图11-3)。

  1. I/O装置,有的直接附带在计算机主机的主板(用来放置CPU的基板)上,有的则是各自独立的扩张板卡。键盘、鼠标、打印机等常用的I/O,一般都在主板上,而显示高速图形的显示器及网卡等特殊的I/O,通常是独立的扩张板卡。

通过Windows的控制面板,我们可以查看外围设备所连接的I/O控制器的端口号。图11-4是通过Windows控制面板来查看软盘驱动控制器的属性时的情况。“I/O的范围”右侧的数值就是端口号。通过指定该端口号,并利用IN/OUT命令,就可以直接控制软驱这个硬件设备,实现输入输出处理了。

3.编写测试用的输入输出程序

首先让我们利用IN指令和OUT指令,来进行一个直接控制硬件的试验。假设该试验的目的是让计算机内配置的蜂鸣器(小喇叭)发音。虽然蜂鸣器内置在计算机内部,但其本身也是外围设备的一种。因为就算是把蜂鸣器取出,对计算机主机也不会有什么影响。

由于用汇编语言编写程序比较麻烦,因此这里我们采取在C语言源代码中插入助记符的方式来实现。在大部分C语言的处理(编译器的种类)中,只要使用_asm{}括起来,就可以在其中记述助记符。也就是说,这样就可以编写C语言和汇编语言混合的源代码。这里,我们使用微软的Visual C++来作成应用。

AT兼容机中,蜂鸣器的默认端口号是61H(末尾的H,表示的是十六进制数(Hexadecimal)的意思)。用IN指令通过该端口号输入数据,并将数据的低2位设定为ON,然后再通过该端口号用OUT指令输出数据,这时蜂鸣器就会响起来。采用同样的操作方法,将数据的低2位设定为OFF并输出后,蜂鸣器就停止了。

位设定为ON指的是将该位设定为1,位设定为OFF指的是将该位设定为0。把位设定为ON,只需把想要设定为ON的位设定为1,其他位设定为0后进行OR运算即可。由于这里需要把低2位置为1,因此就是和03H进行OR运算。03H用8位二进制数来表示的话是00000011。由于即便高6位存在着具体意义,和0进行OR运算后也不会发生变化,因而就和03H进行OR运算。把位设定为OFF,只需把想要置OFF的位设定为0,其他位设定为1后进行AND运算即可。由于这里需要把低2位设定为0,因此就要和FCH进行AND运算。在源代码中,FCH是用0FCH来记述的。在前面加0是汇编语言的规定,表示的是以A~F这些字符开头的十六进制是数值的意思。0FCH用8位二进制数来表示的话是11111100。由于即便高6位存在着具体意义,和1进行AND运算后也不会产生变化,因而就是同0FCH进行OR运算(代码清单11-2)。

(1)部分是控制蜂鸣器发音的部分。首先,通过IN EAX, 61H(助记符不区分大小写)指令,把端口61H的数据存储到CPU的EAX寄存器中。接下来,通过OR EAX, 03H指令,把EAX寄存器的低2位设定成ON。最后,通过OUT 61H, EAX指令,把EAX寄存器的内容输出到61号端口,使蜂鸣器开始发音。虽然EAX寄存器的长度是32位,不过由于蜂鸣器端口是8位,所以只需对下8位进行OR运算和AND运算就可以正常运行了。

(2)部分是一个重复100万次的空循环,主要是为了在蜂鸣器开始发音和停止发音之间稍微加上一些时间间隔。

(3)部分是用来控制蜂鸣器发音停止的部分。首先,通过IN EAX, 61H指令,把端口61H的数据存储到CPU的EAX寄存器中。接下来,通过AND EAX, 0FCH指令,把EAX寄存器的低2位设定成OFF。最后,通过OUT 61H, EAX指令,把寄存器EAX的内容输出到61号端口,使蜂鸣器停止发音。大家可以把61H端口的低2位认为是蜂鸣器的开关。

最后,让我们对代码清单11-2进行编译,并尝试运行一下。这时,蜂鸣器应该会发出“嘀!”的短促声音。此外,有一点需要注意的是,该程序虽然在旧版本Windows(95、98)中可以正常运行,但在这以后的Windows(XP、Vista等)版本中是无法正常运行的。这是因为,为了保护系统安全,现在的Windows禁止了应用直接控制硬件的方式。如果将该程序在最近的Windows版本上运行的话,就会出现如图11-5所示的错误信息,而且蜂鸣器也不会发出声音。

4.外围设备的中断请求

让我们再来看一下图11-4。在“I/O范围”下面有一个“IRQ”项目,对应的值是0x00000006(06)。IRQ(Interrupt Request)是中断请求的意思。那么,IRQ主要是用来做什么的呢?

IRQ是用来暂停当前正在运行的程序,并跳转到其他程序运行的必要机制。该机制称为中断处理。中断处理在硬件控制中担当着重要角色。因为如果没有中断处理,就有可能出现处理无法顺畅进行的情况。

从中断处理开始到请求中断的程序(中断处理程序)运行结束之前,被中断的程序(主程序)的处理是停止的。这种情况就类似于在处理文档的过程中有电话打进来,电话就相当于中断处理。假如没有中断功能的话,就必须等到文档处理完毕才可以接听电话。这样就太不方便了。由此可见,中断处理有着很大的价值。就像接听完电话后返回到原来的文档作业一样,中断处理程序运行结束后,处理也会返回到主程序中继续(图11-6)。

实施中断请求的是连接外围设备的I/O控制器,负责实施中断处理程序的是CPU。为了进行区分,外围设备的中断请求会使用不同于I/O端口的其他编号,该编号称为中断编号。另一方面,操作系统及BIOS$^1$则会提供响应中断编号的中断处理程序。

  1. BIOS(Basic Input Output System)位于计算机主板或扩张板卡上内置的ROM中,里面记录了用来控制外围设备的程序和数据。

假如同时有多个外围设备进行中断请求的话,CPU也会为难。为此,我们可以在I/O控制器和CPU中间加入名为中断控制器的IC来进行缓冲。中断控制器会把从多个外围设备发出的中断请求有序地传递给CPU(图11-7)。

CPU接收到来自中断控制器的中断请求后,会把当前正在运行的主程序中断,并切换到中断处理程序。中断处理程序的第一步处理,就是把CPU所有寄存器的数值保存到内存的栈中。在中断处理程序中完成外围设备的输入输出后,把栈中保存的数值还原到CPU寄存器中,然后再继续进行对主程序的处理。假如CPU寄存器的数值没有还原的话,就会影响到主程序的运行,甚至还有可能会使程序意外停止或者发生运行异常。这是因为主程序在运行过程中,出于某些原因用到CPU寄存器。而这时如果突然插入别的程序,主程序必然会受到影响。因此,在中断请求完毕后,各寄存器的数值必须要还原到中断前的状态。只要寄存器的值保持不变,主程序就可以像没有发生任何事情一样继续处理(图11-8)。

5.用中断来实现实时处理

在主程序运行的过程中,中断发生的频率有多大呢?实际上,大部分的外围设备,都会频繁地发出中断请求。其原因就是为了实时处理从外围设备输入的数据。虽然不利用中断也可以从外围设备输入数据。但那种情况下,主程序就必须要持续不断地检测外围设备是否有数据输入。

由于外围设备有很多个,因此就有必要按照顺序来调查。按照顺序调查多个外围设备的状态称为轮询。对几乎不产生中断的系统来说,轮询是比较合适的处理。不过,对计算机来说就不适合了。举例来说,假如主程序正在调查是否有鼠标输入,这时如果发生了键盘输入的话,该如何处理呢?结果势必会导致键盘输入的文字无法实时地显示在显示器上。而通过使用中断,就可以实现实时显示了。

打印机等输出用的外围设备中,外围设备接收数据的状态,有时是需要用中断来通知的。由于外围设备的处理速度比计算机主机的处理速度要慢很多,因此,这种情况下就不需要对打印机的状态进行多次调查,只需在中断请求发生时输出数据即可,这样一来,其他时间CPU就可以集中处理别的程序了。

6.DMA可以实现短时间内传送大量数据

DMA(Direct Memory Access)是指在不通过CPU的情况下,外围设备直接和主内存进行数据传送。磁盘等都用到了这个DMA机制。通过利用DMA,大量数据就可以在短时间内转送到主内存。之所以这么快速,是因为CPU作为中介的时间被节省了(图11-9)。

图11-10和在前面看到的软盘控制器的属性画面是相同的。在资源$^1$标签中有DMA设定,可以看出此处该设定为02。02这个编号称为DMA通道。CPU借助DMA通道,来识别是哪一个外围设备使用了DMA。

  1. 资源是计算机具备的有限资源的统称。端口号、IRQ、DMA等可以指定的数值范围都是有限的,因此它们也是资源的一种。

I/O端口号、IRQ、DMA通道可以说是识别外围设备的3点组合。不过,IRQ和DMA通道并不是所有的外围设备都必须具备的。计算机主机通过软件控制硬件时所需要的信息的最低限,是外围设备的I/O端口号。IRQ只对需要中断处理的外围设备来说是必需的,DMA通道则只对需要DMA机制的外围设备来说是必需的。假如多个外围设备都设定成同样的端口号、IRQ及DMA通道的话,计算机就无法正常工作了。这种情况下,就会出现“设备冲突”的提示。

7.文字及图片的显示机制

如果用一句话来简单地概括该机制,那就是显示器中显示的信息一直存储在某内存中。该内存称为VRAM(Video RAM)。在程序中,只要往VRAM中写入数据,该数据就会在显示器中显示出来。实现该功能的程序,是由操作系统或BIOS提供,并借助中断来进行处理的。

在MS-DOS时代,对大部分计算机来说,VRAM都是主内存的一部分。例如PC-9801这种机型的计算机,主内存地址A0000地址以后是VRAM区域。如果用程序往VRAM内存地址中写入数据,文字及图形就可以显示出来。不过,文字和图形的颜色最多只能有16种。这是因为VRAM的内存空间太小了(图11-11(a))。

在现在的计算机中,显卡等专用硬件中一般都配置有与主内存相独立的VRAM和GPU。这是因为,对经常需要描绘图形的Windows来说,数百兆的VRAM是必需的。而为了提升图形的描绘速度,有时还需要专用的图形处理器(图11-11(b))。但不管怎样,内存VRAM中存储的数据就是显示器上显示的信息,这一机制是不变的。