计算机程序运行时会发生什么?

时间:2011-03-02 01:50:26

标签: c++ memory operating-system x86 computer-architecture

我知道一般理论,但我不能适应细节。

我知道程序驻留在计算机的辅助内存中。程序开始执行后,它将完全复制到RAM中。然后处理器一次检索几条指令(它取决于总线的大小),将它们放入寄存器并执行它们。

我也知道计算机程序使用两种内存:堆栈和堆,它们也是计算机主存储器的一部分。堆栈用于非动态内存,堆用于动态内存(例如,与C ++中new运算符相关的所有内容)

我无法理解的是这两件事是如何联系的。用于执行指令的堆栈在什么时候?指令从RAM,堆栈到寄存器?

4 个答案:

答案 0 :(得分:158)

答案 1 :(得分:59)

Sdaz在很短的时间内获得了大量的赞成票,但令人遗憾的是,他们对指令如何在CPU中移动产生了误解。

问的问题是:

  

指令从RAM,堆栈到寄存器?

Sdaz说:

  

另请注意,这些不是C代码执行的实际行。编译器已将它们转换为可执行文件中的机器语言指令。然后(通常)将它们从TEXT区域复制到CPU管道中,然后复制到CPU寄存器中,然后从那里执行。

但这是错误的。除了自修改代码的特殊情况外,指令永远不会进入数据路径。它们不是,也不可能是从数据路径执行的。

x86 CPU registers是:

  • 一般登记册 EAX EBX ECX EDX

  • 段寄存器 CS DS ES FS GS SS

  • 索引和指针 ESI EDI EBP EIP ESP

  • 指示器 EFLAGS

还有一些浮点和SIMD寄存器,但为了讨论的目的,我们将它们归类为协处理器的一部分而不是CPU。 CPU内部的内存管理单元也有自己的一些寄存器,我们将再次将其视为一个单独的处理单元。

这些寄存器都不用于可执行代码。 EIP包含执行指令的地址,而不是指令本身。

指令通过数据(哈佛架构)在CPU中完全不同的路径。所有当前的机器都是CPU内部的哈佛架构。这些天大多数也是缓存中的哈佛架构。 x86(您的普通台式机)是主存储器中的Von Neumann架构,意味着数据和代码混合在RAM中。这就是重点,因为我们正在讨论CPU内部发生的事情。

计算机体系结构中教授的经典序列是fetch-decode-execute。存储器控制器查找存储在地址EIP的指令。指令的位经过一些组合逻辑,为处理器中的不同多路复用器创建所有控制信号。并且在一些周期之后,算术逻辑单元到达结果,该结果被计时到目的地。然后获取下一条指令。

在现代处理器上,事情的工作方式略有不同。每个传入指令都被转换成一系列微码指令。这启用了流水线操作,因为稍后不需要第一个微指令使用的资源,因此他们可以从下一条指令开始处理第一个微指令。

最重要的是,术语有点混乱,因为 register 是D触发器集合的电气工程术语。并且指令(或特别是微指令)可以很好地临时存储在这样的D触发器集合中。但是,当计算机科学家或软件工程师或普通开发人员使用术语 register 时,这并不意味着什么。它们表示上面列出的数据路径寄存器,这些寄存器不用于传输代码。

数据路径寄存器的名称和数量因其他CPU体系结构而异,例如ARM,MIPS,Alpha,PowerPC,但它们都执行指令而不通过ALU。

答案 2 :(得分:17)

进程执行时内存的确切布局完全取决于您正在使用的平台。考虑以下测试程序:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int stackValue = 0;
    int *addressOnStack = &stackValue;
    int *addressOnHeap = malloc(sizeof(int));
    if (addressOnStack > addressOnHeap)
    {
        puts("The stack is above the heap.");
    }
    else
    {
        puts("The heap is above the stack.");
    }
}

在Windows NT(及其子代)上,该程序通常会生成:

  

堆在堆栈上方

在POSIX盒子上,它会说:

  

堆栈位于堆

之上

@Sdaz MacSkibbons在这里很好地解释了UNIX内存模型,所以我在此不再重复。但这不是唯一的记忆模型。 POSIX需要此模型的原因是sbrk系统调用。基本上,在POSIX盒子上,为了获得更多内存,一个进程只是告诉内核将“hole”和“heap”之间的分隔符进一步移动到“hole”区域。无法将内存返回给操作系统,操作系统本身也无法管理堆。您的C运行时库必须提供(通过malloc)。

这也对POSIX二进制文件中实际使用的代码类型有影响。 POSIX盒(几乎普遍)使用ELF文件格式。在这种格式中,操作系统负责不同ELF文件中库之间的通信。因此,所有库都使用与位置无关的代码(也就是说,代码本身可以加载到不同的内存地址并仍然可以运行),并且库之间的所有调用都通过查找表来查找控件需要跳转到何处以进行交叉库函数调用。这会增加一些开销,如果其中一个库更改了查找表,则可以利用它。

Windows的内存模型不同,因为它使用的代码类型不同。 Windows使用PE文件格式,它使代码保持位置相关的格式。也就是说,代码取决于加载代码的虚拟内存的确切位置。 PE规范中有一个标志,告诉操作系统在程序运行时,库或可执行文件在内存中的确切位置。如果无法在其首选地址加载程序或库,则Windows加载程序必须 rebase 库/可执行文件 - 基本上,它将位置相关代码移动到指向新位置 - 这不会不需要查找表,因为没有要覆盖的查找表。不幸的是,这需要在Windows加载器中实现非常复杂的实现,并且如果需要重新映射图像,则确实具有相当大的启动时间开销。大型商业软件包经常修改其库,以便在不同的地址开始,以避免变基; windows本身使用它自己的库(例如ntdll.dll,kernel32.dll,psapi.dll等) - 默认情况下都有不同的起始地址)

在Windows上,通过调用VirtualAlloc从系统获取虚拟内存,并通过VirtualFree将其返回给系统(好的,从技术上讲,VirtualAlloc是NtAllocateVirtualMemory,但这是一个实现细节)(对比POSIX,无法回收内存)。这个过程很慢(和IIRC一样,要求你在物理页面大小的块中分配;通常为4kb或更多)。 Windows还提供了自己的堆函数(HeapAlloc,HeapFree等),作为称为RtlHeap的库的一部分,它作为Windows本身的一部分包含在其中,C运行时(即malloc和朋友)通常是实施的。

Windows必须处理旧的80386时,还有许多传统的内存分配API,而这些功能现在构建在RtlHeap之上。有关在Windows中控制内存管理的各种API的详细信息,请参阅此MSDN文章:http://msdn.microsoft.com/en-us/library/ms810627

另请注意,这意味着在Windows上,单个进程(并且通常会)具有多个堆。 (通常,每个共享库都会创建自己的堆。)

(大部分信息来自Robert Seacord的“C和C ++中的安全编码”)

答案 3 :(得分:5)

筹码

在X86架构中,CPU使用寄存器执行操作。堆栈仅用于方便原因。您可以在调用子例程或系统函数之前将寄存器的内容保存到堆栈中,然后将其加载回来以继续您离开的操作。 (你可以手动没有堆栈,但它是一个经常使用的功能,所以它有CPU支持)。但是如果没有PC中的堆栈,你几乎可以做任何事情。

例如整数乘法:

MUL BX

将AX寄存器与BX寄存器相乘。 (结果将在DX和AX中,DX包含更高的位)。

基于堆栈的计算机(如JAVA VM)使用堆栈进行基本操作。上面的乘法:

DMUL

这会从堆栈顶部弹出两个值并将tem相乘,然后将结果推回堆栈。堆栈对于这种机器至关重要。

一些更高级的编程语言(如C和Pascal)使用后面的方法将参数传递给函数:参数按从左到右的顺序被推送到堆栈,并由函数体弹出,并返回返回值。 (这是编译器制造商做出的选择,以及X86使用堆栈的方式滥用)。

堆是另一个仅存在于编译器领域的概念。它需要处理变量后面的内存的痛苦,但它不是CPU或操作系统的功能,它只是内存管理系统给出的内存块的选择。如果你愿意,你可以多次这样做。

访问系统资源

操作系统有一个公共界面,您可以如何访问其功能。在DOS中,参数在CPU的寄存器中传递。 Windows使用堆栈传递OS函数的参数(Windows API)。