【问题标题】:What would be an example of position-dependent code?位置相关代码的示例是什么?
【发布时间】:2023-02-15 05:42:23
【问题描述】:

与位置相关的代码被编写为加载到内存中的特定物理地址并从中运行。这种类型的代码带来的问题之一是它阻碍了处理器并发运行多个进程的能力,主要是当编写为从同一地址运行的不同进程试图同时执行时。

话虽如此,我从未遇到过指定要在其上执行的内存地址的代码,因此我发现很难想象这样的代码会是什么样子。我可以看到给定的代码可以指定特定变量要存储在内存中的地址,但是当涉及到要加载程序的[第一个]内存地址时,我看不到为什么这不是操作系统的工作而不是程序的责任。

【问题讨论】:

  • jmp 0x123(跳转到确切地址)是“位置相关代码”的一个例子......但你正在寻找什么类型的例子还不清楚。请注意,“给我一个……的例子”很少是关于 SO 的主题,因为这样的问题过于开放,而且这个问题可能过于宽泛。另请注意,您以某种方式期望需要/存在某种多任务操作系统来运行程序 - 有大量代码在裸机上运行,​​并且此类代码不需要重新定位。
  • 我知道你提供的代码在某种意义上是“位置相关的”,因为它的执行成功取决于它将被加载的位置,但是我拥有的位置相关代码的主要特征是问题在于它定义了将要加载的内存地址,我发现很难想象它并且看不到背后的实用程序。
  • 恐怕问题/评论中缺少大量上下文。如果您将这个问题作为某些学校课程的一部分 - 问老师/助教。否则可能会重新阅读您使用的术语的定义......虽然我认为这个问题不太可能在 SO 上取得成功,但至少链接到术语的精确定义(例如“位置相关代码”,你的意思是“代码”)并阐明你在什么情况下问这个问题(像 Linux/Windows 这样的通用操作系统,一些定制的操作系统,嵌入式编程,......)。
  • “有一个问题是它定义了它将被加载的内存地址” - 我很难理解为什么你认为这是一个问题 :) - 如果指令说“二进制文件只有在加载时才能工作” 0x100 on {specific CPU} with RAM available in range 0x2000-0x2100” 你会遇到什么类型的“问题”?
  • 我需要查看相关指令的真实代码示例的问题类型,以便我可以更正确地吸收这些概念。我也不明白你为什么要编写一个指定加载位置的程序。这不是操作系统的责任吗?

标签: memory process operating-system ram


【解决方案1】:

这种类型的代码带来的问题之一是它阻碍了处理器并发运行多个进程的能力,主要是当编写为从同一地址运行的不同进程试图同时执行时。

在大多数台式消费类计算机上,都有分页功能。通过分页,CPU 指令包含虚拟地址而不是物理地址。在执行之前,指令操作的地址通过页表传递给MMU(内存管理单元)进行翻译。这些页表可以在物理 RAM 中的任何位置转换虚拟地址。

在今天的计算机上,每个线程都在一个特定的核心上运行。每个内核都有自己的页表基址指针 (PTBP) 寄存器,其中包含第一级页表开头的物理地址。当OS要切换线程时,会保存当前执行线程的信息,并切换到其他线程的信息(包括PTBP寄存器)。

由于虚拟地址 (VA) 可以在 RAM 中的任何位置进行转换,因此每个线程都可以访问所有可用的虚拟地址空间 (VAS)。由于 VAS 跨越超过 100k GB,每个线程的 RAM 量仅受可用物理内存量的限制。只要页表将它们提到的地址转换为不同的物理地址,每个线程都可以从相同的地址开始并且仍然并发执行。

我从未遇到过指定要在其上执行的内存地址的代码,因此我很难想象这样的代码会是什么样子。

您遇到的大多数代码实际上都指定了起始地址。起始地址主要是一个建议,操作系统加载器不一定会遵守。它以这种方式工作,因为在有 ASLR 之前,代码实际上不是位置独立的,只是将所有地址都提到为绝对地址。 就像你提到的,今天大多数代码都是独立于位置的。即使使用与位置无关的代码,您也看不到编译器从您的代码中输出的地址,但其中有很多。编译器负责计算代码中的地址和偏移量以到达某些函数或某些数据。

目前现代计算机上主要有 3 种类型的内存:

  1. 自动记忆(堆栈)

    对于自动存储,编译器只计算堆栈指针寄存器的偏移量。它也可以是基址指针寄存器的偏移量。例如,在没有优化的 x64 上,你会得到类似的东西:

    int main( void ) {
        int a;
        a = 3;
        return 0;
    }
    

    编译为:

    main:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-4], 3
            mov     eax, 0
            pop     rbp
            ret
    

    在这里,您看到第三条指令 mov DWORD PTR [rbp-4], 3 正在访问堆栈,偏移量为 RBP 的 4 个字节,并根据请求将值 3 放置在该地址。

    1. 静态/全局数据

    静态或全局数据是声明为静态或在函数外部声明的数据。该数据在编译后在可执行文件中保留了空间。操作系统在程序加载期间将该数据放入 RAM 中。使用 PC 相对寻址(使用程序计数器的偏移量)访问数据以使访问位置独立。再次在 x64 上你会得到类似的东西:

    int glob;
    
    int main( void ) {
        glob = 5;
        return 0;
    }
    

    编译为:

    glob:
            .zero   4
    main:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR glob[rip], 5
            mov     eax, 0
            pop     rbp
            ret
    

    同样,第三条指令通过取消对 RIP(x64 上的 PC)的偏移量的引用,将 5 放入 glob 整数中。

    1. 堆数据

    堆有几乎一半的 VAS 保留给它的数据。它可以从可执行代码和静态数据的末尾跨越到 VAS 的一半(超过 100k GB)。较高的一半是为内核保留的(显然不需要它)。堆实际上是动态分配的。您需要在 C/C++ 中进行系统调用以获取内存。为此,您将调用 malloc() 或使用 new 关键字。由于该内存是动态的,因此可以使用绝对地址简单地对其进行访问。根据操作的不同,它可能会使用一个临时寄存器来保存值或地址。

【讨论】:

  • 感谢您为这个答案付出如此多的努力,很遗憾它被低估了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-14
  • 2018-05-04
  • 2011-12-01
相关资源
最近更新 更多