x86中的IN和OUT指令用于什么?

在阅读“理解Linux内核”一书时,我已经习惯了这些指令。 我查了参考手册。

5.1.9 I / O指令

这些指令在处理器的I / O端口和寄存器或存储器之间移动数据。

IN Read from a port OUT Write to a port INS/INSB Input string from port/Input byte string from port INS/INSW Input string from port/Input word string from port INS/INSD Input string from port/Input doubleword string from port OUTS/OUTSB Output string to port/Output byte string to port OUTS/OUTSW Output string to port/Output word string to port OUTS/OUTSD Output string to port/Output doubleword string to port 

我没有得到什么东西:

  1. “处理器的I / O端口”。 他们是什么? 为什么我们要读写这些端口的“string”?
  2. 我从来没有遇到过需要使用这些指令的场景。 我什么时候需要这些?
  3. 举一些实际的例子。

你知道内存寻址的工作原理吗? 有一个地址总线,一条数据总线和一些控制线。 CPU将地址总线上的一个字节(或一个开始字节)的地址放在地址总线上,然后提高READ信号,有些RAM芯片希望通过boost或降低单独的行(对应于位在数据总线上的字节中)。 这适用于RAM和ROM。

但是也有I / O设备:串行和并行端口,PC微型内置扬声器的驱动程序,磁盘控制器,声音芯片等等。 而且这些设备也可以读取和写入。 还需要对其进行处理,以便CPU访问正确的设备和(通常)给定设备中正确的数据位置。

对于包括大多数“现代”个人电脑中的xxx86系列的一些CPU型号,I / O设备与存储器共享地址空间。 RAM / ROM和IO设备都连接到相同的地址,数据和控制线。 例如,COM1的串行端口从(hex)03F8开始。 但是在同一地址几乎肯定有记忆。

这是一个非常简单的图表:

[ https://qph.ec.quoracdn.net/main-qimg-e510d81162f562d8f671d5900da84d68-c?convert_to_webp=true ]

显然,CPU需要与内存或I / O设备进行通信,而不是两者。 为了区别这两者,称为“M /#IO”的控制线之一断言CPU是否要与存储器(线=高)或I / O设备(线=低)通话。

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线?

有两种types的基于内存和基于I / O的指令,因为有可寻址的空间,可以通过IO / M IO或内存轻松解码。

请记住,您有74LSxx胶合逻辑,大量电线和大量芯片将内存连接到处理器。 而内存就是那个内存,昂贵的芯片。 如果你需要一个外设来做有用的事情,你也有控制寄存器,内存可能是像素数据,但是你需要设置水平和垂直扫描时钟的限制,这可能是单独的74LSxx锁存器,而不是存储器,有I / O映射的I / O保存在两个胶合逻辑上,并且从程序员的angular度来看,它也避免了改变你的段寄存器,使你的64K内存窗口等等。内存地址空间是一个神圣的资源,尤其是当你想把你的地址解码限制在几位,因为每一个比特都需要一定数量的芯片和电线。

像大大小小的endian内存映射I / O与I / O映射I / O是一场宗教战争。 对于你的问题,你将会看到的一些反应将会反映出今天仍然在生活中的人们的强烈的意见。 事实上,目前市场上的每一个芯片都有多种busess的东西,你不要挂在你的实时时钟的ddr内存总线与地址解码器。 有些甚至还有完全独立的指令和数据总线。 在某种意义上,英特尔赢得了针对不同类别的事物的单独地址空间概念的争论,尽pipe术语I / O端口是邪恶的,不应该说20-30年以上。 在战争结束之前,需要我们这个年龄段的人来退休或者离开。 即使是内存映射I / O这个术语也是过去的事情。

这实际上就是这样,intel芯片外部的单个地址解码位由使用特定的指令控制。 使用一组指令位上使用的一组指令位已closures。 想看看一些有趣的东西,看一看xmos xcore处理器的指令集,它们有许多指令而不是内存映射寄存器,它把这个I / O映射的I / O变成了一个全新的级别。

在使用它的地方就像我上面所描述的那样,你会把有意义的东西,你可以负担像video像素,networking数据包内存(也许),声卡记忆(也不是这样,但你可以有)等等。而控制寄存器,地址空间相对于数据来说是非常小的,也许只有less数几个寄存器,在I / O空间被解码和使用。 显而易见的是串行端口和并行端口(如果有的话),如果有的话,你可能在串行端口上有一个小的fifo。

由于地址空间稀缺,这种情况并不less见,而且现在仍然看到隐藏在两个寄存器后面的地址寄存器和数据寄存器,这个存储器只能通过这两个寄存器使用,而不是存储器映射。 所以你把偏移量写入地址寄存器的这个隐藏的存储器中,然后你读或写数据寄存器来访问存储器的内容。 现在因为intel有rep指令,所以你可以把它和insb / w结合起来,硬件解码器会在你做I / O周期时自动增加地址(如果你有好的/友好的硬件人员和你一起工作的话)。 所以你可以在地址寄存器中写入起始地址,并且在处理器和存储器总线上进行复制并且不需要读取和解码时钟周期,就可以将数据快速移入或移出外设。 这种事情现在被认为是一个devise缺陷,这要归功于现代超级标量处理器,基于分支预测的提取,您的硬件可以随时体验与执行代码无关的读取,因此您不应该自动增加地址或清除状态寄存器中的位或者修改任何作为读取地址的结果。

内置于386和现在的保护机制实际上使从用户空间访问I / O变得非常容易。 取决于你为什么生活,你的公司生产什么等等。你绝对可以使用用户空间(windows和linux等应用程序)或者内核/驱动程序空间的进出指令系列,这是你的select。 你也可以做一些有趣的事情,例如利用虚拟机,并使用I / O指令与驱动程序进行交stream,但是这可能会使Windows和Linux世界中的人们失望,这种驱动程序/应用程序不会让它变得非常遥远。 其他的海报是正确的,你可能永远不会需要使用这些指令,除非你正在编写驱动程序,你可能永远不会写驱动程序使用I / O映射I / O的设备,因为你知道…这些传统设备的驱动程序已经写好了。 现代devise绝对具有I / O,但它是从存储器映射(从程序员的angular度来看),并使用内存指令而不是I / O指令。 现在如果这是DOS的其他方面,这绝对不是死了,这取决于你可能在哪里build立投票机或加油泵或收银机或DOS基础设备的长长的清单。 事实上,如果你在某些build立PC或PC的外围设备或主板的地方工作,基于DOS的工具仍然被广泛用于testing和分发BIOS更新和其他类似的东西。 我仍然遇到这样的情况,我必须从当前的dostesting程序中编写一个linux驱动程序。 就像不是每个人都可以在NFL中踢球或踢足球一样,百分比明智的做法很less涉及这类东西的软件工作。 所以说这些说明对你来说可能不会比历史课更重要。

举一些实际的例子。

首先学习如何:

  • 创build一个最小的引导程序操作系统,并在QEMU和真正的硬件上运行它,正如我在这里解释的: https : //stackoverflow.com/a/32483545/895245
  • 使一些BIOS调用做一些快速和脏的IO

然后:

  1. PS / 2控制器 :获取键盘上input的最后一个字符的扫描码ID为al

     in $0x60, %al 

    最小的例子

  2. 实时时钟(RTC) :以秒为单位定义墙的时间:

     .equ RTCaddress, 0x70 .equ RTCdata, 0x71 /* al contains seconds. */ mov $0, %al out %al, $RTCaddress in $RTCdata, %al /* al contains minutes. */ mov $0x02, %al out %al, $RTCaddress in $RTCdata, %al /* al contains hour. */ mov $0x04, %al out %al, $RTCaddress 

    最小的例子

  3. 可编程间隔定时器(PIT) :每0x1234 / 1193181秒产生一个中断号码8:

     mov $0b00110100, %al outb %al, $0x43 mov $0xFF, %al out %al, $0x34 out %al, $0x12 

    最小的例子

    一个Linux内核4.2的用法 。 还有其他的。

testing:QEMU 2.0.0 Ubuntu 14.04,以及真正的硬件联想ThinkPad T400。

如何查找端口号: 是否有x86 I / O端口分配规范?

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”。

从程序员的angular度来看,我更喜欢ARM的方式,因为IO指令已经需要魔术地址来操作了,而且我们在64位寻址中有很多未使用的地址空间。

有关具体的ARM示例,请参阅https://stackoverflow.com/a/40063032/895245

如果你不写一个操作系统,那么你将永远不会使用这些指令。

基于x86的机器有两个独立的地址空间 – 您熟悉的内存地址空间,然后是I / O地址空间。 I / O端口地址只有16位宽,并且是一个I / O设备组成部分的参考低级寄存器和其他低级小部件,如串行或并行端口,磁盘控制器等。

没有实际的例子,因为这些只是由设备驱动程序和操作系统使用。

在硬件层面上,大多数微处理器几乎没有或没有内置I / Ofunction。less数处理器有一个或多个引脚可以使用特殊指令打开和closures,和/或一个或多个引脚可以使用特殊分支指令,但这样的function很less见。 相反,I / O通常是通过对系统进行接线来处理的,从而对一系列存储器地址的访问将触发一些效果,或者通过包括与存储器加载/存储操作类似的“入”和“出”会输出“这是一个I / O操作而不是内存操作”。 在16位处理器的时代,有专门的input/输出指令有一些真正的优势。 如今,这样的优势在很大程度上是没有意义的,因为人们可以简单地将一大块地址空间分配给I / O,并且还有大量的内存空间。

由于程序可能通过不恰当地执行I / O指令而在系统上造成相当大的破坏(例如,这样的指令可以执行任意的磁盘访问),所以所有现代操作系统都禁止在用户级代码中使用这样的指令。 一些系统可能允许这样的指令被虚拟化; 例如,如果用户代码尝试写入I / O端口0x3D4和0x3D5,操作系统可能会将此解释为试图设置一些video控制控制寄存器来移动闪烁的光标。 每次用户程序执行OUT指令时,操作系统都会接pipe,查看用户程序正在尝试执行的操作,并采取适当的行动。

在绝大多数情况下,即使操作系统将IN或OUT指令翻译成合适的东西,从操作系统直接请求适当的操作也会更有效率。

这比它更有一些诡计。 它不只是将一个单独的64kb地址空间多路复用到一个“额外的地址总线/芯片select引脚”上。 英特尔8086和8088及其克隆也复用数据总线和地址总线; 所有非常罕见的东西在CPU中。 数据表充满了“最小/最大”configuration的东西,所有的锁存寄存器,你需要挂钩它,使其performance“正常”。 另一方面,它在地址解码中节省了大量的门和“或”门,64kb应该是“足够大的I / O端口”:P。

另外,对于所有那些“仅限驾驶员开发人员”的人,请注意:除了使用英特尔兼容芯片而不是PC以外的其他硬件(他们从来没有真正打算用于IBM个人电脑 – IBM只是拿走了他们,因为他们价格便宜,已经上市),英特尔还销售具有相同指令集(Intel Quark)的微控制器,并且有相同指令集的其他供应商拥有大量的“片上系统”。 我不认为你会设法将任何单独的'用户空间''内核'和'司机'塞进32kb :)。 对于大多数情况来说,这种复杂的“操作系统”既不是最佳的, 在RAM中形成一些UDP数据包,然后将它们放入一些环形缓冲区,然后再点击一下,就不需要30mb的内核和10秒的加载时间了。 如果PIC单片机不够完美,但不想要一个完整的工业PC,这基本上是最好的select。 因此,端口I / O指令确实被大量使用,而不仅仅是“驱动程序开发者”用于较大的操作系统。

CPU通过io端口连接到一些外部控制器。 在旧的x86 pc上,我使用I / O端口使用软盘驱动器。 如果你知道什么命令接受设备控制器,你可以通过它的端口进行编程。

在现代世界中,您将永远不会使用端口指令。 如果你是(或将会)驱动程序开发人员,则为例外。

有关I / O端口的更多详细信息http://webster.cs.ucr.edu/AoA/DOS/ch03/CH03-6.html#HEADING6-1