컴퓨터 프로그램이 실행되면 어떻게됩니까?


180

나는 일반적인 이론을 알고 있지만 세부 사항에 적합하지 않습니다.

프로그램이 컴퓨터의 보조 메모리에 있다는 것을 알고 있습니다. 프로그램이 실행을 시작하면 완전히 RAM에 복사됩니다. 그런 다음 프로세서는 한 번에 몇 가지 명령 (버스 크기에 따라 다름)을 검색하여 레지스터에 넣고 실행합니다.

또한 컴퓨터 프로그램은 두 가지 종류의 메모리를 사용한다는 것을 알고 있습니다. 스택과 힙은 컴퓨터의 기본 메모리의 일부이기도합니다. 스택은 비 동적 메모리에 사용되며 동적 메모리에 대한 힙 (예 : newC ++ 의 연산자 와 관련된 모든 것 )

내가 이해할 수없는 것은이 두 가지가 어떻게 연결되는지입니다. 명령어 실행에 스택이 사용되는 시점은 언제입니까? RAM에서 스택, 레지스터로 명령이 전달됩니까?


43
근본적인 질문을하는 +1!
mkelley33

21
흠 ... 알다시피, 그들은 그것에 관한 책을 씁니다. SO의 도움으로 OS 아키텍처 의이 부분을 정말로 공부하고 싶습니까?
Andrey

1
질문의 메모리 관련 특성과 C ++에 대한 참조를 기반으로 몇 가지 태그를 추가했지만 Java 또는 C #에 대해 잘 아는 사람이 올 수도 있다고 생각합니다!)
mkelley33

14
공감하고 좋아했습니다. 난 항상 물어 너무 두려워했습니다 ...
Maxpm

2
"레지스터에 넣는"이라는 용어는 옳지 않습니다. 대부분의 프로세서에서 레지스터는 실행 코드가 아닌 중간 값을 보유하는 데 사용됩니다.

답변:


161

실제로 시스템에 따라 다르지만 가상 메모리가있는 최신 OS 는 프로세스 이미지를로드하고 다음과 같은 메모리를 할당하는 경향이 있습니다.

+---------+
|  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.
+---------+

이것은 많은 일반적인 가상 메모리 시스템의 일반적인 프로세스 주소 공간입니다. "구멍"은 전체 메모리의 크기에서 다른 모든 영역이 차지하는 공간을 뺀 것입니다. 이것은 힙이 커질 수있는 많은 공간을 제공합니다. 이것은 "가상"이기도합니다. 즉 , 변환 테이블을 통해 실제 메모리에 매핑되며 실제로 실제 메모리의 어느 위치 에나 저장 될 수 있습니다. 이는 한 프로세스가 다른 프로세스의 메모리에 액세스하지 못하도록 보호하고 각 프로세스가 완전한 시스템에서 실행되고 있다고 생각하게하기 위해 수행됩니다.

예를 들어, 스택 및 힙의 위치는 일부 시스템에서 다른 순서 일 수 있습니다 ( Win32에 대한 자세한 내용은 아래 Billy O'Neal의 답변 참조).

다른 시스템은 매우 다를 수 있습니다 . 예를 들어 DOS는 실제 모드 에서 실행되었으며 프로그램을 실행할 때의 메모리 할당은 크게 다르게 보입니다.

+-----------+ 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

DOS에서 보호없이 운영 체제 메모리에 직접 액세스 할 수 있다는 것을 알 수 있습니다. 이는 사용자 공간 프로그램이 일반적으로 원하는 것을 직접 액세스하거나 덮어 쓸 수 있음을 의미합니다.

그러나 프로세스 주소 공간에서 프로그램은 비슷해 보였으며 코드 세그먼트, 데이터 세그먼트, 힙, 스택 세그먼트 등으로 설명되었으며 약간 다르게 매핑되었습니다. 그러나 대부분의 일반 지역은 여전히있었습니다.

프로그램과 필요한 공유 라이브러리를 메모리에로드하고 프로그램의 일부를 올바른 영역에 배포하면 OS는 기본 방법이있는 곳마다 프로세스를 실행하기 시작하고 프로그램이 거기에서 인계하여 필요한 경우 시스템 호출을 수행합니다 그것들이 필요합니다.

임베디드 시스템은 스택리스 시스템, 하버드 아키텍처 시스템 (코드 및 데이터가 별도의 물리적 메모리에 보관 됨), 실제로 BSS를 읽기 전용 메모리에 유지하는 시스템 (처음에는 프로그래머) 등입니다. 그러나 이것은 일반적인 요지입니다.


당신은 말했다 :

또한 컴퓨터 프로그램은 두 가지 종류의 메모리를 사용한다는 것을 알고 있습니다. 스택과 힙은 컴퓨터의 기본 메모리의 일부이기도합니다.

"스택"과 "힙"은 물리적으로 별개의 "종류"의 메모리가 아니라 추상적 인 개념 일뿐입니다.

스택은 단지 후입 선출 데이터 구조이다. x86 아키텍처에서는 실제로 끝에서 오프셋을 사용하여 임의로 주소를 지정할 수 있지만 가장 일반적인 기능은 각각 PUSH 및 POP로 항목을 추가 및 제거하는 것입니다. 함수 로컬 변수 (소위 "자동 스토리지"), 함수 인수, 리턴 주소 등에 일반적으로 사용됩니다 (자세한 내용은 아래 참조).

"힙" 단지 수요에 할당 할 수있는 메모리 청크의 별명이며, (당신이 직접에서 어떤 위치에 액세스 할 수 있습니다 의미) 무작위로 해결된다. 그것은 일반적으로 런타임에 할당하는 것이 데이터 구조에 사용 (C ++에서 사용 new하고 delete, 그리고 malloc와 C의 친구 등).

x86 아키텍처의 스택 및 힙은 물리적으로 시스템 메모리 (RAM)에 상주하며 위에서 설명한대로 가상 메모리 할당을 통해 프로세스 주소 공간으로 매핑됩니다.

레지스터 (계속 86의) CPU의 지시에 따라, 물리적으로 프로세서 (RAM 반대) 안에 상주하고, 텍스트 영역에서, 프로세서에 의해로드 된 (그리고 메모리 또는 다른 장소에서 다른 장소로부터로드 될 수있는 실제로 실행됩니다). 그것들은 본질적으로 매우 작고 매우 빠른 온칩 메모리 위치이며 여러 다른 목적으로 사용됩니다.

레지스터 레이아웃은 아키텍처 (실제로 레지스터, 명령어 세트 및 메모리 레이아웃 / 디자인, 정확히 "아키텍처"가 의미하는 것)에 크게 의존하므로 확장하지는 않지만 권장합니다. 더 잘 이해하기 위해 어셈블리 언어 코스.


당신의 질문 :

명령어 실행에 스택이 사용되는 시점은 언제입니까? RAM에서 스택, 레지스터로 명령이 전달됩니까?

스택 (가지고 있고 사용하는 시스템 / 언어)은 다음과 같이 가장 자주 사용됩니다.

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.
}

이와 같은 간단한 프로그램을 작성한 다음 어셈블리로 컴파일하고 ( gcc -S foo.cGCC에 액세스 할 수있는 경우) 살펴보십시오. 어셈블리는 따라 가기가 매우 쉽습니다. 스택은 함수 로컬 변수와 함수 호출, 인수 및 반환 값 저장에 사용됩니다. 이것이 또한 다음과 같은 일을 할 때의 이유입니다.

f( g( h( i ) ) ); 

이 모든 것이 차례로 호출됩니다. 말 그대로 함수 호출 및 인수 스택을 작성하고 실행 한 다음 뒤로 (또는 위로) 바람에 터집니다. 그러나 위에서 언급 한 바와 같이 스택 (x86)은 실제로 프로세스 메모리 공간 (가상 메모리)에 상주하므로 직접 조작 할 수 있습니다. 실행하는 동안 별도의 단계가 아니거나 프로세스와 직교합니다.

참고로 위의 C 호출 규칙 은 C ++에서도 사용됩니다. 다른 언어 / 시스템은 인수를 다른 순서로 스택에 푸시 할 수 있으며 일부 언어 / 플랫폼은 스택을 사용하지 않고 다른 방식으로 진행합니다.

또한 이들은 실제 C 코드 실행 행이 아닙니다. 컴파일러가이를 실행 파일에서 기계 언어 명령어로 변환했습니다. 그런 다음 TEXT 영역에서 CPU 파이프 라인으로, 그 다음 CPU 레지스터로 (일반적으로) 복사되어 거기서 실행됩니다. [이것은 틀렸다. 아래의 Ben Voigt의 수정 사항을 참조하십시오.]


4
죄송하지만 책을 추천하는 것이 더 좋습니다. IMO
Andrey

13
예, "RTFM"이 항상 좋습니다.
Sdaz MacSkibbons

56
@Andrey : 어쩌면 당신은 그 의견을 "또한 당신의 좋은 책 추천 을 읽고 싶을 것"으로 바꿔야 할 것입니다. 나는 이런 종류의 질문은 더 많은 조사가 필요하다는 것을 이해하지만, "미안하지만. .. "아마도 중재자의 관심을 끌기 위해 게시물에 플래그를 지정하거나 적어도 귀하의 의견이 다른 사람에게 중요한 이유에 대한 설명을 제공하는 것을 고려해야 합니다.
mkelley33

2
훌륭한 답변입니다. 그것은 확실히 나를 위해 몇 가지를 정리했습니다!
Maxpm

2
@Mikael : 구현에 따라 필수 캐싱이있을 수 있습니다.이 경우 메모리에서 데이터를 읽을 때마다 전체 캐시 라인을 읽고 캐시가 채워집니다. 또는 캐시 관리자에게 데이터가 한 번만 필요하다는 힌트를 제공 할 수 있으므로 캐시에 복사하는 것이 도움이되지 않습니다. 읽어 볼게요 쓰기에는 DMA 컨트롤러가 데이터를 읽을 수있는 시점에 영향을주는 쓰기 및 쓰기 캐시가 있으며, 각각 자체 캐시가있는 여러 프로세서를 처리하기위한 캐시 일관성 프로토콜 전체 호스트가 있습니다. 이것은 실제로 자체 Q를받을 자격이 있습니다.
Ben Voigt

61

Sdaz는 매우 짧은 시간에 현저한 업 보트 수를 얻었지만 슬프게도 명령이 CPU를 통해 어떻게 움직이는 지에 대한 오해를 계속하고 있습니다.

질문은 :

RAM에서 스택, 레지스터로 명령이 전달됩니까?

Sdaz는 말했다 :

또한 이들은 실제 C 코드 실행 행이 아닙니다. 컴파일러가이를 실행 파일에서 기계 언어 명령어로 변환했습니다. 그런 다음 (일반적으로) TEXT 영역에서 CPU 파이프 라인으로 복사 한 다음 CPU 레지스터로 복사하여 거기서 실행됩니다.

그러나 이것은 잘못입니다. 자체 수정 코드의 특수한 경우를 제외하고 명령은 절대 데이터 경로를 입력하지 않습니다. 그리고 데이터 경로에서 실행할 수 없습니다.

의 x86 CPU 레지스터가 있습니다 :

  • 일반 레지스터 EAX EBX ECX EDX

  • 세그먼트 레지스터 CS DS ES FS GS SS

  • 색인 및 포인터 ESI EDI EBP EIP ESP

  • 지표 EFLAGS

부동 소수점 및 SIMD 레지스터도 있지만이 설명에서는 CPU가 아닌 보조 프로세서의 일부로 분류합니다. CPU 내부의 메모리 관리 장치에는 자체 레지스터가 있으므로 별도의 처리 장치로 다시 처리합니다.

이 레지스터 중 어느 것도 실행 코드에 사용되지 않습니다. EIP명령어 자체가 아니라 실행 명령어의 주소를 포함합니다.

명령어는 CPU에서 데이터와 완전히 다른 경로를 거칩니다 (Harvard 아키텍처). 모든 현재 머신은 CPU 내부의 하버드 아키텍처입니다. 요즘 대부분은 캐시의 하버드 아키텍처이기도합니다. x86 (공통 데스크탑 시스템)은 주 메모리의 Von Neumann 아키텍처로, 데이터와 코드가 RAM에 섞여 있습니다. 우리는 CPU 내부에서 일어나는 일에 대해 이야기하고 있기 때문에 요점 옆에 있습니다.

컴퓨터 아키텍처에서 가르치는 고전적인 순서는 fetch-decode-execute입니다. 메모리 컨트롤러는 주소에 저장된 명령어를 찾습니다 EIP. 인스트럭션의 비트는 프로세서의 서로 다른 멀티플렉서에 대한 모든 제어 신호를 생성하기 위해 조합 로직을 거칩니다. 그리고 몇 번의 사이클 후, 산술 논리 유닛은 결과에 도달하며, 이는 목적지로 클럭킹됩니다. 그런 다음 다음 명령어를 가져옵니다.

최신 프로세서에서는 상황이 약간 다르게 작동합니다. 각 수신 명령어는 전체 일련의 마이크로 코드 명령어로 변환됩니다. 이는 첫 번째 마이크로 명령에 의해 사용되는 리소스가 나중에 필요하지 않기 때문에 파이프 라이닝을 가능하게하여 다음 명령에서 첫 번째 마이크로 명령에 대한 작업을 시작할 수 있습니다.

또한 레지스터 는 D- 플립 플롭 모음에 대한 전기 엔지니어링 용어 이므로 용어가 약간 혼동 됩니다. 그리고 명령어 (또는 특히 미세 명령어)는 그러한 D- 플립 플롭 모음에 일시적으로 잘 저장 될 수 있습니다. 그러나 이것은 컴퓨터 과학 자나 소프트웨어 엔지니어 또는 상용 개발자가 register 라는 용어를 사용할 때의 의미가 아닙니다 . 이는 위에 나열된 데이터 경로 레지스터를 의미하며 코드 전송에는 사용되지 않습니다.

데이터 경로 레지스터의 이름과 수는 ARM, MIPS, Alpha, PowerPC와 같은 다른 CPU 아키텍처에 따라 다르지만 모든 ALU를 통하지 않고 명령어를 실행합니다.


설명해 주셔서 감사합니다. 나는 친숙하게 익숙하지 않기 때문에 다른 사람의 요청에 따라 추가하기를 주저했습니다.
Sdaz MacSkibbons

"측정 데이터 및 코드가 ARM에 통합되어 있음"의 s / ARM / RAM / 권리?
Bjarke Freund-Hansen

@bjarkef : 처음은 그렇지만 두 번째는 아닙니다. 내가 고칠 게
Ben Voigt

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에 의해 잘 설명되어 있으므로 여기서는 반복하지 않겠습니다. 그러나 이것이 유일한 메모리 모델은 아닙니다. POSIX가이 모델을 요구하는 이유는 sbrk입니다 시스템 호출입니다. 기본적으로 POSIX 박스에서 더 많은 메모리를 얻기 위해 프로세스는 단지 커널에게 "구멍"과 "힙"사이의 디바이더를 "구멍"영역으로 더 이동 시키라고 지시합니다. 운영 체제로 메모리를 리턴 할 방법이 없으며 운영 체제 자체가 힙을 관리하지 않습니다. C 런타임 라이브러리는 malloc을 통해이를 제공해야합니다.

이것은 POSIX 바이너리에서 실제로 사용되는 코드 종류에도 영향을 미칩니다. POSIX 상자는 (거의 보편적으로) ELF 파일 형식을 사용합니다. 이 형식에서 운영 체제는 다른 ELF 파일의 라이브러리 간 통신을 담당합니다. 따라서 모든 라이브러리는 위치 독립적 코드를 사용합니다 (즉, 코드 자체는 다른 메모리 주소로로드되어 여전히 작동 할 수 있음). 라이브러리 함수 호출. 이것은 약간의 오버 헤드를 추가하고 라이브러리 중 하나가 찾아보기 테이블을 변경하면 악용 될 수 있습니다.

Windows의 메모리 모델은 사용하는 코드 종류가 다르기 때문에 다릅니다. Windows는 PE 파일 형식을 사용하여 코드를 위치 종속 형식으로 둡니다. 즉, 코드는 가상 메모리에서 코드가 정확히로드되는 위치에 따라 다릅니다. PE 사양에는 프로그램이 실행될 때 라이브러리 또는 실행 파일이 메모리의 정확히 어디에 메모리에 있는지 OS에 알려주는 플래그가 있습니다. 프로그램이나 라이브러리를 원하는 주소로로드 할 수없는 경우 Windows 로더는 리베이스라이브러리 / 실행 파일-기본적으로 위치 종속 코드를 새 위치를 가리 키도록 이동합니다. 조회 테이블이 필요하지 않으며 덮어 쓸 조회 테이블이 없기 때문에 이용할 수 없습니다. 불행히도, 이것은 Windows 로더에서 매우 복잡한 구현을 필요로하며, 이미지를 리베이스해야 할 경우 시작 시간이 많이 소요됩니다. 대규모 상용 소프트웨어 패키지는 종종 다른 주소에서 시작하여 리베이스를 피하기 위해 라이브러리를 수정합니다. Windows 자체는 자체 라이브러리로이 작업을 수행합니다 (예 : ntdll.dll, kernel32.dll, psapi.dll 등-모두 기본적으로 시작 주소가 다름)

Windows에서 가상 메모리는 VirtualAlloc 호출을 통해 시스템에서 가져 오고 VirtualFree 를 통해 시스템으로 반환됩니다 (기술적으로 VirtualAlloc은 NtAllocateVirtualMemory로 팜 아웃되지만 구현 세부 사항입니다) (메모리는 POSIX와 대조됩니다) 되찾아주십시오). 이 프로세스는 느립니다 (IIRC에서는 실제 페이지 크기의 청크 (일반적으로 4kb 이상)로 할당해야합니다). Windows는 또한 RtlHeap이라는 라이브러리의 일부로 자체 힙 함수 (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 ++의 보안 코딩"에서 제공됨)


좋은 정보, 감사합니다! "user487117"이 실제로 다시 돌아 오기를 바랍니다. :-)
Sdaz MacSkibbons

5

스택

X86 아키텍처에서 CPU는 레지스터로 작업을 실행합니다. 스택은 편의상 사용됩니다. 서브 루틴 또는 시스템 기능을 호출하기 전에 레지스터의 내용을 스택에 저장 한 다음 다시로드하여 남은 위치에서 작업을 계속할 수 있습니다. (스택없이 수동으로 할 수 있지만 자주 사용되는 기능이므로 CPU를 지원합니다). 그러나 PC에 스택이 없으면 거의 모든 것을 할 수 있습니다.

예를 들어 정수 곱셈 :

MUL BX

AX 레지스터에 BX 레지스터를 곱합니다. (결과는 DX 및 AX, DX는 더 높은 비트를 포함합니다).

JAVA VM과 같은 스택 기반 시스템은 기본 작업에 스택을 사용합니다. 위의 곱셈 :

DMUL

스택의 상단에서 두 개의 값을 팝하고 온도를 곱한 다음 결과를 스택으로 다시 푸시합니다. 이런 종류의 기계에는 스택이 필수적입니다.

C 및 Pascal과 같은 일부 고급 프로그래밍 언어는이 후자의 방법을 사용하여 매개 변수를 함수에 전달합니다. (이것은 컴파일러 제조업체가 X86이 스택을 사용하는 방식을 만들고 남용하는 선택입니다).

힙은 컴파일러 영역에만 존재하는 다른 개념입니다. 변수 뒤의 메모리를 처리하는 데 어려움을 겪지 만 CPU 또는 OS의 기능은 아니며 OS에서 제공하는 메모리 블록을 유지 관리하는 것만 선택할 수 있습니다. 원하는 경우이 작업을 여러 번 수행 할 수 있습니다.

시스템 리소스에 액세스

운영 체제에는 해당 기능에 액세스하는 방법에 대한 공용 인터페이스가 있습니다. DOS에서 매개 변수는 CPU의 레지스터로 전달됩니다. Windows는 OS 함수 (Windows API)의 매개 변수를 전달하기 위해 스택을 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.