所以有几件事情在这里谈论。让我们一次一个覆盖他们。
首先,我假设你在没有操作系统(即,制作你自己的操作系统)的情况下运行,或者在像MS-DOS那样不能执行I/O的操作系统中运行。正如Olaf正确指出的那样,in al, 0x60
不适用于现代保护模式的操作系统;也不会在USB键盘上工作(除非您正在模拟器或假装提供经典PS/2键盘的虚拟机中运行)。
其次,我假设你正在编写一个32位的CPU。对于16位CPU,32位CPU和64位CPU,C ABI(应用程序二进制接口)不同,因此根据所使用的CPU不同,代码的读取方式也会有所不同。
第三,端口60h是一个奇怪的野兽,从我为它写驱动程序已经很长时间了。您读取的值不是您认为您将在很多时间读取的值,并且有E0h扩展代码,并且存在Pause键的行为。编写一个无bug的键盘驱动程序比看起来要困难得多。但是让我们忽略这个问题的一切。因此,我们假设你没有操作系统来阻挡,32位CPU,只有最基本的按键。你将如何将数据从键盘传递给C函数?几乎和你一样:
in al, 0x60
movzx eax, al
push eax
call cFunction
为什么这是正确的?
那么,第一行用键盘加载一个8位寄存器;它必须是al
,因为这是in
可以写入的唯一的8位寄存器。
32位C ABI期望按反向调用顺序将函数的参数压入堆栈。所以为了调用C函数,在它之前必须有一个push
指令。但是,在32位模式下,所有push
指令都是32位大小的,所以您只能推eax
,ebx
,esi
, edi
等等:您无法直接推al
。 (即使你可以 - 而且在技术上,你可以使用直接堆栈写入 - 它会错位,因为在32位模式下,每个项目必须对齐到一个4字节的边界。)所以唯一的方法来推动值首先将其从8位提升到32位,而movzx
确实很好。
对于它的价值,还有其他方法可以做到这一点。您可以在in
之前清除eax
:
xor eax, eax
in al, 0x60
push eax
call cFunction
该解决方案比表现原来的解决方案差些;它有一个部分寄存器失速的成本:处理器内部实际上不保持al
作为eax
的一部分,而是作为一个单独的寄存器;任何尝试将不同大小的子寄存器混合在一起会导致处理器在能够这样做之前停顿:当你在这里push eax
时,处理器认识到al
被先前的指令改变了,并且停止一个时钟周期到很快混搭al
的位到eax
所以它仍然看起来像al
竟是eax
的一部分。
值得指出的是,如果你在经典的16位模式(8086,或“286保护模式)的时候,调用顺序稍有不同:
in al, 0x60
movzx ax, al
push ax
call cFunction
在这种情况下,int
为16大小,所以做16位的一切都是正确的。另外,在64位模式下,你需要使用rax
代替:
in al, 0x60
movzx rax, al
push rax
call cFunction
即使cFunction可能已被编译int
是只有32位,在64位模式下任务堆栈对齐要求,即64位的值将被推送。 C函数将正确读出64位值作为32位值,但您只能将其推送为64位。
所以你有它。根据您的CPU和环境,与C ABI交互的各种方式将端口数据导入您的功能。
你不想'推ax'(如果这就是即使你的汇编允许的),因为你需要按4个字节一般,在这种情况下C函数需要一个4字节的无符号整型反正。你做的是对的。 – Jester
如果您使用的是现代操作系统或使用USB键盘,则无法工作。 – Olaf
您将AL扩展到EAX为零,所以它的值是RAX = EAX = AX = AL = scancode。 –