1

我正在扩展关于计算机程序运行时会发生什么的问题?并来自斯坦福 CS101 站点Software: Running Programs的讨论。CS101现场报价

机器代码定义了一组单独的指令。每个机器代码指令都非常原始,例如添加两个数字或测试一个数字是否等于 0。存储时,每条指令只占用几个字节。当我们前面说 CPU 每秒可以执行 20 亿次操作时,我们的意思是 CPU 每秒可以执行 20 亿行机器代码。

一个程序,比如 Firefox,是由数百万条这些非常简单的机器代码指令组成的。有点难以相信像 Firefox 这样丰富而复杂的东西可以通过添加或比较两个数字的指令来构建,但这就是它的工作原理。一个沙雕从远处看可以是丰富而复杂的,即使单个的沙粒非常简单。

我不明白的是如何将 Firefox 窗口或 GUI 转换为简单的 CPU 指令,只需添加或比较两个数字?怎么知道 CPU 执行了哪些指令来启动 Firefox 窗口呢?在搜索栏中输入的用户搜索怎么样?这将什么转化为 CPU 指令?

如果 Firefox 是一个复杂的例子,那么像记事本这样的简单应用程序呢?实际上是否可以看到从运行记事本到键入ABCDEFGHIJ并将其保存为的所有指令test.txt

4

2 回答 2

2

正如我在评论中提到的,使用objdump -dLinux 中的反汇编工具可以帮助您获取二进制/可执行文件并生成包含整个程序的汇编指令集。

例如,如果您使用objdump -don notepad.exe(它不会完全准确或有洞察力,因为objdump它适用于 Linux 而记事本是 Windows 程序),您将看到:

notepad.exe:     file format pei-x86-64


Disassembly of section .text:

0000000140001000 <.text>:
   140001000:   cc                      int3   
   140001001:   cc                      int3   
   140001002:   cc                      int3   
   140001003:   cc                      int3   
   140001004:   cc                      int3   
   140001005:   cc                      int3   
   140001006:   cc                      int3   
   140001007:   cc                      int3   
   140001008:   40 55                   rex push %rbp
   14000100a:   48 8d 6c 24 e1          lea    -0x1f(%rsp),%rbp
   14000100f:   48 81 ec d0 00 00 00    sub    $0xd0,%rsp
   140001016:   48 8b 05 8b 14 03 00    mov    0x3148b(%rip),%rax        # 0x1400324a8
   14000101d:   48 33 c4                xor    %rsp,%rax
   ...

我正在使用objdump因为我在 Linux 上,但正如@PeterCordes 在评论中指出的那样,汇编指令应该与 Windows 反汇编程序相同。

输出有超过 43k的汇编指令,因此破译汇编的objdump每个部分所做的工作需要很长时间。这是记事本可以执行的全部指令集。因此,如果您想知道执行了哪些汇编指令以及执行诸如键入ABC和保存之类的操作时的顺序,您将需要使用某种跟踪器(例如gdb)来单步执行那些特定的执行指令。

于 2021-09-16T18:04:46.857 回答
2

简短的回答是,Firefox 窗口使用系统调用。syscall 指令使程序从用户模式跳转到内核。它跳转到 LSTAR64 寄存器中指定的地址。系统调用可以是写入屏幕、写入文件等的调用。键盘本身由 Intel 的 xHC 轮询,当软件(操作系统)检测到某个键被按下时,它将发送一条消息当前具有焦点的应用程序的消息队列。从内核模式来看,不同的硬件,如用于写入屏幕的 GPU 或用于读/写 USB 设备的 xHC,将使用 DMA 的 PCI 设备的 MMIO(内存映射 IO)进行交互。今天一切都是PCI。PCI 是 DMA,因为它直接写入 RAM。它也是 MMIO,因为要与 PCI 设备交互,您只需在常规位置写入 RAM。

较长的答案相当复杂。我会试着把它分解成更小的部分。另外,我可能会说一些事情是错误的(因为我主要是从头开始写作),但我会尽力提供事实信息。随时纠正我可能说的任何错误。在这个答案中,我将以 x86-64 上的 Linux 为例。事情将在 Windows 上类似地工作。

系统调用

机器代码定义了一组单独的指令。每个机器代码指令都非常原始,例如添加两个数字或测试一个数字是否等于 0。

x86-64 处理器具有这些指令集之一,称为指令集。所有 x86 处理器主要有 2 个制造商:AMD 和 Intel。AMD 从 Intel 获得 x86 架构的许可,用于制造自己的处理器。

在最初只有分段的前 32 位处理器之后引入了分页机制。分页机制允许在每个页表中设置/取消设置一个位,以确定该页是主管还是用户。显然,不能从用户页面访问主管页面。这允许通过将内核与用户模式( https://wiki.osdev.org/Paging)隔离来提供安全性。

指令集中的指令之一是syscall具有特定二进制编码的指令(我不知道)。大多数汇编语言都支持将它们汇编成正确二进制格式的系统调用指令。

syscall 指令使处理器跳转到 LSTAR64 MSR(模型特定寄存器)中指定的地址。这提供了一种从用户模式跳转到内核的安全机制。内核将在该寄存器中设置特定入口点的地址。Linux 的入口点是文件 /arch/x86/entry/entry_64.s。该文件在汇编中定义,将调用 C 函数来完成主要工作。

每种类型的调用都有一个在 RAX 寄存器中传递的数字,这些数字因操作系统而异。在 Windows 上,系统调用将有不同的编号(甚至可能使用不同的寄存器来传递系统调用编号)。

最后,一旦执行了 syscall 指令,处理器现在处于内核模式代码,并且该代码可以访问整个 RAM 和所有 IO 设备。

引导

要了解 GUI 是如何启动的,您需要了解引导过程。今天,计算机使用 UEFI 启动。UEFI 标准将启动时可用的系统调用定义为某种小型操作系统。UEFI 固件因此设置了这个小型操作系统,以允许操作系统在启动时设置计算机。

这些 UEFI 系统调用允许从磁盘读取文件、获取一些 ACPI 表、设置图形模式、获取内存映射等。UEFI 固件内置了驱动程序以支持当今计算机上存在的所有硬件。这允许为操作系统提供一个引导接口,以便能够从磁盘获取内核文件,而无需在引导加载程序本身中使用巨大的临时驱动程序。

因此,操作系统开发人员提供了一个使用 EDK2 或 gnu-efi 编译(实际上)的 UEFI 应用程序。UEFI 应用程序将使用引导期间出现的系统调用编译为代码,以从磁盘获取内核文件,然后跳转到内核的入口点。

然后内核将控制一切并建立自己的系统调用接口。

对于 Linux,引导非常复杂,尤其是在 systemd 出现之后。Linux 内核将启动 sbin/init 作为计算机的第一个进程。在最近的发行版中,sbin/init 是 systemd 的符号链接。systemd 程序将从磁盘读取单元文件,这些文件是特殊文件,告诉 systemd 要做什么以及要启动哪些其他进程。在要启动的进程中,是主 GUI(桌面)本身。

X 服务器

X 服务器是一个特殊的程序,它在几乎所有 Linux 发行版的第一个进程中启动。X 服务器充当本地服务器(也可以不是本地服务器),以便能够使用套接字与其通信。套接字实现存在于 libstdc++ 中,用于 C++。

X 服务器还有一个名为 X11 的库,它定义了一组要调用的函数,这些函数主要完成通过套接字与 X 服务器通信的工作。

X 服务器使用/dev/input/目录和其中存在的字符设备从不同的输入设备获取输入。

为了写入屏幕,X 服务器在 libdrm 中进行调用,而 libdrm 会自己调用系统调用。libdrm 库将使用一个/dev/dri/名为 card0 或 card1 的文件(card0 是集成 GPU,card1 是离散 GPU)。因此,该库将在 card* 文件上使用 ioctl 调用 ( https://man7.org/linux/man-pages/man2/ioctl.2.html ) 来直接控制显卡 ( http://betteros.org/图/graphics1.php)。

Mesa3D 项目一直在尝试使用开源驱动程序支持多种显卡。英伟达失败了,因为他们不合作。NVIDIA 显卡有自己的闭源驱动程序,即使在内核运行时也可以作为模块安装。

这些闭源驱动程序提供了 OpenGL 的库实现。因此,一旦您启用某个闭源驱动程序,X 服务器将开始在库中进行 OpenGL 调用以写入屏幕。这也需要与 glx 库链接。否则,它将使用显卡的帧缓冲模式或 VESA 模式。

字符设备

您可能听说过这样一句话:Linux 中的一切都是文件。这是由于虚拟文件系统将大多数设备作为文件呈现给用户模式。有几种类型的虚拟文件,其中有字符设备。

字符设备有 open、read、write 和 ioctl 调用。因此,X 服务器将从字符设备中读取数据,以收集来自系统不同输入设备的输入。

驱动程序

阅读以下内容:内核模块的加载如何在 linux 中工作?

PCI

您今天的鼠标和键盘可能是 USB。计算机通过最初由英特尔创建的可扩展主机控制器 (xHC) 与 USB 进行交互。我不知道 AMD 是制造自己的芯片版本还是从英特尔购买芯片。

您可以在那里阅读我的答案以获取有关其工作原理的准确信息:https ://cs.stackexchange.com/questions/141870/when-are-a-controllers-registers-loaded-and-ready-to-inform-an- io-操作/141918#141918

于 2021-09-16T20:06:28.707 回答