【问题标题】:The lowest level function to handle a user input处理用户输入的最低级函数
【发布时间】:2019-10-26 08:08:37
【问题描述】:

以下代码我已读取用户输入,类似于C 语言中的gets 函数。

section .text
global _start

_start:
    mov eax, 3          ; Read user input into str
    mov ebx, 0          ; |
    mov ecx, str        ; | <- destination
    mov edx, 100        ; | <- length
    int 80h             ; \

    mov eax, 1          ; Return
    mov ebx, 0          ; | <- return code
    int 80h             ; \

section .data
    str: times 100 db 0 ; Allocate buffer of 100 bytes

我不确定 Linux 是如何处理我的代码的,但我很好奇这些代码是如何在英特尔机器上本地处理的(没有任何操作系统)。据我所知,通过中断向量表查找中断80h,并调用相关代码函数。但是,再低一点,那段代码是怎么写的呢?我想知道处理这种功能的算法是怎样的?

谁能告诉我如何在实际机器上找到处理用户输入最低级别的完整功能代码?我对了解函数以及找到获取此类代码的方法都很感兴趣。

【问题讨论】:

  • 您删除了“linux-kernel”标签?你知道这个用户空间代码依赖于像 Linux 这样的操作系统,对吧?它只是调用内核。您的问题是“该代码是如何编写的”,在这种情况下 该代码 是 Linux 内核。这里没有任何与“运行”的操作系统分开的 x86 机器代码,操作系统直接通过 CPU 从 x86 加载/存储指令(编译器从 C 生成)生成的 PCI 消息与真正的硬件对话。
  • 我想你希望你的问题能得到一个简短的回答,只需要内核方面的一些指令。情况远非如此,除非您想使用 BIOS 或 UEFI 服务。但这与在操作系统下使用系统调用没有什么不同,就哪种 CPU 指令实际驱动硬件而言。
  • 顺便说一句,我删除了interrupt 标签,因为它只是调用 32 位 Linux 系统调用的机制。 int xx 不参与实际访问键盘硬件,并且 CPU 没有任何内置的中断处理程序来执行此操作。 (BIOS 可能设置了一些,但您询问了本机/低级,例如操作系统如何在内部进行操作,这 涉及通过旧版 BIOS 接口进行操作。)如果您运行 这段代码没有操作系统,它只会崩溃。
  • @PeterCordes,非常感谢您的全面回答。我其实对操作系统之外的底层代码很感兴趣。
  • 没有看到你的回复,因为你没有@notify我。在非 x86 嵌入式系统上,除了引导加载程序之外,您通常没有为您实现任何东西的固件。所以是的,您需要的 USB 驱动程序与我在答案中链接的完全一样。您可能会简化为控制台屏幕/键盘的 API,而不是使用可重定向的文件描述符,但您仍然需要至少一个基本的 USB-HID 驱动程序才能与 USB 键盘通信。它的代码可能“看起来像”Linux 的usbhid/usbkbd.c,但可能不太便携/模块化,并且不关心那么多特殊情况。

标签: assembly x86 keyboard low-level


【解决方案1】:

在最低级别(裸机),没有您使用 int xx 指令“调用”的“函数”。 int xx 只能调用在 CPU 上运行的其他代码,不会发生特殊情况。

与硬件通信涉及运行代码,这些代码使用加载或存储来生成 PCI/PCIe 事务,或使用in/out 指令来访问 I/O 空间。 (或者至少可以访问一些特殊的物理地址;在较旧的计算机上,它不一定是 PCI,现代 CPU 上的一些 MMIO 地址实际上并没有脱离芯片,因此并不真正涉及 PCI。)

据我所知,通过中断向量表查找中断80h,并调用相关代码函数。但是,再低一点,那段代码是怎么写的呢?

是的,Linux 内核(用 C 和汇编语言编写)设置了 IDT,因此用户空间 int 80h 将在其 int80 处理程序入口点进入内核。

int 80h / eax=3 在用户空间中只是分派到 Linux 内核中的 sys_read 函数,它实现了 POSIX read() 函数/系统调用。 (请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?,了解有关向系统调用表分派的内核方面的一些信息)。

int 0x80(或更高效的sysentersyscall 指令)视为跨权限边界进行函数调用的方法。所有的魔法都是(如您所料)在内核内部实现该函数以及它调用的其他函数。 (以及“一切都是文件”的整个 Unix 模型;这与您用来读取磁盘文件的系统调用相同。)

它需要一个文件描述符 arg,在您的情况下为 stdin = 0。这只是一个文件,通常是连接到终端仿真器的 TTY 或伪 tty,它与 X11 服务器通信以获取键盘事件。您可以在 Linux 文本控制台 (ctrl+alt+f2) 上运行,在这种情况下,内核正在运行带有键盘输入的终端仿真器来自连接的任何物理键盘。或者您可以重定向来自任何类型文件的输入。

如果我在我的系统上重定向来自 /dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-kbd 的输入,我可以获得一些原始按键事件,但不是 ASCII 文本格式。 (您可以安全地sudo cat 该文件;输入仍然会发送到您的X 服务器以及,因此您可以控制-C 它)。这是与键盘驱动程序对话的一种更直接的方式,但在实际获取访问连接到键盘的 USB 主机控制器的代码之前,您仍然需要通过 Linux 的 HID(人机接口设备)和事件子系统。


您无法在裸机上做任何类似的事情;您将使用in / out 或 MMIO 加载/存储通过 USB 主机控制控制器(例如 eHCI 或 xHCI)与键盘对话。 https://wiki.osdev.org/Universal_Serial_Bus

(使用 PS/2 键盘的 BIOS 仿真,或在具有真正 PS/2 键盘控制器的旧机器上,您可以更轻松地进行对话。在早期的 PC 时代,许多不同的程序在真实环境中运行模式将直接与硬件交互,因此对于如何访问它有更简单的标准,通常使用 inout 指令到众所周知的端口号。不需要 PCI 总线枚举或任何需要的东西。

在现代 PC 中,您仍然可以这样做,但大多数情况下,它是由捕获访问的软件伪造的。根据定义,BIOS 仿真不是 bare 金属。只有主板固件开发人员真正编程裸机。系统管理模式允许固件设置即使在操作系统启动后也可以运行的挂钩。幸运的是,大多数系统并没有做太多/任何事情,尽管仍然有内核应该读取而不是直接探测硬件的 ACPI 表。)


如果您启动旧版引导加载程序,固件将切换回实模式并设置一堆“BIOS 服务”,您可以通过 int 10h 和其他中断号使用这些服务。就像在 Linux 内核下运行一样,您离“直接”与真实硬件对话并不遥远。所有设备驱动程序的详细信息都隐藏在标准 API 后面。 https://wiki.osdev.org/BIOS

如果您启动现代 UEFI 引导加载程序,您将再次获得用于访问屏幕/键盘的标准 API,以及固件提供的“驱动程序”代码。它就像一个最小的内核。 https://wiki.osdev.org/UEFI


一旦真正的内核(如 Linux)启动,它就拥有真正的 USB 控制器的设备驱动程序。如果 BIOS 设置了 PS/2 硬件的仿真,内核会替换/禁用它。

就像我说的,这个 Linux 内核代码是用 C 编写的,带有一些内联的 asm 包装器。

几乎可以肯定,这比您想涉足的代码要多,但它是您问题的答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多