IO内存和IO端口

CPU 如何访问到外设的 ROM 呢?访问外设有两种方式。

(1)内存映射:通过地址总线将外设自己的内存映射到某个内存区域(并不是映射到主板上插的内存条中)。

(2)端口操作:外设都有自己的控制器,控制器上有寄存器,这些寄存器就是所谓的端口,通过 in/out指令读写端口来访问硬件的内存

外设编址

CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。在一个系统中也可以同时使用两种方式,前提是首先要支持I/O独立编址。

20221019002321

Intel的x86微处理器都支持I/O 独立编址,因为它们的指令系统中都有I/O指令,并设置了可以区分I/O访问和存储器访问的控制信号引脚。

而一些微处理器或单片机,为了减少引脚,从而减少芯片占用面积,不支持I/O独立编址,只能采用存储器统一编址。

统一编址

RISC指令系统的CPU(如,PowerPC、m68k、ARM等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称“I/O空间”)。

统一编址优点:

  • 由于对I/O设备的访问是使用访问存储器的指令,所以指令类型多,功能齐全,这不仅使访问I/O端口可实现输入/输出操作,而且还可对端口内容进行算术逻辑运算,移位等等;
  • 另外,能给端口有较大的编址空间,这对大型控制系统和数据通信系统是很有意义的。

这种方式的缺点是端口占用了存储器的地址空间,使存储器容量减小,另外指令长度比专门I/O指令要长,因而执行速度较慢。

独立编址

一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间”或者“I/O端口空间”。

这是一个与CPU的RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。

CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O端口)。

与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB(0-0xffff)。这是“I/O映射方式”的一个主要缺点。

独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。

独立编址主要优点是:

  • I/O端口地址不占用存储器空间;使用专门的I/O指令对端口进行操作,I/O指令短,执行速度快。
  • 并且由于专门I/O指令与存储器访问指令有明显的区别,使程序中I/O操作和存储器操作层次清晰,程序的可读性强。
  • 同时,由于使用专门的I/O指令访问端口,并且I/O端口地址和存储器地址是分开的,故I/O端口地址和存储器地址可以重叠,而不会相互混淆。
  • 译码电路比较简单(因为I/0端口的地址空间一般较小,所用地址线也就较少)。

其缺点是:只能用专门的I/0指令,访问端口的方法不如访问存储器的方法多。

数据空间

CPU可以直接读以下三个地方的数据。

(1)CPU内部的寄存器; (2)内存单元; (3)端口。

其中内存单元,有直接来自内存条的ram,也有外设通过内存映射过来的

内存空间

内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幂-即4G,cpu需要地址映射才能访问。

每个外设,包括显卡、键盘、各种控制器等,都有自己的内存(主板也有自己的内存,BIOS 就存放在里面),不过这种内存都是只读存储器 ROM。

硬件自己的功能调用例程及初始化代码就存放在这 ROM中。根据规范,第 1 个内存单元的内容是 0x55,第 2 个存储单元是 0xAA,第 3 个存储单位是该 rom 中以512 字节为单位的代码长度。从第 4 个存储单元起就是实际代码了,直到第 3 个存储单元所示的长度为止。

8086内存映射表

如下表所示,bios的rom就采用内存映射,显存ram也采用内存映射.通常所说的内存,也就是我们的内存条,映射了最大一段范围,而其他外设映射过来的内存空间,我们也叫做IO内存,用以与内存条相区别

这里面的数据我们可以直接寻址访问,简单没什么好说的.在汇编里面,通常指令都可以操作内存

起始 结束 大小 用途
FFFF0 FFFFF 16B BIOS 入口地址,此地址也属于BIOS 代码,同样属于顶部的640KB 字节。只是为了
强调其入口地址才单独贴出来。此处16 字节的内容是跳转指令jmp f000:e05b
F0000 FFFEF 64KB-16B 系统 BIOS 范围是 F0000~FFFFF 共 64KB,为说明入口地址,将最上面的 16字节从此处去掉了,所以此处终止地址是 0XFFFEF
C8000 EFFFF 160KB 映射硬件适配器的 ROM 或内存映射式 I/O
C0000 C7FFF 32KB 显示适配器 BIOS
B8000 BFFFF 32KB 用于文本模式显示适配器
B0000 B7FFF 32KB 用于黑白显示适配器
A0000 AFFFF 64KB 用于彩色显示适配器
9FC00 9FFFF 1KB EBDA(Extended BIOS Data Area)扩展 BIOS 数据区
7E00 9FBFF 622080B 约 608KB 可用区域
7C00 7DFF 512B MBR 被 BIOS 加载到此处,共 512 字节
500 7BFF 30464B 约 30KB 可用区域
400 4FF 256B BIOS Data Area(BIOS 数据区)
000 3FF 1KB Interrupt Vector Table(中断向量表)

IO空间

X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间,cpu可以直接访问。

在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片。

  • 各种接口卡(比如,网卡、显卡)上的接口芯片,它们控制接口卡进行工作;
  • 主板上的接口芯片,CPU通过它们对部分外设进行访问;
  • 其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。

在这些芯片中,都有一组可以由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片中,但是它们在以下两点上相同。

  • 都和CPU的总线相连,当然这种连接是通过它们所在的芯片进行的;
  • CPU对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口读写命令。

可见,从CPU的角度,将这些寄存器都当作端口,对它们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。

在访问端口的时候,CPU通过端口地址来定位端口。因为端口所在的芯片和CPU通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。

在PC系统中,CPU最多可以定位64KB个不同的端口。则端口地址的范围为0~65535。

对端口的读写不能用mov、push、pop等内存读写指令。端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。

IO端口表

PC只用了10位地址线(A0-A9)进行译码,其寻址的范围为0H-3FFH,共有1024个I/O地址。 这1024个地址中前半段(A9=0,范围为0H-1FFH)是属于主机板I/O译码, 后半段(A9=1,范围为200H-3FFH)则是用来扩展插槽上的I/O译码用。

端口地址范围 分配说明 端口地址范围 分配说明
0x000~0x01F 8237A DMA控制器1 0x1F0~0x1F7 IDE硬盘控制器0
0x020~0x03F 8259A 可编程中断控制器1 0x278~0x27F 并行打印机端口2
0x040~0x05F 8253/8254A 定时计数器 0x2F8~0x2FF 串行控制器2
0x060~0x06F 8042 键盘控制器 0x378~0x37F 并行打印机端口1
0x070~0x07F 访问CMOS RAM/实时时钟RTC(Real Time Clock)端口 0x3B0~0x3BF 单色MDA显示控制器
0x080~0x09F DMA页面寄存器访问端口 0x3C0~0x3CF 彩色CGA显示控制器
0x0A0~0x0BF 8259A 可编程中断控制器2 0x3D0~0x3DF 彩色EGA/VGA显示控制器
0x0C0~0x0DF 8237A DMA控制器2 0x3F0~0x3F7 软盘控制器
0x0F0~0x0FF 协处理器访问端口 0x3F8~0x3FF 串行控制器1
0x170~0x177 IDE硬盘控制器1

数据读取

在有了IO空间的概念后,就有IO端口和IO内存

  • I/O端口:当一个寄存器或内存位于I/O空间时,称其为I/O端口。
  • I/O内存:当一个寄存器或内存位于内存空间时,称其为I/O内存。
  • 这种映射是通过OS的MMU来把虚拟地址映射为物理地址的

20221019003312

I/O端口具有边际效应(side effect),而内存操作则没有,内存写操作的唯一结果就是在指定位置存贮一个数值;内存读操作则仅仅是返回指定位置最后一次写入的数值。

何为边际效应呢?就是读取某个地址时可能导致该地址内容发生变化。比如很多设备的中断状态寄存器只要一读取,便自动清零。

边际效应

尽管硬件寄存器和内存之间有很强的相似性, 程序员在存取 I/O 寄存器的时候还是要格外小心,避免被CPU(或者编译器)优化所迷惑, 因为它可能修改你期待的 I/O 行为.I/O 寄存器和 RAM 一个主要的不同是:I/O 操作会带来边际效应, 而内存操作没有。

  • 一个内存写操作的唯一效果是存储一个值到某个地址, 并且一个内存读操作返回上次写到该地址的值. 由于内存存取速度对CPU 性能是至关重要的, 这种无副作用的操作已被多种方式优化: 值被缓存, 并且读/写指令被重新编排.
  • 编译器能够缓存数据值到CPU 寄存器而不写到内存, 并且即使数据值已经存储到内存, 读和写操作都能够在缓冲内存中进行而不是直接接触物理RAM.
  • 此外,指令重编排可能在编译器级别或在硬件级别发生: 很多情况下,如果一个指令以不同于在程序文本中出现的顺序来执行(例如, 为避免在 RISC 流水线中的互锁),它能够执行得更快,
  • 对于传统内存(至少在单处理器系统)来说,这些优化是透明和有益的。驱动直接存取I/O寄存器的主要目的是能提高CPU性能。

然而,这些优化对正确的 I/O 操作可能是致命的. 处理器无法预见这种情形, 一些其他的操作(在一个独立处理器上运行, 或者发生在一个 I/O 控制器的事情)依赖内存存取的顺序.编译器或者 CPU 可能只尽力胜过你并且重编排你请求的操作; 结果可能是奇怪的错误而非常难于调试.因此, 一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.

side effect(译为边际效应或副作用):是指读取某个地址时可能导致该地址内容发生变化,比如,有些设备的中断状态寄存器只要一读取,便自动清零。I/O 寄存器的操作具有side effect,因此,对其操作不能使用cpu缓存。

内存操作没有边际效应,在《linux设备驱动程序》中是这样描述的:内存写操作的唯一结果就是在指定位子存储一个数据,内存读操作就是在指定位子读取最后一次写入该位子的数据。

I/O端口与实际外部设备相关联,通过访问I/O端口控制外部设备,访问I/O 口的主要目的就是边际效应,不仅是读写寄存器值,还有其他由读写引起的后续操作,如读中断状态寄存器不仅能获得中断的flag值,还能在读取flag后将flag值清零。

IO内存

IO内存:IO内存又称为Memory-Mapped I/O(MMIO),该IO空间处在CPU空间范围内,IO内存和普通的内存没什么区别,两者都是通过CPU的地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。要想操纵该IO就得首先将该IO映射到CPU的地址中,然后就可以访问该IO,如同访问内存。大多数嵌入式设备都属于此。

IO内存占用了CPU的总线地址空间,其性质和普通的内存一样,由于访问时该IO时和访问内存一样都是物理地址,而在linux中并不会直接对物理地址进行操作,需要将其映射到虚拟地址中。由于linux属于宏内核,驱动位于内核中,一个驱动程序要想访问IO内存就必须将其映射到内核的虚拟地址空间中(linux在空间划分时会专门留出一段空间预留给IO使用),然后才能对IO进行读写操作。

使用I/O内存的步骤:1. 申请 2. 映射 3. 访问 4. 释放

根据计算机平台和所使用总线的不同,I/O内存可能是也可能不是通过页表访问的。

如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。

如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读写它们。

IO端口

IO端口:又称为Port(PIO),该IO的空间与CPU空间相互独立,两者互相独立,相互不干扰,这种类型IO在X86中比较常见,该IO端口有独立的空间,所以CPU要想访问该端口就得通过一些专有函数或者指令。

使用I/O端口的步骤:1. 申请 2. 访问 3. 释放

访问I/O端口时,多数硬件都会把8位,16位和32位的端口区分开。因此,C语言程序中必须调用不同的函数来访问大小不同的端口。

  1. CPU通过地址线将地址信息发出;
  2. CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
  3. 端口所在的芯片将60h端口中的数据通过数据线送入 CPU。

注意,在 in和 out指令中,只能使用ax 或al来存放从端口中读入的数据或要发送到端口中的数据。访问8位端口时用al,访问16位端口时用ax。

;对0~255以内的端口进行读写时:
in al,20h
;从20h端口读入一个字节
out 20h,al
;往20h端口写入一个字节

;对256~65535的端口进行读写时,端口号放在dx中:mov dx,3f8h
;将端口号3f8h送入dx
in al,dx
;从3f8h端口读入一个字节
out dx, al
;向3f8h端口写入一个字节

读取硬盘

需要同时填充这么多寄存器,才能实现对硬盘的操作

20221023042714

20221023042904

读取内存

  1. CPU通过地址线将地址信息发出;
  2. CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据;
  3. 存储器将地址单元中的数据通过数据线送入CPU。