【问题标题】:Abstraction and bare-metalness with OSes?操作系统的抽象和裸机?
【发布时间】:2018-05-17 18:31:36
【问题描述】:

如果我有一个 CPU 并且我编写了一个程序并且我想存储一个值(将一个值从寄存器复制到内存 (RAM)),那么我会在 CPU 的指令集中使用一个指令(假设这是x86 CPU) 这样做?

第二个问题,x86 指令集中的指令是在 RAM 中名为MOV 的特定地址设置值吗?

第三个问题。 BIOS、UEFI、内核和引导加载程序都使用 x86 指令集中的 MOV 指令来执行此操作(将值(如 10)分配给 RAM 中的特定地址)对吗?

第四个问题。在操作系统(带有内核(如Linux))环境中运行的程序在请求时不使用MOV 指令来分配一块内存,而是要求内核代表它们来分配?

第五题。我在第四个问题中描述的是否称为系统调用(当操作系统环境中运行的程序要求内核代表它做某事(在这种情况下给它一些内存))?

【问题讨论】:

  • 1.是的。 2.是的,最常见的x86存储指令是mov。例如mov dword [rdi], 10。 3. 这很奇怪。您可能会编写一个没有任何静态数据的引导加载程序,因此您的所有 mov-immediate 指令都将使用寄存器目标。
  • 对于第三个问题,我将通过询问来重新表述它。该问题中提到的所有程序(BIOS、UEFI、内核和引导加载程序)是否都使用 CPU 指令集中的指令将值存储在内存 (RAM) 中?
  • 是的,任何需要比寄存器更多空间的重要程序,或者需要调用函数或系统调用来返回内存中的值,都必须使用内存。引导加载程序必须做的部分工作就是从磁盘加载到内存中,因此虽然它本身可能不使用任何存储指令,但它调用的 BIOS 或 UEFI 函数会代表它这样做。

标签: x86 computer-science cpu-architecture


【解决方案1】:

0.我认为您对汇编语言的了解还不够,甚至无法提出一个真正填补您所缺少的任何空白的合理问题,但我已尝试按原样回答您的问题。我真的不确定你真正想知道什么。

也许可以查看 Matt Godbolt 的 CppCon2017 演讲:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” 了解 x86 asm 的初学者介绍。

但我认为您缺少更多关于堆栈/堆/静态存储位置的概念。见Stack, Static, and Heap in C++


  1. 是的

  2. 是的,最常见的 x86 存储指令是 mov。例如mov dword [rdi], 10。或者在指令中也使用绝对地址编码,mov dword [my_static_32_bit_location], 10

  3. 这很奇怪。您可以编写没有任何静态数据的引导加载程序。您所有的 mov-immediate 指令都将使用寄存器目标。

  4. mov 永远不会分配保留内存。在操作系统下运行的程序可以有静态数据。例如考虑这个 C 函数:

    unsigned LCG_rand() {
        static unsigned seed = 0x1234;
        seed = seed * 0x5677 + 0x7723;  // Linear Congruential Generator with badly chosen coefficients I just made up
        return seed;                    // mod type-width is implicit
    }
    

    针对 x86-64 Linux 的 gcc7.2 将其编译为 (Godbolt compiler explorer):

    LCG_rand:
        imul    eax, DWORD PTR seed.2294[rip], 22135    # eax = load(seed) * 22135
        add     eax, 30499                              # eax += 30499
        mov     DWORD PTR seed.2294[rip], eax           # store the old value back to memory.
        ret
    
    .data            # static read-write data goes in the .data section
    .align 4
    seed.2294:       # label which the compiler uses to refer to it.
        .long   4660
    

    .data 部分保存具有非零初始值设定项的静态读写数据。初始值按字面意思存储在可执行文件中。它链接到可执行文件的数据段。当你运行它时,可执行文件的数据段被映射到进程的内存空间中,具有读/写权限,即写时复制(私有)。 (因此,将数据写入内存不会更新磁盘上的数据,就像您将 mmap 与 MAP_SHARED 一起使用时那样。)

    这些地址是链接时常量,所以程序可以直接使用它。 (在上面的代码中,该函数使用PC相对寻址,但也支持绝对寻址。You may have to use -no-pie on some gcc setups, though.

    完全相同的机器代码可以在裸机环境中正常工作,即使禁用分页,所以您使用的地址是物理地址,而不是允许每个进程的通常虚拟地址有自己的记忆观。 (错误,但您可能必须处于 16 位或 32 位模式,我忘记了 x86-64 长模式是否甚至可以在禁用分页的情况下工作。)

  5. 是的。有关用户空间进程如何进行系统调用的详细信息,请参阅What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

【讨论】:

  • 啊,我想我现在理解得更好了。正如您所说,(您编写的程序的)相同的机器代码将在裸机环境中运行。因此,任何程序,无论是内核(如 Linux)还是用户级程序(人类与之交互的东西)如网络浏览器(如 FireFox)都将使用 CPU 的指令集指令来完成工作,在这种情况下想要在内存中存储一​​些东西,他们会使用MOV 之一(或存储指令)来做到这一点?
  • 所以在裸机情况下,它会使用物理地址运行,但是当内核出现时,它会以不同的方式为需要获取内存的程序映射内存。跨度>
  • @TristanB.Kildaire 在内存中存储和加载是完成工作的正常部分。 32 位 x86 只有 7 个通用寄存器。代码一次需要跟踪超过 7 个变量是很常见的,因此编译器使用堆栈内存作为额外的本地存储空间。我们只是在谈论 RAM,而不是像硬盘驱动器这样的永久性非易失性存储。即使是 64 位代码也经常使用堆栈。
  • 啊,好吧。我想我模糊了裸机程序和在操作系统环境中运行的程序之间的界限。两者显然都编译为汇编并使用给定的 CPU 指令集来完成工作。在裸机 acse 中,程序使用物理地址并完全控制机器,而在 OS 环境中,它具有虚拟内存访问(由内核控制),然后相同的程序将使用该访问。 这是正确的吗?
  • @TristanB.Kildaire:裸机和操作系统下分配内存的方式不同,但你总是有一个堆栈,如果你想要任何静态数据你可以拥有它。对于裸机,您可以编写自己的分配器来为您提供动态存储,因为您已经拥有所有内存(这是裸机的定义特征之一)。但是一旦你确定了你的代码可以使用哪些地址,这就不是“模糊线”的问题了。但也许这只是你的困惑的错误表达。我希望。
猜你喜欢
  • 1970-01-01
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
  • 2015-06-13
  • 1970-01-01
相关资源
最近更新 更多