cs

[크래프톤 정글] cpu의 명령어 사이클

하루이2222 2024. 10. 1. 19:16

참고 https://velog.io/@ckstn0777/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0-9htxi9jo

1. 프로그램 로드

프로그램 실행이 시작되면 운영 체제는 실행 파일을 메모리에 로드한다. 이때 프로그램의 텍스트(코드) 영역에 있는 명령어들이 메모리에 적재되고, CPU가 프로그램의 첫 번째 명령어를 실행하기 위해 준비한다.


2. 명령어 사이클 (Instruction Cycle)

명령어 사이클은 CPU가 메모리에서 명령어를 읽고, 해석하고, 실행하는 일련의 반복 과정이다. 각 사이클은 다음과 같은 세 단계로 구성된다: 명령어 인출(Fetch)명령어 해석(Decode)명령어 실행(Execute). 이 사이클이 프로그램의 모든 명령어가 실행될 때까지 반복된다.

명령어 사이클의 각 단계를 순서대로 설명하면 다음과 같다.


1. 명령어 인출 (Fetch)

명령어 인출 단계는 CPU가 메모리에서 다음에 실행할 명령어를 읽어오는 단계다.

세부 실행 과정:

  1. 프로그램 카운터(PC) 또는 RIP(x86-64 아키텍처의 Instruction Pointer)가 가리키는 주소는 메모리에서 다음에 실행할 명령어의 주소를 가지고 있다.
    • 예: RIPmain 함수의 첫 번째 명령어 주소를 가리킴.
  2. MAR(Memory Address Register)에는 PC 또는 RIP가 가리키는 주소가 저장된다.
    • MAR은 CPU가 메모리에서 데이터를 읽거나 쓸 때 사용하는 주소를 보관하는 레지스터다.
  3. 메모리에서 MAR에 저장된 주소로 이동하여 해당 명령어를 읽어오고, 이 명령어는 MBR(Memory Buffer Register)에 저장된다.
    • MBR은 메모리에서 읽어온 데이터를 임시로 저장하는 레지스터다.
  4. MBR에 저장된 명령어가 IR(Instruction Register)로 전달된다.
    • IR은 현재 실행할 명령어를 저장한다.
  5. 명령어가 IR에 저장되면, PC(또는 RIP)는 자동으로 다음 명령어 주소를 가리키도록 증가한다.
    • 이때 PC는 명령어의 길이만큼 증가한다. (예: 64비트 명령어의 경우 8바이트 만큼 증가)

요약:

  • CPU가 다음 실행할 명령어를 메모리에서 읽어와 IR에 저장.
  • PC/RIP는 다음 명령어를 가리키도록 업데이트됨.

2. 명령어 해석 (Decode)

명령어 해석 단계에서는 IR에 저장된 명령어를 CPU의 제어 유닛(CU)이 해석하여 CPU가 해야 할 작업을 결정한다. 이 단계에서는 명령어의 연산 코드(OpCode)를 분석하고, 명령어가 필요로 하는 피연산자(Operands)와 그 위치를 결정한다.

세부 실행 과정:

  1. IR(Instruction Register)에 저장된 명령어가 제어 유닛(CU)에 의해 해석된다.
    • 명령어는 OpCode와 피연산자로 구성된다.
    • 예: printf 호출 명령어라면 OpCode는 함수 호출을 나타내며, 피연산자는 호출할 함수의 주소 또는 인자를 나타낸다.
  2. 제어 유닛은 OpCode를 해석하여 CPU가 어떤 연산을 해야 하는지 결정한다.
    • 예: 산술 연산(덧셈, 곱셈 등), 메모리 접근, 함수 호출 등의 명령어가 있을 수 있다.
  3. 만약 메모리에서 데이터를 읽거나 쓰는 명령어라면, 피연산자로부터 데이터가 저장된 메모리 주소를 결정하고 MAR에 그 주소를 저장한다.
  4. 필요한 레지스터나 ALU(산술 논리 장치)를 설정하고, 연산 준비를 완료한다.

요약:

  • IR에 저장된 명령어가 해석되어 CPU가 수행할 작업을 결정.
  • 필요한 데이터와 연산 자원(ALU, 레지스터 등)이 준비됨.

3. 명령어 실행 (Execute)

명령어 실행 단계는 해석된 명령어에 따라 실제 연산이 수행되는 단계이다. 연산의 종류에 따라 다양한 작업이 수행될 수 있다: 메모리 접근, 산술 연산, 논리 연산, 함수 호출 등.

세부 실행 과정:

  1. ALU(Arithmetic Logic Unit)가 산술 연산이나 논리 연산을 처리한다.
    • 예: 두 수를 더하거나, 비교 연산을 수행하는 작업.
  2. 만약 메모리 접근 명령어일 경우, MAR에 저장된 주소를 사용해 메모리에서 데이터를 읽거나 쓴다.
    • 읽어온 데이터는 MBR(Memory Buffer Register)에 저장된다.
  3. 함수 호출 명령어의 경우, 스택을 사용하여 함수 호출 관련 데이터를 저장하고, 제어 흐름을 함수로 넘긴다.
    • 예: printf 호출 시, CPU는 스택에 인자를 전달하고, printf 함수의 시작 주소로 RIP를 업데이트한다.
  4. 실행 결과가 레지스터나 메모리에 저장된다.
    • 만약 연산 결과가 있다면, 그 값은 지정된 레지스터나 메모리 주소에 기록된다.

요약:

  • ALU 또는 메모리를 통해 실제 명령어가 실행됨.
  • 연산 결과가 레지스터나 메모리에 저장됨.

명령어 사이클의 반복

이 세 가지 단계인 명령어 인출 → 명령어 해석 → 명령어 실행이 반복적으로 수행된다. hello.c 같은 프로그램에서는 여러 명령어가 순차적으로 실행되며, CPU는 한 번에 한 명령어씩 처리한다.


hello.c의 구체적 명령어 실행 흐름 예시

hello.c의 경우, 다음과 같은 흐름으로 명령어가 실행된다:

  1. 프로그램 시작: CPU는 프로그램의 첫 번째 명령어를 인출하고 실행한다.
    • 이 명령어는 main 함수의 시작일 것이다.
  2. printf 함수 호출:
    • printf("Hello, World!\n")를 만나면 printf 함수가 호출된다.
    • CPU는 인자를 스택에 저장하고, printf 함수의 주소로 RIP를 업데이트하여 제어를 전달한다.
  3. printf 함수 실행:
    • CPU는 printf 함수의 명령어를 순차적으로 인출, 해석, 실행하여 문자열 "Hello, World!\n"을 출력 장치로 보낸다.
    • 이는 운영 체제의 시스템 호출을 통해 출력된다.
  4. return 0 실행:
    • main 함수가 종료되면 return 0 명령이 실행된다.
    • CPU는 종료 상태를 운영 체제에 전달하고, 프로그램 실행이 종료된다.

결론

  • 명령어 사이클은 CPU가 프로그램 명령어를 처리하는 기본 메커니즘이다.
  • 명령어 인출 → 명령어 해석 → 명령어 실행의 과정이 반복적으로 수행되면서 프로그램이 실행된다.
  • hello.c 같은 프로그램도 이 명령어 사이클을 통해 CPU가 명령어를 하나씩 처리하고, 실행하게 된다.

프로그램의 메모리 적재

프로그램이 실행될 때, 명령어가 메모리에 적재되는 과정은 운영 체제가 프로그램 실행 파일을 읽어 해당 명령어와 데이터를 메모리에 적재하고, CPU가 그 명령어들을 처리할 수 있도록 하는 일련의 과정이다. 이 과정은 프로그램의 실행에 필요한 정보가 메모리의 특정 영역에 저장되는 방식으로 이루어진다. 이를 더 자세하게 설명하면 다음과 같다.


1. 프로그램 실행 파일 준비

먼저, 프로그램이 실행되기 전에 컴파일된 실행 파일이 준비된다. 이 실행 파일에는 프로그램의 명령어(기계어)데이터가 포함된다.

실행 파일 구조:

  • 텍스트(코드) 섹션: 기계어로 번역된 명령어들이 저장된 섹션.
  • 데이터 섹션: 전역 변수나 초기화된 데이터를 저장하는 섹션.
  • BSS 섹션: 초기화되지 않은 전역 변수들을 저장하는 섹션.
  • 힙(Heap): 동적으로 할당되는 메모리 공간.
  • 스택(Stack): 함수 호출 시 사용되는 메모리 공간.

이 실행 파일은 디스크에 저장되어 있다가, 사용자가 프로그램을 실행할 때 운영 체제가 이를 메모리에 로드하게 된다.


2. 운영 체제에 의한 메모리 적재 (Memory Loading)

프로그램이 실행되면, 운영 체제가 이 실행 파일을 메모리로 로드하는 과정을 거친다. 이 과정을 프로세스 생성이라고 한다.

주요 단계:

  1. 프로세스 생성: 운영 체제는 프로그램을 실행하기 위해 새로운 프로세스(Process)를 생성한다. 프로세스는 실행 중인 프로그램을 의미하며, 이 프로세스를 위한 고유한 메모리 주소 공간이 할당된다.
  2. 실행 파일 로드:
    • 운영 체제는 프로그램의 실행 파일을 디스크에서 읽어서 메모리의 적절한 위치에 로드한다.
    • 이때, 프로그램의 명령어가 텍스트 섹션(코드 섹션)에 로드되고, 초기화된 전역 변수는 데이터 섹션에 로드된다.
    • 프로그램의 BSS 섹션은 초기화되지 않은 변수를 위한 공간을 메모리에 할당하며, 이 변수들은 실행 중에 값이 초기화된다.
  3. 메모리 매핑:
    • 실행 파일에 포함된 명령어는 일반적으로 가상 메모리 주소로 매핑된다. 즉, 물리 메모리 주소와는 별도로 운영 체제가 할당한 가상 주소 공간에서 명령어가 로드된다.
    • 운영 체제는 프로그램의 각 섹션(텍스트, 데이터, BSS, 스택, 힙 등)을 가상 메모리의 특정 위치에 배치한다.

3. 메모리에서의 명령어 적재 방식

메모리 레이아웃:

프로그램이 메모리에 적재되면, 그 메모리는 여러 영역으로 나뉜다. 일반적인 메모리 레이아웃은 다음과 같다:

+-----------------------------+
| 스택 (Stack)                 | ← 높은 주소
+-----------------------------+
| 힙 (Heap)                    |
+-----------------------------+
| BSS 섹션 (초기화되지 않은 변수) |
+-----------------------------+
| 데이터 섹션 (초기화된 변수)  |
+-----------------------------+
| 텍스트 섹션 (기계어 코드)    | ← 낮은 주소
+-----------------------------+
  1. 텍스트 섹션 (Text Section):
    • 프로그램의 기계어 명령어들이 이 영역에 저장된다.
    • CPU는 명령어를 실행할 때 이 섹션에 저장된 명령어들을 차례로 읽어온다.
    • 이 영역은 읽기 전용으로 설정되어, 명령어가 변경되지 않도록 보호된다.
  2. 데이터 섹션 (Data Section):
    • 프로그램의 초기화된 전역 변수들이 저장된다.
    • 이 섹션은 읽기 및 쓰기가 가능한 영역이다.
  3. BSS 섹션 (Block Started by Symbol):
    • 초기화되지 않은 전역 변수들이 저장되는 공간이다.
    • 이 섹션은 프로그램 실행 시 메모리에 할당되며, 값이 나중에 초기화된다.
  4. 힙 (Heap):
    • 프로그램이 실행 중에 동적으로 메모리를 할당하는 영역이다.
    • 예를 들어, malloc() 같은 함수 호출로 힙에서 메모리가 할당된다.
  5. 스택 (Stack):
    • 함수 호출 시 사용되는 메모리 영역이다.
    • 지역 변수나 함수 호출 시 전달되는 인자가 이 공간에 저장된다.

4. 명령어 실행 (Instruction Execution)

명령어 적재 이후의 과정:

프로그램이 메모리에 적재된 후, CPU는 메모리에서 명령어를 차례대로 읽어와 실행한다. 이 과정은 명령어 사이클에서 설명한 단계를 따른다:

  1. 프로그램 카운터(PC 또는 RIP)는 메모리에서 첫 번째 명령어의 주소를 가리키고, CPU는 해당 주소에서 명령어를 인출해 실행한다.
  2. 인출된 명령어는 CPU의 IR(Instruction Register)로 이동하고, CPU의 제어 유닛(CU)이 이를 해석한다.
  3. 명령어가 해석된 후, 해당 연산이 ALU(산술 논리 장치)에서 처리되거나, 메모리에서 추가 데이터를 읽어오는 등의 작업이 수행된다.
  4. 명령어가 실행된 후, PC(프로그램 카운터)는 다음 명령어의 주소를 가리키도록 증가하고, 다음 명령어가 인출된다.

이 과정을 반복하면서 프로그램이 차례대로 실행된다.


5. 페이징 및 스왑핑 (Paging and Swapping)

대부분의 운영 체제는 가상 메모리를 사용하여 프로그램이 실행되는 동안 필요한 메모리 공간을 효율적으로 관리한다. 특히 대형 프로그램이나 여러 프로그램이 동시에 실행될 때는 페이징(paging)스왑핑(swapping) 같은 기법을 통해 메모리를 관리한다.

페이징:

  • 프로그램의 실행 파일 전체가 메모리에 동시에 적재되지 않더라도, 필요한 부분(페이지)만 메모리에 로드하는 방식이다.
  • CPU는 가상 주소를 물리 주소로 변환해 필요한 페이지를 메모리로 로드하고, 필요하지 않은 페이지는 디스크로 스왑하여 메모리 공간을 확보한다.

스왑핑:

  • 사용되지 않는 프로세스나 페이지를 디스크로 내보내고, 활성화된 페이지는 메모리에 적재하는 방식이다.
  • 메모리 부족 상황에서 프로그램의 일부가 스왑 영역으로 이동될 수 있으며, 필요 시 다시 메모리로 불러온다.

전체 프로세스 요약:

  1. 프로그램 컴파일: hello.c가 컴파일되어 실행 파일(hello)이 생성된다.
  2. 운영 체제에 의해 메모리로 로드: 운영 체제가 실행 파일의 텍스트(코드), 데이터 섹션을 메모리에 적재하고, 프로세스를 생성한다.
  3. 프로그램 시작: CPU는 프로그램의 첫 번째 명령어를 텍스트 섹션에서 읽어와 실행하기 시작한다.
  4. 명령어 사이클 수행: CPU는 명령어를 순차적으로 인출, 해석, 실행하며 프로그램을 처리한다.
  5. 메모리 관리: 프로그램이 필요로 하는 메모리 공간은 가상 메모리를 통해 관리되며, 필요에 따라 페이징이나 스왑핑이 발생할 수 있다.

결국, 명령어는 메모리의 텍스트 섹션에 적재되어 CPU에 의해 처리된다. CPU는 이 명령어들을 순차적으로 읽어 실행하며, 프로그램은 계속해서 진행된다.

예제

add 10, 20와 같은 명령어가 메모리에서 인출되고 각 레지스터를 거쳐 연산이 완료되는 과정을 더 자세하게 설명해보겠다. 이 과정에서 CPU 내부의 여러 레지스터들이 어떻게 사용되는지 단계별로 설명한다.

1. 명령어 인출(Fetch)

먼저, CPU는 메모리에서 명령어를 가져온다. 이 과정에서 사용되는 주요 레지스터는 다음과 같다:

  • 프로그램 카운터(PC, Program Counter): 다음에 실행할 명령어의 메모리 주소를 가리킨다.
  • 메모리 주소 레지스터(MAR, Memory Address Register): CPU가 메모리에서 명령어를 인출할 때 사용되는 메모리 주소를 저장한다.
  • 메모리 버퍼 레지스터(MBR, Memory Buffer Register): 메모리로부터 읽어온 데이터(명령어)를 일시적으로 저장하는 레지스터다.
  • 명령어 레지스터(IR, Instruction Register): 인출된 명령어를 저장한다.

단계

  1. PC는 메모리에서 실행할 명령어의 주소를 가리키고 있다. 이 주소는 0x00400000이라고 가정하자.
  2. PC에 있는 값(0x00400000)이 MAR에 복사된다.
  3. MAR은 해당 주소로 메모리에서 명령어를 인출하도록 요청한다.
  4. 메모리에서 add 10, 20 명령어를 읽어서 MBR에 저장한다.
  5. MBR에 있는 값이 IR로 복사된다. 이제 add 10, 20 명령어가 IR에 저장되어 명령어가 준비되었다.

2. 명령어 해석(Decode)

명령어 레지스터에 저장된 add 10, 20 명령어를 제어 유닛(Control Unit)이 해석한다.

레지스터

  • 명령어 레지스터(IR): 인출된 명령어를 저장하고, 제어 유닛이 이를 해석한다.
  • 제어 레지스터(Control Register): 명령어의 타입(add)을 해석하고, 필요한 연산을 수행하도록 지시한다.

단계

  1. 제어 유닛IR에 있는 add 명령어를 해석한다.
  2. 명령어가 덧셈 연산(add)임을 파악하고, 피연산자로 1020이 사용될 것임을 인식한다.
  3. 이제 다음 단계인 피연산자 인출 단계로 넘어간다.

3. 피연산자 인출(Fetch Operands)

피연산자인 1020은 즉시값(Immediate Value)이므로, 메모리에서 데이터를 가져오는 대신 바로 레지스터에 저장되어 연산에 사용된다. 이때 ALU(산술 논리 연산 장치)가 연산을 수행할 준비를 한다.

레지스터

  • 즉시값 레지스터(Immediate Register): 명령어 내의 즉시값 1020이 저장된다.
  • ALU 입력 레지스터(ALU Input Registers): ALU로 데이터를 전달하는 레지스터다. 두 개의 입력값을 저장한다.

단계

  1. 명령어에서 해석된 즉시값 1020즉시값 레지스터에 저장된다.
  2. 즉시값 레지스터에 있는 값 1020이 각각 ALU 입력 레지스터로 복사된다.
  3. 이제 두 피연산자 값이 준비되었고, 연산을 위해 ALU로 전달된다.

4. 명령어 실행(Execute)

이제 ALU가 덧셈 연산을 수행한다. ALU는 두 입력 레지스터의 값을 더하고, 그 결과를 출력 레지스터에 저장한다.

레지스터

  • ALU(Arithmetic Logic Unit): 두 값을 더하는 연산을 수행한다.
  • ALU 출력 레지스터(ALU Output Register): 연산 결과가 저장된다.
  • 일반 레지스터(R1, General Purpose Register): 연산 결과가 저장될 수 있는 레지스터이다.

단계

  1. ALU가 입력 레지스터에서 값을 가져와 10 + 20을 계산한다.
  2. 덧셈 결과 30ALU 출력 레지스터에 저장된다.
  3. ALU 출력 레지스터에 있는 값 30R1 레지스터로 복사된다. 이제 30이라는 값이 레지스터에 저장되어, 이후 명령어에서 사용할 수 있다.

5. 결과 저장(Write Back)

연산이 완료된 후, 결과는 레지스터에 저장되거나 필요에 따라 메모리로 다시 저장된다. 이 과정에서는 다음 레지스터가 사용된다:

레지스터

  • 일반 레지스터(R1): 결과값이 저장된다.
  • 메모리 주소 레지스터(MAR): 결과를 메모리에 저장할 경우, 메모리 주소를 지정한다.
  • 메모리 버퍼 레지스터(MBR): 메모리에 기록할 데이터를 임시로 저장한다.

단계

  1. 덧셈 결과인 30R1 레지스터에 저장된다.
  2. 필요에 따라, 이 결과를 메모리로 다시 저장할 수도 있다. 이 경우:
    • R1에 있는 값이 MBR에 복사되고,
    • MAR에 저장할 메모리 주소를 지정한 후,
    • MBR에 있는 값이 지정된 메모리 주소로 기록된다.

6. 명령어 완료(Complete)

이제 add 10, 20 명령어가 완전히 실행되었으며, 연산 결과 30R1 레지스터에 저장되었다. CPU는 다음 명령어를 처리할 준비를 한다.


레지스터 흐름 요약:

  1. PC -> MAR: 메모리 주소 인출.
  2. MBR -> IR: 인출된 명령어 저장.
  3. IR -> 제어 유닛: 명령어 해석.
  4. 즉시값 레지스터 -> ALU 입력 레지스터: 피연산자 준비.
  5. ALU 입력 레지스터 -> ALU: 연산 수행.
  6. ALU 출력 레지스터 -> R1: 결과 저장.

이처럼 CPU 내부에서 다양한 레지스터들이 명령어 실행 과정에서 서로 데이터를 교환하며 연산을 수행한다.