在阅读“Understanding Linux Kernel”一书时,我已经将这些内容与指令 IN & OUT 联系起来。我查了参考手册。
5.1.9 I/O 指令 这些指令在处理器的I/O 端口和寄存器或存储器之间移动数据。 IN 从端口读取 OUT 写入端口 INS/INSB 从端口输入字符串/从端口 INS/INSW 输入字节字符串 从端口输入字符串/从端口 INS/INSD 输入字字符串 从端口输入字符串/从端口 OUTS 输入双字字符串/OUTSB 输出字符串到端口/输出字节字符串到端口 OUTS/OUTSW 输出字符串到端口/输出字字符串到端口 OUTS/OUTSD 输出字符串到端口/输出双字字符串到端口
我没有得到一些东西:
“处理器的 I/O 端口”。这些是什么?为什么我们要从这些端口读取和写入“字符串”?我从来没有遇到过需要使用这些说明的场景。我什么时候需要这些?举一些实际的例子。
你知道内存寻址是如何工作的吗?有地址总线、数据总线和一些控制线。 CPU 将内存的一个字节(或起始字节)的地址放在地址总线上,然后提高 READ 信号,并且一些 RAM 芯片希望通过提高或降低各个行(对应于位)返回该地址处的内存内容在数据总线上的字节(S))。这适用于 RAM 和 ROM。
但也有 I/O 设备:串行和并行端口、PC 微型内部扬声器的驱动器、磁盘控制器、声音芯片等。这些设备也会被读取和写入。它们还需要被寻址,以便 CPU 访问正确的设备和(通常)给定设备内的正确数据位置。
对于某些 CPU 型号,包括大多数“现代”PC 中的 xxx86 系列,I/O 设备与内存共享地址空间。 RAM/ROM 和 IO 设备都连接到相同的地址、数据和控制线。例如,COM1 的串行端口从(十六进制)03F8 开始寻址。但几乎可以肯定在同一个地址有内存。
这是一个非常简单的图表:
https://i.stack.imgur.com/ra1Km.jpg
很明显,CPU 需要与内存或 I/O 设备通信,而不是两者都通信。为了区分这两者,其中一条名为“M/#IO”的控制线断言 CPU 是否要与内存(line=high)或 I/O 设备(line=low)通信。
IN 指令从 I/O 设备读取,OUT 写入。当您使用 IN 或 OUT 指令时,M/#IO 不会被断言(保持低电平),因此内存不会响应而 I/O 芯片会响应。对于面向内存的指令,M/#IO 被置位,因此 CPU 与 RAM 通信,而 IO 设备不参与通信。
在某些条件下,IO 设备可以驱动数据线,而 RAM 可以同时读取它们。反之亦然。它被称为 DMA。
传统上,串行和打印机端口以及键盘、鼠标、温度传感器等都是 I/O 设备。磁盘介于两者之间。数据传输将由 I/O 命令启动,但磁盘控制器通常会将其数据直接存储在系统内存中。
在 Windows 或 Linux 等现代操作系统中,对 I/O 端口的访问被隐藏在“普通”用户程序之外,并且存在处理硬件的软件层、特权指令和驱动程序。所以在本世纪,大多数程序员都不会处理这些指令。
从这样的事情开始:
http://www.cpu-world.com/info/Pinouts/8088.html
您正在学习一个非常古老的技术芯片/架构的说明。回到除了处理器内核之外的所有东西都在芯片之外的时候。看到地址线和数据线,有RD读线和WR写线和IO/M线吗?
有两种类型的指令基于内存和基于 I/O,因为有可寻址空间,很容易被 IO/M IO 或内存解码。
请记住,您有 74LSxx 胶合逻辑、大量电线和大量芯片来将内存连接到处理器。内存就是那个内存,又大又贵的芯片。如果你有一个需要做任何有用的外设,你也有控制寄存器,内存可能是像素数据,但在某个地方你需要设置水平和垂直扫描时钟限制,这些可能是单独的 74LSxx 锁存器,而不是内存,我/O 映射 I/O 保存在胶合逻辑上,从程序员的角度来看很有意义,它还避免了更改段寄存器以瞄准 64K 内存窗口等。内存地址空间是一种神圣的资源,尤其是当你想要将您的地址解码限制为几位,因为每几位都会花费您大量的芯片和电线。
就像大端和小端内存映射 I/O 与 I/O 映射 I/O 是一场宗教战争。您将看到的一些对您的问题的回答将反映今天仍然存在于生活中的人们的强烈意见。现实情况是,当今市场上的每个芯片都有用于各种事物的多条总线,您不会使用地址解码器将实时时钟挂在 ddr 内存总线上。有些甚至还有完全独立的指令和数据总线。从某种意义上说,英特尔赢得了为不同类别的事物提供独立地址空间概念的战争,尽管 I/O 端口这个词是邪恶的和坏的,不应该再说 20 到 30 年。你需要我这个年龄的人在战争真正结束之前退休或离开。甚至术语内存映射 I/O 也已成为过去。
这就是它曾经的全部,英特尔芯片外部的单个地址解码位,由使用特定指令控制。使用该位打开的一组指令 使用该位关闭的一组指令。想看一些有趣的东西去看看 xmos xcore 处理器的指令集,他们有很多指令而不是内存映射寄存器,它把这个 I/O 映射 I/O 东西带到了一个全新的水平。
就像我上面描述的那样,你会放一些有意义的东西,你可以负担得起为视频像素、网络数据包内存(也许)、声卡内存(当然也不是,但你可以有)燃烧内存地址空间)等。而控制寄存器,相对于数据的地址空间很小,可能只有几个寄存器,被解码并用于I/O空间。显而易见的是/是串行端口和并行端口,如果有的话,它们的存储空间很小,如果有的话,你可能在串行端口上有一个小的 fifo。
由于地址空间稀缺,因此并不少见,今天仍然可以看到将内存隐藏在两个寄存器后面,一个地址寄存器和一个数据寄存器,该内存只能通过这两个寄存器使用,它不是内存映射的。因此,您将偏移量写入地址寄存器中的隐藏内存,然后读取或写入数据寄存器以访问内存的内容。现在因为英特尔有 rep 指令,你可以将它与 insb/w outsb/w 结合起来,硬件解码器(如果你有很好/友好的硬件人员与你一起工作)会在你执行 I/O 周期时自动增加地址。因此,您可以在地址寄存器中写入起始地址并执行一次输出,而无需在处理器和内存总线上烧毁获取和解码时钟周期,您可以非常快速地将数据移入或移出外设。这种事情现在被认为是设计缺陷,这要归功于现代超级标量处理器基于分支预测的提取,您的硬件可以随时体验与执行代码无关的读取,因此您永远不应该自动递增地址或清除状态寄存器中的位或修改任何作为读取地址的结果。 (编者注:实际上,您只需确保具有读取副作用的 I/O 寄存器位于不可缓存的内存区域/页面中。在 x86 ISA 中不允许推测性预取不可缓存的内存。而且我永远不会发生/O 空间访问。但是 in
/out
非常慢并且部分序列化,并且物理内存地址空间不再稀缺,因此设备内存通常只是内存映射,以便通过全尺寸 PCIe 事务进行高效访问。)
内置到 386 和现在的保护机制实际上使得从用户空间访问 I/O 变得非常容易。取决于您的谋生方式、公司生产的产品等。您绝对可以使用来自用户空间(Windows 和 linux 中的应用程序等)或内核/驱动程序空间的 in 和 out 指令系列,这是您的选择。您还可以做一些有趣的事情,例如利用虚拟机并使用 I/O 指令与驱动程序对话,但这可能会惹恼 windows 和 linux 世界的人们,驱动程序/应用程序不会走得太远。其他海报是正确的,因为除非您正在编写驱动程序,否则您可能永远不需要使用这些说明,并且您可能永远不会为使用 I/O 映射 I/O 的设备编写驱动程序,因为您知道......这些旧设备的驱动程序已经编写好了。现代设计肯定有 I/O,但它都是内存映射的(从程序员的角度来看)并且使用内存指令而不是 I/O 指令。现在,如果这是 DOS,那么另一方肯定不会死,这取决于您可能在哪里建造投票机、加油泵或收银机或一长串基于 DOS 的设备。事实上,如果您在构建 PC 或基于 PC 的外围设备或主板的地方工作,基于 DOS 的工具仍被广泛用于测试和分发 BIOS 更新和其他类似的东西。我仍然遇到必须从当前的 dos 测试程序中获取代码来编写 linux 驱动程序的情况。就像不是每个可以在 NFL 中投球或接球的人一样,从百分比上讲,很少有人会做涉及这类东西的软件工作。因此,可以肯定地说,您发现的这些说明对您来说可能不会比历史课更重要。
举一些实际的例子。
首先学习如何:
创建一个最小的引导加载程序操作系统并在 QEMU 和真实硬件上运行它,正如我在这里解释的那样:https://stackoverflow.com/a/32483545/895245
进行一些 BIOS 调用以进行一些快速而肮脏的 IO
然后:
PS/2 控制器:获取在键盘上键入的最后一个字符的扫描码 ID 到 al: in $0x60, %al 最小示例实时时钟 (RTC):获取具有秒定义的挂钟时间: .equ RTCaddress, 0x70 。 equ RTCdata, 0x71 /* al 包含秒。 */ mov $0, %al out %al, $RTCaddress in $RTCdata, %al /* al 包含分钟。 */ mov $0x02, %al out %al, $RTCaddress in $RTCdata, %al /* al 包含小时。 */ mov $0x04, %al out %al, $RTCaddress 最小示例可编程间隔定时器 (PIT):每 0x1234 / 1193181 秒生成一个中断号 8: mov $0b00110100, %al outb %al, $0x43 mov $0xFF, %al out %al, $0x34 out %al, $0x12 最小示例 Linux 内核 4.2 使用。还有其他的。
测试环境:QEMU 2.0.0 Ubuntu 14.04,真实硬件联想 ThinkPad T400。
如何查找端口号:Is there a specification of x86 I/O port assignment?
https://github.com/torvalds/linux/blob/v4.2/arch/x86/kernel/setup.c#L646 列出了 Linux 内核使用的许多端口。
其他架构
并非所有架构都有这样的 IO 专用指令。
例如,在 ARM 中,IO 只需写入神奇的硬件定义的内存地址即可完成。
我认为这就是 https://stackoverflow.com/a/3221839/895245 所指的“内存映射 I/O 与 I/O 映射 I/O”。
从程序员的角度来看,我更喜欢 ARM 方式,因为 IO 指令已经需要魔法地址来操作,而且我们在 64 位寻址中有大量未使用的地址空间。
有关具体的 ARM 示例,请参见 https://stackoverflow.com/a/40063032/895245。
在硬件层面,大多数微处理器几乎没有或没有内置 I/O 功能。少数处理器有一个或多个引脚可以使用特殊指令打开和关闭,和/或一个或多个引脚可以使用特殊指令进行测试分支指令,但这样的功能很少见。相反,I/O 通常是通过连接系统来处理的,这样对一系列内存地址的访问会触发一些效果,或者通过包含“in”和“out”指令,这些指令的行为类似于内存加载/存储操作,除了一个特殊信号输出“这是一个 I/O 操作而不是内存操作”。在 16 位处理器时代,拥有专门的输入/输出指令曾经有一些真正的优势。如今,这些优势在很大程度上没有实际意义,因为人们可以简单地将一大块地址空间分配给 I/O,并且仍然有足够的内存留给内存。
由于程序可能通过不恰当地执行 I/O 指令(例如,此类指令可以执行任意磁盘访问)对系统造成相当大的破坏,因此所有现代操作系统都禁止在用户级代码中使用此类指令。某些系统可能允许将此类指令虚拟化;例如,如果用户代码尝试写入 I/O 端口 0x3D4 和 0x3D5,操作系统可能会将其解释为尝试设置某些视频控制控制寄存器以移动闪烁的光标。每次用户程序执行 OUT 指令时,操作系统都会接管,查看用户程序试图做什么,并采取适当的行动。
在绝大多数情况下,即使操作系统会将 IN 或 OUT 指令转换为合适的指令,直接从操作系统请求适当的操作会更有效率。
使用像“I/O 信号”和“内存映射”这样的名称,一切都变得比实际复杂得多,因此给人的印象是它还有很多东西,它涵盖了一个高级主题。现在的趋势是人们将其视为新事物。但这远非如此。即使是 1830 年代的巴贝奇也开着他的打印机,这需要一个 I/O 信号,尽管是由轴和齿轮完成的。例如,在 2000 年前的《亚历山大英雄》的机器中,或者回到希腊时代的剧院里,他们总是从一组不同的绳索中拉出一根绳索来控制灯光或场景,每根绳索就像一条输入和输出线,就像就这么简单,地址是“哪一行”,即我们正在选择哪个东西、内存或设备,数据是您传递到该内存或设备或从该内存或设备读回的信息。
尽管在 40 年代用机柜填满建筑物的大型主机计算机使用 64 位之类的东西,因此在很久以前就可以处理 I/O 映射,例如 Konrad Zuse 和他的房间大小的计算机使用浮动这个点在 1930 年代大约有 20 位十进制数字,必须驱动他的打印机、各种灯泡指示灯和开关之类的东西。但是在微型微处理器上,情况就不同了,它们直到 60 年代才被设想出来,直到 1971 年才建成。所有这些技术在 80 年代使用 8 位逻辑,在 70 年代用于 4 位微处理器,在 60 年代用于 2 位并被使用在 16bit 90 年代大家开始买电脑的时候,所以因为现在摆在他们面前,第一次开始讨论这个 I/O 和内存映射的话题,它似乎是一个新的东西。互联网的;然后我们在 00 年代有 32 位计算机,在 10 年代有 64 位计算机,这引起了关于内存向下数据线的无休止的讨论。为了回答您的问题,我将谈论电子爱好者在 30-40 年前购买的芯片,就像我当时所做的那样,因为后来事情变得如此先进,我无法用后来的芯片构建,但是原理现在是一样的,门只是隐藏在更大的黑盒芯片中,这些芯片包含处理这些操作的其他引脚,这些操作更多地并行进行(例如,启用许多八进制锁存器,同时启用许多芯片),并且数据和地址总线有更多的线路,这是唯一的区别。
好吧,我对所有新语言以及它现在在现代 PC 上的情况一无所知,但我可以告诉你在过去我用芯片制造计算机时的情况。
简单来说,所有 I/O 映射和内存映射意味着,如果您将大量灯泡示例串起来以进行庆祝,并且将电线连接到每个灯泡并调用灯泡内存位置(即灯泡代表 RAM 中的内存,或者打开或关闭,如果您选择位置 0,您会得到线 0、位置 1、线 1、loc 2 线 2 等等)如果您添加了更多线,例如一根线是铃,则该特定位置不是它的记忆是一个设备,您可以使用 OUT 命令输出到它以使其响铃。但从计算机的角度来看,它被视为一个内存位置,因为它同样作为连接 MPU 的电线进入。如果添加了另一条线,它是您在外部操作的开关,则这是一个 I/O 设备,这将是对 pc 的 IN 指令。所以这称为 I/O 映射 I/O。
现在在计算机上,总线上的线代表地址线或数据线,但它们是二进制的,即用 2 根线你可以有 00 01 10 11 即 4 种组合 2^2,所以用 8 线 2^8=256 种可能性,有20 行 2^20=1048576 和 30 行 2^30=1073741824 (1 gig) 有 30 行的可能性。所以这就是为什么它被称为 MAPPED,而不是仅仅说 I/O 和内存,他们说 I/O 映射和内存映射,因为您将线映射为组合 y 对它们进行二进制编码。所以如果说你有 2 根线,4 种组合,它们不能只连接到灯泡,(更不用说 MPU 的微小电压所需的电流放大,以及防止反馈电流),但是 2 根线有通过解码器(我们过去使用 138 将 3 行解码为 8 行,使用 164 将 4 二进制行解码为 16 行。)一旦通过解码器,这 2 行例如 A0 和 A1(地址 0 和地址 1(LINES )),对于您正在驾驶的特定灯泡(在计算机上的情况下,内存)变成 4 行(开或关),但在某些情况下,这些位置会选择一些输入/输出设备,并说“使用我”相反,即像内存一样,一旦定位,数据就会在数据总线 D0..7 或 D0..31 或其他任何方式上以一种或另一种方式传递(使用聪明的三态逻辑来切断每次途中的电压)计算机上数据的大小(您有 2 位、4 位、8 位、16 位、32 位、64 位、128 位、256 位、计算机,无论您正在构建什么计算机)。因此,数据自然地从数据线传入或传出到内存或 I/O 设备(如果它是内存映射的),但这不应该与 IN/OUT 指令混淆,这个 IN 和 OUT 意味着来自某些其他 I/O 内存块,MPU 内专门为 I/O 分配的特殊 I/O 内存块,即(非内存映射),此 I/O 空间在某些微处理器上并不总是可用,例如我没有'认为我们没有在 6502 上使用它,但我们在 z80 上使用了它。更多艺术芯片仅使用内存映射,例如在游戏机等中,更明智但无趣(留在书中)的芯片也用于 I/O 空间。内存映射 I/O 正在加快速度,因为它结合了内存寻址(这对于 RAM 来说非常快),因此图形类型的计算机仅使用 I/O 的内存映射来获得速度。 I/O 映射 I/O 分配给慢速端口,例如 rs232 或并行端口,并使用 IN OUT 命令。
现在,如果您不是添加两根电线,而是实际上更换了两根原本连接灯泡的电线,并拿走了其中一些灯泡并将它们替换为其他东西,例如一个铃铛和另一个开关,这些现在不被引用(选择) 分别使用 IN 和 OUT 指令,通过访问选择那些电线(最初是灯泡)的特定内存位置来引用它们。所以这是内存映射 I/O。
内存映射 I/O 意味着通常进入内存(RAM)的实际地址总线也连接到其他解码器(逻辑解码器),当它检测到地址信号的特定二进制组合时,它会产生一个输出高, (例如,如果你有一个和而不是门的负载,并且你说,如果这个而不是那个等等,使用引脚 A0..A20 或任何大小的地址总线),那么这个高信号启用一个锁存器, (对于特定设备,如串行端口、并行端口),此锁存器然后将数据总线上的数据传递到 I/O 设备。这是为了写入 I/O 设备。读取工作相反,I/O 设备将数据传回,如果我没记错的话,它会将完全相同的地址代码组合发送到地址线上。
我想,它今天必须以同样的方式工作,除了它们将是更多的数据和地址线。
您实际上是在将 I/O 连接到地址线。因此,I/O 被有效地映射到内存空间,就好像它是内存一样。但是另一个锁存器会禁止地址引脚同时访问 ram,这样您就不会在同一条线上获得两个地址或数据源的电压,这会损坏芯片。
使用 IN 和 OUT 指令,我们在 40 年前就已经在 z80 芯片上使用了。这适用于芯片实际上以不同方式处理 I/O 本身的特殊情况,即它不是内存映射的。 (即,使用内存映射,您只需读取或写入内存位置,但使用 IN 和 OUT,您已经告诉 CPU 它是 I/O 信号而不是内存)。因此,对于 IN/OUT 指令,它有自己的 I/O 地址空间(这是 RAM 内存的额外空间),这个 I/O Ram,看起来,有一组相同的地址,除非您通过附加到这些 I/O 地址的解码器直接访问设备,并且您不能从标准地址引脚访问 I/O 设备,这是针对 IN/OUT 指令的。
当您输入和输出字符串时,我不知道 x86,但这可能意味着您正在数据总线上发送或接收数据(使用所有数据引脚 D0..D15 或任何大小的数据总线)多次串联以该特定 I/O 设备可能的最大数据速率(也许要做到这一点,它使用某种握手信号,您必须查找它。)因此,D0..63 线上的数据(或 D0. .31 on old pc's or D0..15 on late 80's early 90's pcs, 或 D0..7 or 80's and pre 80's pcs, 一个接一个地在 SERIES 中,而不是只有一次与 IN 和 OUT。即 INSTR 和OUTSTR 只是在某个定义的数据速率下的多个 IN 和 OUT。例如,如果您正在访问互联网,您每次都需要大量信息输入和输出,因此您将使用输入和输出数据字节,这对于这种情况,最好作为字母和数字的 ASCII 代码字符串传递。这些命令与在循环中使用 IN 和 OUT 指令完全相同,其中计数是字符串长度。
如果您正在访问例如 pc 扬声器,您只需使用 OUT 一次传递一条数据。
如果您正在从并行端口读取数据,那么您将执行 IN,并使用端口 I/O 地址的代码。写入它,例如通过电子信号驱动旧打印机或机器人,您将使用 OUT 命令。并行端口和串行端口(旧 RS232)是使用的典型端口。 RS232 是串行数据,只允许输入或输出一位,因此如果您从 rs232 读取,则只有 1 位相关字节,与输出相同。 rs232 的波特率最大约为 17kHz,但这些用于驱动电子设备很多,在过去,我曾经构建 rs232 电路,例如读取电压或驱动 PIC 微控制器。每个端口被命名为例如 COM1 COM2 COM3 COM4 并且它们具有 I/O 地址。我在这里不确定,但它们类似于例如 3F8h 378h (h=hex address)
我不确定现代端口,但如果您正在写入 USB,这很可能是内存映射 I/O,以提高速度。
PS/2 键盘端口,我认为这是使用 IN 指令,从键盘读取数据。这取代了旧的 RS232,但我相信它的规格略有不同。
磁盘驱动器通常是内存映射的,大概现在仍然如此,即您不驱动带有输入/输出指令的磁盘驱动器,它们太慢了。但无论如何端口都很慢,所以没关系,例如,与硬盘所需的 200 兆字节 / 秒的极好数据速率相比,打印机的数据速率要求较慢。一个扬声器,它只需要大约 10 或 20 倍的声音频率,比如说 20kHz 对于蜂鸣器来说就足够了,因此它是 I/O。慢的东西使用 I/O,IN/OUT 指令。因此USB现在可能是内存映射的,你必须检查它。
更好的理解方式是这样。在 80 年代的旧电脑上,有时您想控制您构建的某些设备,并且没有输出端口的规格(因为当时制造商将其隐藏起来,以便某些公司,例如操纵杆和墨盒公司)可以在通过一些商业交易进入市场)。您需要做的是打开计算机并将电线焊接到地址总线上的某些点,例如,您将三根电线焊接到电路中安全距离的某些点(以免热量损坏芯片),那些由电路板布局连接到微处理器上的引脚 A15 A7 和 A1 的点。而且你通常还必须连接一条 MREQ 线(一条内存请求线和/或 RD/WR 线以产生更整洁的信号,并将其添加到与或非逻辑中,但如果你很聪明,你可以这样做它与地址线)然后你连接这三根线+这个额外的就绪类型信号(例如,MREQ RD或WR线给出一些有效的低电平或高电平(这可能需要一个额外的非门)来表示数据准备就绪线现在)通过一个 4 输入与门,它通过一个 200 欧姆电阻将输出提供给一个 LED,你有自己的内存映射高速 I/O 到一个 LED 灯,你可以通过一个 SR 锁存器锁存或 D 型锁存器将其存储在某些电路板上的外部 1 位存储器中。这里 15 是 32K 行,7 是 64 行,1 是 2 行(二进制工作在 2 的幂,所以 A1 是 2^1,A7 是 2^7,A15 是 2^15),所以如果你寻址位置 32768+64+2=32834 = F041 十六进制,在汇编器中的旧 MPU 上使用 LDA 或 STA 或 LD,您将输出到此 LED,如果电阻器约为 100 欧姆,它会亮起。所以你已经完成了内存映射 I/O,就像它一样简单,你今天可以通过焊接到你的 mpu 地址线来做到这一点。但是由于电路的精细性,您现在不会这样做。但是您也可以加入数据线 D0..7(在过去),或者现在说 d0..31 用于旧 486 PC 上的 32 位。然后,如果您通过使用值 8 加载累加器(现在是 mov ax,8)来处理机器代码中的该位置,或者将该累加器值存储到地址位置(mov F041h,ax 累加器,那么您今天甚至会得到导致的结果请注意,示例中的 8 是数据总线上的内容,在这种特殊情况下,我们不传递数据,我们只是启用特定设备(LED 亮,如果我们选择了那个 I/O 设备,这里,只是一个 LED),所以在这个例子中,我们使用 MOV ax,8 指令有什么数字并不重要,它可以是例如 mov ax,243,我们仍然会在 F041h 线上启用 LED 时然后我们执行 mov F041h,因为我们使用相同的地址。你看,有地址线和数据线。所以当你在 COM1 中寻址 3F8 或任何地址时,I/O 内存映射只是发送一个信号输出到一个端口,例如 ps/2,并且一个和门正在检查你是否有 1110000100 在行上,即 11 是 3 1000 是 F 并且 0100 是 8,参见二进制到十六进制转换。如果高电压出现在有 1 的位位置,则端口(例如 rs232 或 ps/2)设置为活动,即启用,这通过 CE 芯片启用信号或 CS 芯片启用锁存器选择简单。
在锁存器上,它是 E 使能引脚或 OE 低电平有效输出使能。即在上面描述的示例中,我们使用地址来选择(通过解码)我们想要使用的 I/O 设备(即在示例中,如果选择了该 I/O 设备,LED 会亮起。所以这是启用线. THEN,一旦选择了 I/O 设备,THEN 数据通过旧的八进制锁存器 373 从数据总线(过去的 D0..7,或现在对于 64 位计算机的示例 D0..63)传递天,这些是 D 型触发器电路,将数据存储在触发器内部。在有效的高时钟沿时,数据通过并被存储。这个时钟沿将来自数据信号上的“DATA RDY”信号,这个有各种各样的名字,我现在不知道叫什么名字了。所以对于 64 位,我们有 8 个八进制锁存器。它们使用双向锁存器来控制数据,无论是双向还是三态,这样当我/O设备未使用,数据线处于高阻状态,所以选择I/O设备与地址线上的组合,这是数字,例如OUT中的3f8h 3F8h, 7 和示例 7 中的数据是在数据线上传递的内容,在 OUT 命令中,数据将 OUT 传递到数据锁存器,然后再传递到 I/O 设备。如果您有 IN,您将执行一个命令,例如 IN 3f8h,800h,(我希望,但我不知道 x86 汇编程序的语法),我的意思是,对于 IN,您正在输入来自数据线(在选择了地址之后,例如这里的 3f7h,它选择了那个 I/O 设备),这个数据来自 I/O 设备,通过数据锁存器中的 D 型触发器(一个用于数据总线),并输入到 MPU 微处理单元上的 D0..7 或(现代 pc 上的 D0..63)引脚)。在这个例子中,我输入了 IN 3f8h, 800h,以表明一旦数据进入,它就会被存储到地址 800h 中。我认为x86的语法不同,你可能必须做IN 3f8h,啊或类似的东西,即先将数据放入寄存器,然后再MOV 800h,即将数据移入内存在 RAM 中的位置,(如果你想存储它),或者用 ah 等做其他事情。ah 是一个示例寄存器,它可以是任何,al,bh,bl 等,但检查语法,每个汇编系统都是略有不同,我不是 x86 方面的专家。同样,我使用 3f8h 作为示例 I/O 地址,有数百个,可能有数千个这样的地址,例如 378h。请参阅 IBM PC 的 I/O 内存映射以获取完整列表。
而当您访问内存时(RAM,例如 70 年代的 64 字节静态 ram 和动态 RAM,80 年代的 8K SRAM 和 DRAM,90 年代和现在的 SIMMS 行每行都有几兆字节(单行内存模块)如果不是 I/O 地址(非常很少有地址是 I/O 地址,如今内存在地址空间中的可能性是现代 pc 上的 I/O 的数百万倍或更多),您仍然使用相同的读写数据指令到内存,但您不是驱动一些寻找这些位的外部逻辑电路,而不是那些地址和数据引脚直接连接到 RAM 芯片。
在机器代码中,I/O 和内存寻址看起来是一样的,就好像它们都是内存访问一样,但是在实际的电子电路中,物理上发生的事情却完全不同。
还有比这更多的诡计。它不只是将 64kb 的单独地址空间复用到具有“额外地址总线/芯片选择引脚”的相同线路上。 Intel 8086 和 8088 及其克隆也复用数据总线和地址总线; CPU中所有非常罕见的东西。数据表中充满了“最小/最大”配置内容以及您需要连接到它以使其“正常”运行的所有锁存寄存器。另一方面,它在地址解码中节省了大量的和门和“或”门,64kb 应该是“每个人都足够的 i/o 端口”:P。
此外,对于所有那些“仅限驱动程序开发人员”的人,请注意:除了在 PC 以外的其他硬件中使用英特尔兼容芯片的人(他们从来没有真正打算在 IBM PC 中使用 - IBM 只是因为他们价格便宜并且已经上市),英特尔还销售具有相同指令集的微控制器(英特尔 Quark),并且其他供应商也有大量具有相同指令集的“片上系统”。我不认为你会设法用单独的“用户空间”“内核”和“驱动程序”将任何东西塞进 32kb 中:)。对于大多数事情来说,这种复杂的“操作系统”既不是最优的,也不是我们想要的。在 RAM 中形成一些 UDP 数据包,然后将它们放入一些环形缓冲区并让一些中继点击点击不需要 30mb 内核和 10 秒的加载时间,你知道的。如果 PIC 微控制器还不够用,但您又不想要整台工业 PC,这基本上是最佳选择。因此,端口 I/O 指令确实被大量使用,而不仅仅是“驱动程序开发人员”用于更大的操作系统。
CPU 通过 io 端口连接到一些外部控制器。在旧的 x86 电脑上,我使用 I/O 端口使用软盘驱动器。如果您知道哪些命令接受设备控制器,您可以通过它的端口对其进行编程。
在现代世界中,您永远不会使用端口指令。如果您是(或将成为)驱动程序开发人员,则例外。
有更多关于 I/O 端口的详细信息 http://webster.cs.ucr.edu/AoA/DOS/ch03/CH03-6.html#HEADING6-1
如果您不编写操作系统,那么您将永远不会使用这些说明。
基于 x86 的机器有两个独立的地址空间——您熟悉的内存地址空间,然后是 I/O 地址空间。 I/O 端口地址只有 16 位宽,并且引用作为 I/O 设备一部分的低级寄存器和其他低级小部件——例如串行或并行端口、磁盘控制器等。
没有实际示例,因为这些仅由设备驱动程序和操作系统使用。
insb
或 outsb
(显然受 x86 架构的影响)您的代码更具架构可移植性。然而,这些功能是通过每个体系结构的汇编指令实现的。
不定期副业成功案例分享
in
/out
与加载/存储指令具有不同的语义:它们主要在 CPU 管道上进行序列化,并在 I/O 空间访问之前刷新存储缓冲区。