当计算机程序运行时会发生什么? [英] What happens when a computer program runs?

查看:219
本文介绍了当计算机程序运行时会发生什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



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



我不能理解的是这两个东西是如何连接的。什么点是用于执行指令的堆栈?

解决方案

这取决于系统,但现代操作系统虚拟内存倾向于加载其过程映像并分配内存,如下所示:

  + --------- + 
|堆栈|函数局部变量,返回地址,返回值等等。
| |通常通过push和pop访问(但可以随机访问
| |;反汇编程序看到)
+ --------- +
|共享|映射共享库(C库,数学库等)
| libs |
+ --------- +
|孔|在堆和堆栈chunk之间分配的未使用的内存,跨越
| |您的最大和最小内存之间的差,减去其他总和
+ --------- +
|堆|动态,随机存取存储器,分配有malloc等。
+ --------- +
| bss |未初始化的全局变量;必须在读写存储区
+ --------- +
|数据|数据段,用于初始化的全局变量和静态变量
| | (可以进一步分成只读和读写区域,
| |只读区域存储在某些系统上的ROM的其他地方)
+ --------- +
|文本|程序代码,这是正在运行的实际可执行代码。
+ --------- +

这是一般的流程地址许多通用虚拟内存系统上的空间。 孔是您的总内存的大小,减去所有其他区域占用的空间;这给了大量的空间用于堆生长。这也是虚拟,意味着它通过翻译表映射到您的实际内存,并且可能实际上存储在实际内存中的任何位置。这样做是为了保护一个进程不访问另一个进程的内存,并使每个进程认为它正在一个完整的系统上运行。



请注意, ,在某些系统上,堆栈和堆可能会有不同的顺序(请参阅 Billy O'Neal的答案下面有关Win32的更多详细信息)。



其他系统可以非常不同。例如,DOS运行在实模式中,运行程序时其内存分配看起来截然不同:

  + ----------- +内存顶部
|扩展|在高内存区域以上,并且达到你的总内存;需要驱动程序到
| |能够访问它。
+ ----------- + 0x110000
|高|刚好超过1MB-> 1MB + 64KB,由286s及以上使用。
+ ----------- + 0x100000
|上|上部存储器区域(从640kb-> 1MB)已经映射视频设备的存储器
| | DOS瞬态区等,有些经常是免费的,可以用于驱动程序
+ ----------- + 0xA0000
| USER PROC |用户进程地址空间,从DOS结束到640KB
+ ----------- +
| command.com | DOS命令解释器
+ ----------- +
| DOS | DOS永久区,保持尽可能小,提供用于显示的例程,
|内核| *基本硬件访问等
+ ----------- + 0x600
| BIOS数据| BIOS数据区,包含简单的硬件描述等。
+ ----------- + 0x400
|中断|中断向量表,从0开始到1k,包含
|矢量|发生中断时调用的例程的地址。例如
|表|中断0x21检查0x21 * 4处的地址,并远远跳转到
| |位置服务中断。
+ ----------- + 0x0

该DOS允许直接访问操作系统内存,没有保护,这意味着用户空间程序通常可以直接访问或覆盖任何他们喜欢的东西。



地址空间,然而,程序往往看起来类似,只有它们被描述为代码段,数据段,堆,堆栈段等,它的映射有点不同。



在将程序和必要的共享库加载到内存中,并将程序的各部分分配到正确的区域后,操作系统



不同的系统(嵌入式,无论何种方式) )可以具有非常不同的架构,诸如无栈系统,哈佛架构系统(其中代码和数据保存在单独的物理存储器中),实际上将BSS保持在只读存储器中的系统(最初由编程器设置)等等。但是,


$ b

我也知道一个计算机程序使用两种内存:stack和heap,它们也是计算机主存储器的一部分。


Stack和heap只是抽象概念,而不是(必然)物理上不同的种类的内存。



堆栈仅仅是一个后进先出的数据结构。在x86架构中,它实际上可以通过使用从末端的偏移随机寻址,但最常见的功能是PUSH和POP分别添加和删除项目。它通常用于函数局部变量(所谓的自动存储),函数参数,返回地址等(更多如下)



A 只是一个内存块的昵称,可以根据需要分配,并随机寻址(意思是,您可以访问其中的任何位置直接)。它通常用于在运行时分配的数据结构(在C ++中,使用 new delete c $ c> malloc 和C中的朋友等)。



x86架构上的堆栈和堆都位于系统内存(RAM),并通过虚拟内存分配映射到进程地址空间,如上所述。



寄存器(仍然在x86上),物理上驻留在处理器内部(与RAM相反),并由处理器从TEXT区域加载(也可以从其他地方加载在存储器或其他位置,取决于实际执行的CPU指令)。它们本质上是非常小的,非常快的片上存储器位置,用于许多不同的目的。



寄存器布局高度依赖于架构,寄存器,指令集和存储器布局/设计,正是架构的含义),因此我不会扩展它,但建议您采取汇编语言课程以更好地了解它们。






您的问题:


point是用于执行指令的堆栈?


堆栈(在具有和使用它们的系统/语言中)是最常用的像这样:

  int mul(int x,int y){
return x * y; //存储MULtiplying两个变量
//从栈中返回值地址以前的
//分配的结果,然后发出一个RET,它重置栈帧
//基于arg列表,并返回到
// CALLer设置的地址。
}

int main(){
int x = 2,y = 3; //这些变量存储在栈上
mul(x,y); //这将y推入堆栈,然后x,然后一个返回地址,
//在堆栈上分配一个返回值的空间,
//然后发出一个汇编CALL指令。
}

编写一个这样的简单程序,然后编译为汇编c $ c> gcc -S foo.c 如果你有访问GCC),并看看。组装很容易遵循。你可以看到栈用于函数局部变量,并且用于调用函数,存储它们的参数和返回值。这也是为什么当你做类似的事情:

  f(g(h(i))); 

依次调用所有这些。它实际上构建了一堆函数调用和它们的参数,执行它们,然后当它退回(或向上)时弹出它们。但是,如上所述,堆栈(在x86上)实际驻留在进程内存空间(在虚拟内存中),因此可以直接操作;它不是执行过程中的一个单独步骤(或至少与进程正交)。



FYI,上面是 C调用约定,也由C ++使用。其他语言/系统可以以不同的顺序将参数推入堆栈,并且一些语言/平台甚至不使用堆栈,并以不同的方式进行。



还要注意,这些不是实际行的C代码执行。编译器将它们转换为可执行文件中的机器语言指令。 然后(通常)从TEXT区域复制到CPU流水线,然后进入CPU寄存器,并从那里执行。 [这是不正确的。请参阅下面的 Ben Voigt的更正。]


I know the general theory but I can't fit in the details.

I know that a program resides in the secondary memory of a computer. Once the program begins execution it is entirely copied to the RAM. Then the processor retrive a few instructions (it depends on the size of the bus) at a time, puts them in registers and executes them.

I also know that a computer program uses two kinds of memory: stack and heap, which are also part of the primary memory of the computer. The stack is used for non-dynamic memory, and the heap for dynamic memory (for example, everything related to the new operator in C++)

What I can't understand is how those two things connect. At what point is the stack used for the execution of the instructions? Instructions go from the RAM, to the stack, to the registers?

解决方案

It really depends on the system, but modern OSes with virtual memory tend to load their process images and allocate memory something like this:

+---------+
|  stack  |  function-local variables, return addresses, return values, etc.
|         |  often grows downward, commonly accessed via "push" and "pop" (but can be
|         |  accessed randomly, as well; disassemble a program to see)
+---------+
| shared  |  mapped shared libraries (C libraries, math libs, etc.)
|  libs   |
+---------+
|  hole   |  unused memory allocated between the heap and stack "chunks", spans the
|         |  difference between your max and min memory, minus the other totals
+---------+
|  heap   |  dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
|   bss   |  Uninitialized global variables; must be in read-write memory area
+---------+
|  data   |  data segment, for globals and static variables that are initialized
|         |  (can further be split up into read-only and read-write areas, with
|         |  read-only areas being stored elsewhere in ROM on some systems)
+---------+
|  text   |  program code, this is the actual executable code that is running.
+---------+

This is the general process address space on many common virtual-memory systems. The "hole" is the size of your total memory, minus the space taken up by all the other areas; this gives a large amount of space for the heap to grow into. This is also "virtual", meaning it maps to your actual memory through a translation table, and may be actually stored at any location in actual memory. It is done this way to protect one process from accessing another process's memory, and to make each process think it's running on a complete system.

Note that the positions of, e.g., the stack and heap may be in a different order on some systems (see Billy O'Neal's answer below for more details on Win32).

Other systems can be very different. DOS, for instance, ran in real mode, and its memory allocation when running programs looked much differently:

+-----------+ top of memory
| extended  | above the high memory area, and up to your total memory; needed drivers to
|           | be able to access it.
+-----------+ 0x110000
|  high     | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
|  upper    | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
|           | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+ 
|    DOS    | DOS permanent area, kept as small as possible, provided routines for display,
|  kernel   | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained 
|  vector   | the addresses of routines called when interrupts occurred.  e.g.
|  table    | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that 
|           | location to service the interrupt.
+-----------+ 0x0

You can see that DOS allowed direct access to the operating system memory, with no protection, which meant that user-space programs could generally directly access or overwrite anything they liked.

In the process address space, however, the programs tended to look similar, only they were described as code segment, data segment, heap, stack segment, etc., and it was mapped a little differently. But most of the general areas were still there.

Upon loading the program and necessary shared libs into memory, and distributing the parts of the program into the right areas, the OS begins executing your process wherever its main method is at, and your program takes over from there, making system calls as necessary when it needs them.

Different systems (embedded, whatever) may have very different architectures, such as stackless systems, Harvard architecture systems (with code and data being kept in separate physical memory), systems which actually keep the BSS in read-only memory (initially set by the programmer), etc. But this is the general gist.


You said:

I also know that a computer program uses two kinds of memory: stack and heap, which are also part of the primary memory of the computer.

"Stack" and "heap" are just abstract concepts, rather than (necessarily) physically distinct "kinds" of memory.

A stack is merely a last-in, first-out data structure. In the x86 architecture, it can actually be addressed randomly by using an offset from the end, but the most common functions are PUSH and POP to add and remove items from it, respectively. It is commonly used for function-local variables (so-called "automatic storage"), function arguments, return addresses, etc. (more below)

A "heap" is just a nickname for a chunk of memory that can be allocated on demand, and is addressed randomly (meaning, you can access any location in it directly). It is commonly used for data structures that you allocate at runtime (in C++, using new and delete, and malloc and friends in C, etc).

The stack and heap, on the x86 architecture, both physically reside in your system memory (RAM), and are mapped through virtual memory allocation into the process address space as described above.

The registers (still on x86), physically reside inside the processor (as opposed to RAM), and are loaded by the processor, from the TEXT area (and can also be loaded from elsewhere in memory or other places depending on the CPU instructions that are actually executed). They are essentially just very small, very fast on-chip memory locations that are used for a number of different purposes.

Register layout is highly dependent on the architecture (in fact, registers, the instruction set, and memory layout/design, are exactly what is meant by "architecture"), and so I won't expand upon it, but recommend you take an assembly language course to understand them better.


Your question:

At what point is the stack used for the execution of the instructions? Instructions go from the RAM, to the stack, to the registers?

The stack (in systems/languages that have and use them) is most often used like this:

int mul( int x, int y ) {
    return x * y;       // this stores the result of MULtiplying the two variables 
                        // from the stack into the return value address previously 
                        // allocated, then issues a RET, which resets the stack frame
                        // based on the arg list, and returns to the address set by
                        // the CALLer.
}

int main() {
    int x = 2, y = 3;   // these variables are stored on the stack
    mul( x, y );        // this pushes y onto the stack, then x, then a return address,
                        // allocates space on the stack for a return value, 
                        // then issues an assembly CALL instruction.
}

Write a simple program like this, and then compile it to assembly (gcc -S foo.c if you have access to GCC), and take a look. The assembly is pretty easy to follow. You can see that the stack is used for function local variables, and for calling functions, storing their arguments and return values. This is also why when you do something like:

f( g( h( i ) ) ); 

All of these get called in turn. It's literally building up a stack of function calls and their arguments, executing them, and then popping them off as it winds back down (or up ;). However, as mentioned above, the stack (on x86) actually resides in your process memory space (in virtual memory), and so it can be manipulated directly; it's not a separate step during execution (or at least is orthogonal to the process).

FYI, the above is the C calling convention, also used by C++. Other languages/systems may push arguments onto the stack in a different order, and some languages/platforms don't even use stacks, and go about it in different ways.

Also note, these aren't actual lines of C code executing. The compiler has converted them into machine language instructions in your executable. They are then (generally) copied from the TEXT area into the CPU pipeline, then into the CPU registers, and executed from there. [This was incorrect. See Ben Voigt's correction below.]

这篇关于当计算机程序运行时会发生什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆