计算机 CPU 如何执行软件应用程序

How computer CPU executes a Software Application

我正在扩展 What happens when a computer program runs? and from the discussion on Stanford CS101 site Software: Running Programs 上提出的问题。 CS101 网站引用

The machine code defines a set of individual instructions. Each machine code instruction is extremely primitive, such as adding two numbers or testing if a number is equal to zero. When stored, each instruction takes up just a few bytes. When we said earlier that a CPU can execute 2 billion operations per second, we meant that the CPU can execute 2 billion lines of machine code per second.

A program, such as Firefox, is made up of a sequence of millions of these very simple machine code instructions. It's a little hard to believe that something as rich and complicated as Firefox can be built up out of instructions that just add or compare two numbers, but that is how it works. A sand sculpture can be rich and complicated when viewed from a distance, even though the individual grains of sand are extremely simple.

我不明白的是如何将 Firefox window 或 GUI 翻译成简单的 CPU 指令,这些指令只是添加或比较两个数字?如何知道 CPU 执行的实际指令以调出 Firefox Window?在搜索栏中输入的用户搜索怎么样?这转化为 CPU 指令是什么意思?

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

正如我在评论中提到的,使用 Linux 中的 objdump -d 之类的反汇编工具可以帮助您获取 binary/executable 文件并生成包含整个文件的汇编指令集程序。

例如,如果您在 notepad.exe 上使用 objdump -d(这不会完全准确或有见地,因为 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    [=10=]xd0,%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 反汇编程序相同.

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

作为一个简短的回答,Firefox window 使用系统调用。 syscall 指令使程序从用户态跳转到内核。它跳转到 LSTAR64 寄存器中指定的地址。系统调用可以是写入屏幕、写入文件等的调用。键盘本身由 Intel 的 xHC 轮询,当软件(OS)检测到按下某个键时,它将在当前具有焦点的应用程序的消息队列中发送消息。在内核模式下,不同的硬件,如写入屏幕的 GPU 或写入 read/write USB 设备的 xHC,将与使用 DMA 的 PCI 设备的 MMIO(内存映射 IO)进行交互。今天一切都是 PCI。 PCI 是 DMA,因为它直接写入 RAM。它也是 MMIO,因为要与 PCI 设备交互,您只需在常规位置写入 RAM。这允许 read/write 这些 PCI 设备的一些特殊寄存器并告诉它们做一些事情(将在此位置按下的键写入 RAM,使像素改变颜色等)。

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

上的工作方式类似

系统调用

The machine code defines a set of individual instructions. Each machine code instruction is extremely primitive, such as adding two numbers or testing if a number is equal to zero.

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

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

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

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

每种类型的呼叫都有一个在 RAX 寄存器中传递的数字,这些数字从 OS 到 OS 不等。在 Windows,您将有不同的系统调用编号(甚至可能是不同的寄存器来传递系统调用编号)。

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

正在启动

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

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

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

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

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

X 服务器

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

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

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

为了写入屏幕,X 服务器在 libdrm 中进行调用,libdrm 本身进行系统调用。 libdrm 库将使用 /dev/dri/ 中名为 card0 或 card1 的文件(card0 是集成 GPU,card1 是独立 GPU)。因此,库将使用 ioctl 调用 (https://man7.org/linux/man-pages/man2/ioctl.2.html) on the card* file to control the graphics card directly (http://betteros.org/tut/graphics1.php).

Mesa3D 项目一直在尝试支持多种具有开源 driver 的图形卡。 NVIDIA 失败了,因为他们不合作。 NVIDIA 显卡有自己的闭源 drivers,即使内核是 运行.

也可以作为模块安装

这些闭源 driver 随 OpenGL 库实现一起提供。因此,一旦启用某个封闭源 driver,X 服务器将开始在库中进行 OpenGL 调用以写入屏幕。这也需要与 glx 库链接。否则,它将使用显卡的 framebuffer 模式或 VESA 模式。

字符设备

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

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

司机

阅读以下内容:How does loading of kernel module work in linux?

PCI

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

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