내 마음대로 컴퓨터 구조 정리하기 (3)


주제 - “컴퓨터 구조(3)”

부제 - 프로세스, 메모리 영역

참고 서적





3. 프로세스의 메모리 공간 파헤치기


이번에는 프로그램이 메모리에 적재되어 프로세스의 상태로 실행될 때, 메모리 공간에서 발생하는 일들에 대해 정리해보겠습니다.



3-1. RAM에 할당된 프로세스의 공간


프로세스는 RAM에 일부 공간을 할당받아 생성됩니다. 그렇게 할당받는 공간은 크게 4가지 영역으로 구성됩니다.

  • 코드 (code) 영역 : 실행 파일을 구성하는 명령어들이 올라가는 영역
    • 코드가 들어가는 부분이므로 쓰기가 금지된 영역
    • 수정될 것이 없으므로 고정된 크기를 가짐
  • 데이터 (data) 영역 : 전역 변수들을 저장하는 영역
    • 사실 데이터 영역은 초기화 된 값이 저장되는 데이터 영역과
    • 초기화 되지 않은 값이 저장되는 bss 영역이 구분됨
    • 그렇게 해서 크게 5가지로 나누는 경우도 있음
  • 힙 (heap) 영역 : 동적 메모리(malloc 등) 할당에 사용되는 영역
    • 사용자가 직접 접근할 수 있는 영역
    • 크기가 고정되어 있지 않고, 필요에 따라 영역의 크기가 커지거나 작아짐
    • 메모리 할당 (allocate) 과 해제 (deallocate) 에 의해 관리됨
  • 스택 (stack) 영역 : 지역 변수 및 함수의 파라미터를 저장하는 영역
    • 함수 호출 과정에 따라 값이 들어갔다 나왔다 하므로 크기가 가변적
    • 다른 영역과 반대로 주소값이 높은 곳에서부터 값이 들어감
    • 값이 스택에 들어가는 것을 push, 나오는 것을 pop 이라고 함
    • 높은 주소에서부터 값이 쌓이면, 맨 위의 값부터 나올 수 있는 LIFO (Last In First Out) 의 형태


image



3-2. 프로그램이 실행될 때 메모리 공간의 변화


C언어로 쓰인 짧은 코드를 예시로, 프로그램이 실행될 때 메모리 영역에 어떤 일이 일어나는지 정리해보겠습니다.

먼저, 수행되어야 하는 동작이 담긴 명령(코드)들은 모두 코드 영역에 들어갑니다. 물론 아래의 이미지와 같은 C언어(고급 언어)의 형태가 아닌, 기계어의 형태일 것입니다. (본 정리에서 어셈블리어를 따로 다루지 않을 예정이기에 C언어로 표시해두었습니다.)

image


EIP 레지스터는 코드 영역에서 실행되어야 할 부분을 가리킬 것이고, EIP 레지스터가 가리키는 순서대로 프로그램이 실행될 것입니다. 위의 예시에서는 전역변수인 a가 데이터 영역에 들어갈 것입니다. 그리고 main 함수가 실행될 것입니다.

image

위의 이미지를 보면 main 함수가 실행되면서, old ebp가 스택에 쌓이는 것 (push)을 볼 수 있습니다. 이는 함수가 실행될 때마다 일어나는 일로, 이전에 실행중이던 상태로 돌아가기 위해 레지스터 값을 저장하는 것입니다. 이미지에는 ebp라고만 적혀있지만, 당시 사용중이던 레지스터가 존재하면 그 값들도 함께 저장됩니다. 함수의 실행이 종료되면, 저장해둔 값을 꺼내서 원래의 상태로 돌아가기 위함입니다.

위의 이미지처럼 하나의 함수가 실행되면서, 그에 관련된 값이 스택에 쌓이는 부분을 하나의 스택 프레임(stack frame) 이라고 합니다. 그래서 old ebp는 스택 프레임 포인터 (stack frame pointer, sfp)라고도 불립니다.


(아래부터는 스택 영역만을 확대한 이미지입니다.) 다음은 func 함수를 호출하는 부분입니다. 호출할 함수에 전달할 인자가 역순으로 스택에 쌓이며, func 함수의 실행이 완료됐을 때 돌아올 위치가 쌓입니다. 이 돌아올 위치를 return address(RET)라고 합니다.

image


함수 호출에 필요한 값이 다 쌓이고 나면, 함수가 실행됩니다. 아래의 이미지는 func 함수가 실행될 때 쌓이는 값을 보여줍니다. func 함수는 func 함수의 스택 프레임을 가지기에, 기존에 main 함수의 ebp가 저장되고 ebp가 가리키는 위치가 변경됩니다.

image


마지막으로 연산을 수행한 후 func 함수가 종료되면, main으로 돌아오기 위해 쌓아둔 값을 모두 빼내서 (pop) 레지스터 값을 복구시킵니다. 그 결과로 아래의 이미지와 같이 func 함수 호출 전과 같은 형태로 스택이 돌아온 것을 확인할 수 있습니다.

image



조금 더 상세한 정리는 타이틀과 어울리지 않는 것 같아 뺐더니, 정리가 전혀 되지 않은 느낌입니다. 일단 컴퓨터 구조 정리하기는 여기까지로 마무리합니다. 나중에 정리하고 싶은 것이 생긴다면 또 이어질지도 모르겠습니다😁





메모리에 대한 이야기


컴퓨터를 구성하는 메모리는 CPU 내부에 존재하는 레지스터, 주 메모리인 RAM, 비휘발성으로 저장해야하는 데이터를 담고 있는 디스크가 있습니다. 이들은 모두 서로 다른 처리 속도와 용량을 가지고 있습니다.

일반적으로, CPU에 가까이 위치하는 메모리일수록 속도가 빠르고 용량이 작으며 가격이 비싸집니다. 그렇기에 RAM의 처리 속도가 CPU에 미치지 못해 명령 수행에 지연이 발생합니다. 그래서 그 둘간의 속도 차이 완화를 위한 캐시가 CPU에 부착되고, 아래의 이미지와 같은 메모리 계층 구조가 구성되게 됩니다.

image


이전 글에서 주로 다루었던 명령의 실행과정을 보면, 레지스터와 RAM 사이에는 수많은 데이터의 이동이 발생합니다. 이런 데이터의 이동은 CPU가 명령의 처리를 위해 필요한 데이터를 요청하는 경우에 발생하곤 합니다. 이런 데이터 처리를 다룰 때 사용하는 용어가 있는데, 적중(hit)실패(miss)입니다.

  • 적중 (hit) : 프로세서가 요구한 데이터가 상위 계층(CPU에 가까운 메모리 계층)에 존재할 때
  • 실패 (miss) : 프로세서가 요구한 데이터를 상위 계층에서 찾을 수 없을 때

CPU가 요청한 데이터가 레지스터라는 최상위 계층에 존재한다면, 데이터를 요청해서 받아오는 과정은 빠르게 처리될 것입니다. 하지만 존재하지 않는다면, 하위 계층의 메모리로 접근하여 필요한 데이터를 찾아나서야 합니다.

  • 적중률 (hit rate / hit ratio) : 메모리 계층의 성능을 평가하는 척도, 데이터를 찾기 위해 특정 메모리에 접근 했을 때 해당 계층에서 찾을 수 있는 것의 비율
  • 실패율 (miss rate, 1 - 적중률) : 데이터를 찾기 위해 특정 메모리에 접근 했을 때 해당 계층에서 찾을 수 없는 것의 비율
  • 적중 시간 (hit time) : 특정 메모리 계층에 접근하여 적중 / 실패 여부를 결정하는데까지 필요한 시간
  • 실패 손실 (miss penalty) : 하위 계층에서 필요한 데이터를 가져와서, 상위 계층에 전달되는 시간 + 데이터가 CPU에 전달되기까지의 시간

위 용어의 정의를 보면 알 수 있듯이, CPU에 가까운 메모리일수록 적중 시간과 실패 손실이 적습니다.


빠른 시간 내에 명령을 처리하기 위해서는, CPU가 필요로 하는 데이터가 빠르게 전달되는 것이 좋습니다. 즉, 필요로 할 것 같은 데이터들이 상위 계층에 있을 수록 빠른 처리가 이루어질 것입니다. 그렇다면 어떻게 필요로 할 것 같은 데이터들을 뽑아낼 수 있을까요?

이는 지역성의 원리와 밀접한 관련이 있습니다.

  • 지역성의 원리 (principle of locality) : 프로그램은 비교적 최근에 접근한, 비교적 이전과 가까운 주소 공간의 메모리에 접근할 가능성이 높음
  • 시간적 지역성 (temporal locality) : 어떤 항목이 참조되면, 그 데이터는 빠른 시간 내에 다시 참조될 가능성이 높음
  • 공간적 지역성 (spatial locality) : 어떤 항목이 참조되면, 그 데이터의 근처에 위치한 데이터들이 참조될 가능성이 높음

많은 프로그램은 반복문 구조를 가지고 있고, 이는 명령과 데이터에 반복적으로 접근하기에 상당한 시간적 지역성을 보입니다. 또한, 명령들은 순차적으로 접근되는 경우가 많기에 공간적 지역성을 보이기도 합니다. 그리고 메모리 계층구조는 이러한 특성들을 활용하여 필요한 데이터들을 상위 계층에 적재합니다.





이전 - 내 마음대로 컴퓨터 구조 정리하기 (2)

comments powered by Disqus