정글 TIL 10(01.21) "컴파일링과 컴퓨터 구조의 이해"

김동준·2024년 1월 20일

진심

목록 보기
10/15
post-thumbnail

해당 내용은 CS:APP의 1.4.2부터 1.7.4까지의 내용입니다. 개념이나 용어에 대해 정리한 것이므로 글을 이해하셨다면, 꼭 CS:APP로 복습하시길 권합니다.
컴파일 과정(~1.4.2)을 아직 안읽으셨다면? << 이동하기 >>

오늘은 .exe라는 확장자로 저장된 실행파일이 실행될 때 무슨 일이 일어나는지를 설명하기 위해 하드웨어 조직을 이해해보겠습니다.

먼저 복습해보세요!

Translating and Starting a Program(four steps)

C program - preprocess - Compile - (Assembly language pragram) - (Assemble) - (Object: Machine language moduel(Library routine(Machine language)) - Linker - Executable(exe) : Machine language program - Loader - Memory

  • 오늘 내용을 잘 이해해두시면 앞으로 노트북이나 컴퓨터를 살 때, 혹은 AWS에서 서버를 구매할 때 도움이 됩니다!

시스템 하드웨어 구성

먼저 용어와 각 부품들이 어떤 역할을 맡고 있는지 알아보겠습니다. 그 뒤 파일을 실행하기 위해 컴퓨터 내부에서는 어떤 일이 일어나고 있는지를 알려드릴게요!

  • 버스 : 시스템 내를 관통하는 전기적 배선군을 버스(bus)라고 하며, 컴포넌트들 간에 바이트 정보들을 전송합니다. 버스는 일반적으로 워드(word)라고 하는 고정 크기의 바이트 단위로 데이터를 전송하도록 설계됩니다.
    한 개의 워드를 구성하는 바이트 수는 시스템마다 보유하는 기본 시스템 변수이며, 대부분의 컴퓨터들은 4바이트(32bit) 또는 8바이트(64bit)의 워드 크기를 갖습니다.

데이터들을 옮기는 컨베이어 벨트를 떠올리시면 됩니다.

  • 입출력 장치는 시스템과 외부세계와의 연결을 담당하는 장치입니다. 키보드와 마우스, 디스플레이(모니터), 데이터와 프로그램의 장기 저장을 위한 디스크 드라이브를 떠올리시면 됩니다. 각 입출력 장치는 입출력 버스와 컨트롤러나 어댑터를 통해 연결됩니다. 이들을 통해 입출력 버스와 입출력 장치들 간에 정보를 주고받을 수 있습니다.

  • 컨트롤러는 디바이스 자체가 칩셋이거나 마더보드(시스템의 인쇄기판)에 장착되어 있습니다.

  • 어댑터는 마더보드의 슬롯에 장착되는 카드입니다. 저희 USB를 꽂는 잭을 보시면 그게 그거였던 것입니다.

  • 메인 메모리는 프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을 모두 저장하는 임시 장치입니다. 물리적으로는 DRAM(Dynamic Random Access Memory) 칩들로 구성되어 있습니다. 논리적으로 메모리는 연속적인 바이트들의 배열로, 각각 0부터 시작해서 고유 주소를 가지고 있습니다. RAM은 어느 위치에 저장된 데이터든지 접근(읽기 및 쓰기)하는 데 동일한 시간이 걸리는 메모리이기에 ‘랜덤(random, 무작위)’이라는 명칭이 주어졌습니다.

  • 프로세서주처리장치(CPU, Central Processing Unit) 혹은 마이크로프로세서(MPU, MicroProcessing Unit)이라고도 불립니다. 프로세서의 중심에는 워드 크기의 저장장치(또는 레지스터)인 프로그램 카운터(PC)가 있습니다. 프로세서는 인스트럭션 집합 구조(ISA, Instruction Set Architecture)로 정의되는 인스트럭션 실행 모델에 따라 작동합니다.

* 명령어 집합 구조(ISA)란 마이크로프로세서가 인식해서 기능을 이해하고 실행할 수 있는 기계어 명령어를 말합니다. MPU를 제조한 회사마다 상이하지만, 같은 이론에 근본을 두어 흔한 명령어와 기본적인 명령어는 같습니다. MPU 마다 기계어 코드의 길이와 숫자 코드가 다릅니다. 이러한 기계어와 일대일로 문자화한 것이 어셈블리어입니다.

여기서 상식. 컴퓨터를 사실 때 intel사의 x86 i5이니, i7이니, AMD사의 Ryzen7 정도는 들어보셨을겁니다. 같은 명령어 집합 구조를 회사 마다 각각 다른 마이크로아키텍쳐로 구현한 차이입니다. 여기서 RISC 방식과 CISC 방식이 나오지만, PASS! 보통 데스크탑에서는 intel사와 AMD 사가, 스마트폰에서는 ARM(현재 NVIDIA에 인수됨)사의 cortex가 많이 쓰입니다.

  • 프로그램 카운터(PC)는 CPU의 레지스터 중의 하나로서 다음에 실행될 명령어의 주소를 가지고 있어 실행될 기계어 코드의 위치를 지정합니다. 명령 포인터라고도 부릅니다.

  • 레지스터(Register)는 중앙처리 장치 내부에 있는 기억 장치입니다. 시스템 하드웨어 구성 그림에서 보시다시피 자주 쓰이거나 중요도가 높은 명령집합은 CPU 내에 레지스터에 저장되어 성능을 높일 수 있습니다. 레지스터의 최대 처리 용량은 CPU의 처리 용량과도 같습니다. 32bit 컴퓨터는 2^32(약 400만)까지, 64bit 컴퓨터는 2^64(약 1800경)까지 인식 가능합니다.

"레지스터는 산술적/논리적 연산이나 정보 해석, 전송 등을 할 수 있는 일정 길이의 정보를 저장하는 중앙 처리 장치(CPU) 내의 기억 장치이다.
저장 용량에는 제한되어 있으나 주기억 장치에 비해서 접근 시간이 빠르고, 체계적인 특징이 있다."
- TTA

"경고: 레지스트리 편집기를 사용할 때 매우 주의하세요. 레지스트리를 잘못 편집하면 운영 체제를 완전하게 다시 설치해야 하는 심각한 문제가 발생할 수 있으며 데이터 손실이 발생할 수 있습니다. 비공식 원본에서 제안한 편집은 피합니다." - Microsoft Windows 10

  • 산술 논리 연산 장치(ALU, Arithmetic And Logic Unit)은 산술 연산과 논리 연산을 수행하는 회로의 집합입니다. 어려우면 PASS!

프로그램의 실행 과정

hello 프로그램의 실행

여기까지 시스템 하드웨어를 구성하고 있는 부품이 어떤 부품들인지에 대해 알아봤습니다. 이제 이 부품들이 프로그램을 실행하기 위해 어떻게 상호작용하는지 알아볼까요?

  1. 쉘 프로그램(cmd창)은 자신의 인스트럭션을 실행하면서 사용자가 명령하기를 기다립니다.
  2. 여러분이 "/hello"를 입력하면 쉘 프로그램은 각각의 문자를 레지스터에 읽어 들인 후, 메모리에 저장합니다. (그림1-5)


3. 쉘은 파일 내의 코드와 데이터를 복사하는 일련의 인스트럭션을 실행하여 실행파일 hello를 디스크에서 메인 메모리로 로딩합니다.(그림1-6)
4. 코드와 데이터가 메모리에 적재된 후 프로세서는 hello 프로그램의 main 루틴의 기계어 인스트럭션을 실행하기 시작합니다.


5. 이 인스트럭션들은 "hello, world\n" 스트링을 메모리로부터 레지스터 파일로 복사하고, 거기로부터 디스플레이 장치로 전송하여 화면에 글자들이 표시됩니다.(그림1-7)

잠깐 쉬어갈까요?

지루하시죠? 여기서 맨처음에 말씀드렸던 컴퓨터 살 때의 꿀팁을 보고 가겠습니다. "컴잘알이 알려주는 컴퓨터 부품"이라는 글입니다.

쓰레드, 캐쉬 메모리에 대해서는 이후에 배우게 됩니다. 가볍게만 보세요!

메모리의 성질

위 과정에서 보셨다시피, 시스템은 정보(데이터)를 한 곳에서 다른 곳으로 이동시키는 일에 많은 시간을 보냅니다. 이러한 시간을 줄일 수 있다면 컴퓨터의 성능을 높일 수 있겠죠! 이러한 목적을 위해선, 데이터 이동의 경로에 있었던 디스크, 메모리, 캐시에 대해 알아야합니다.

다나와라는 사이트에 컴퓨터를 고를 때에도 상대적으로 저장 공간이 적고 빠른 RAM과 저장 공간이 크고 느린 SSD(혹은 HDD)의 크기 차이는 2^4배(16배)부터 시작합니다. 가격은 2배 차이인데 말이죠.

"여기서, 물리학의 법칙 때문에 더 큰 저장장치들은 보다 작은 저장장치들보다 느린 속도를 갖습니다. 그리고 더 빠른 장치들은 더 느린 장치들보다 만드는 데 더 많이 비용이 듭니다.(옥수수를 많이 들고 있으면 이동하기에 느리겠죠!) ... 이러한 이유로 메인 메모리를 더 빠르게 동작하도록 만드는 것보다 프로세서를 더 빨리 동작하도록 만드는 것이 더 쉽고 비용이 적게 듭니다. 예를 들어 디스크 드라이브(HDD)는 메인 메모리(RAM)보다 1,000배는 크기가 더 크지만, 프로세서가 1워드의 데이터를 읽어들이는 데 걸리는 시간은 천만 배 더 오래 걸릴 수 있습니다." -CSAPP

결국 프로세서(CPU) 안에 Cache memory를 두어 메모리와 프로세서와의 거리를 좁히고, DRAM보다 용량이 작고속도가 빠르고 비싼 SRAM 칩으로 중요한 데이터만을 저장하여 컴퓨터의 성능을 향상시키는 것입니다.

  • 이러한 이유로 저장 장치들은 계층 구조를 이룹니다. 프로세서 칩 내에 들어 있는 L1 캐시는 대략 수천 바이트의 데이터를 저장할 수 있으며, 거의 CPU 레지스터 파일만큼 더 빠른 속도로 액세스할 수 있습니다. 이보다 더 큰 L2 캐시는 수백 Kb ~ 수 Mb의 용량을 가집니다. 프로세서의 액세스 속도는 L2 캐시는 L1보다 5배 정도 느리지만, 메인 메모리(RAM)보다는 5 ~ 10배 정도 빠릅니다.

  • 캐시 메모리라고 부르는 저장장치는 프로세서가 단기간에 필요로 할 가능성이 높은 정보를 임시로 저장할 목적으로 CPU Core의 내부 혹은 외부에 위치합니다. 쉽게 표현하면 CPU는 빠르게 일을 진행하고 있는데, 메모리에서 데이터를 가져오고 가져가는데 느려서 중간에 미리 CPU에 전달될 데이터를 들고 있는 형태라고 말할 수 있습니다. 이러한 캐시 메모리는 공간적, 시간적 지역성을 가지고 있습니다.

  • 어렵다면 PASS!
  • 공간적 지역성은 한 번 참조한 메모리의 옆에 있는 메모리를 다시 참조하게 되는 성질입니다. 예를 들어 배열에서 int a[10] 이라고 선언을 한다면 프로그램 중간에서 a[0]사용 후 인접한 a[1]사용될 확률이 높다는 것입니다.
  • 시간적 지역성은 한 번 참조된 주소의 내용은 곧 다음에 다시 참조된다는 특성입니다. 예를 들어 반복문에서 i라는 변수의 참조(메모리 주소)를 사용했다면, 반복문에서는 방금 접근했던 메모리를 다시 참고하게 될 확률이 높아지는 것이죠!

운영체제(Operate System)

  • 쉘 프로그램은 hello 프로그램을 로드하고 실행했을 때 다시 메모리나 디스크에 직접 액세스하지 않았습니다(그림 1-7). 오히려 운영체제가 제공하는 서비스를 이용합니다. 쉘을 이용해 hello 파일을 실행하는 상황을 가정하여 배워봅시다.

  • 쉘(shell)은 cmd(명령 프롬프트)보다 폭 넓은 기능을 제공하기 위해 window7부터 도입된 명령줄 인터페이스입니다. 리눅스에도 있습니다.

  • 프로세스는 프로세서, 메인 메모리, 입출력장치 모두의 추상화 결과입니다. 한마디로 실행 중인 프로그램에 대한 운영체제의 추상화라고 볼 수 있습니다. 한마디로 프로그램을 구동하여 프로그램 자체와 상태가 메모리 상에서 실행되는 작업 단위입니다.

추상화란 복잡한 현실 세계에서 핵심 개념이나 특정 특징을 간추려 내어 단순화하거나 일반화하는 과정을 말합니다.

  • 프로세스는 쓰레드라고 하는 다수의 실행 유닛으로 구성되어 있습니다. 각각의 쓰레드는 해당 프로세스에서 컨텍스트에서 실행되며 동일한 코드와 전역 데이터를 공유합니다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있습니다. 이러한 실행 방식을 멀티스레드(multithread)라고 합니다.

프로세스는 공장이고, 스레드는 그 안에서 일하는 직원이라 생각하면 편합니다!

위 화면은 여러분들이 프로그램을 강제 종료하기 위해 많이 키셨던 작업 관리자에서 '성능'탭입니다. 저희가 오늘 배운 키워드들이 많이 보이네용. 아래에 '리소스 모니터 열기'를 누르면 더 자세하게 볼 수 있답니다.

"멀티프로세스와 멀티스레드는 양쪽 모두 여러 흐름이 동시에 진행된다는 공통점을 가지고 있다. 하지만 멀티프로세스에서 각 프로세스는 독립적으로 실행되며 각각 별개의 메모리를 차지하고 있는 것과 달리 멀티스레드는 프로세스 내의 메모리를 공유해 사용할 수 있다. 또한 프로세스 간의 전환 속도보다 스레드 간의 전환 속도가 빠르다." - Wikipedia

  • 운영체제는 여러 개의 프로그램을 동시에 실행할 때 프로세서가 프로세스들을 바꿔주는 식으로 한 개의 CPU가 다수의 프로세스를 동시에 실행하는 것처럼 보이게 해줍니다. 운영체제는 문맥 전환(context switching)이라는 방법을 사용해서 교차 실행을 수행합니다.

  • 이를 위해 운영체제는 프로세스가 실행하는 데 필요한 모든 상태정보의 변화를 추적합니다. 이 컨텍스트(context)라고 부르는 상태 정보는 PC, 레지스터 파일, 메인 메모리의 현재 값(주소) 등을 포함합니다. 현재 프로세스에서 다른 새로운 프로세스로 제어를 옮기려고 할 때 현재 프로세스의 컨텍스트를 저장하고 새 프로세스의 컨텍스트를 복원시키는데, 이것이 문맥 전환입니다. 이를 통해 멀티 프로세싱(멀티 태스킹)이 가능한 것입니다.

정리: 우리가 컴퓨터에서 여러 프로그램을 돌릴 때 동시에 수행되는 것처럼 보이는 이유는 이러한 문맥 전환이 CPU 내에서 빠르게 이루어지고 있기 때문입니다.

  • 하나의 프로세스에서 다른 프로세스로의 전환은 운영체제 커널에 의해 관리됩니다. 커널은 운영체제 코드의 일부분으로 메모리에 상주합니다. 이는 컴퓨터 운영 체제의 핵심이 되는 컴퓨터 프로그램으로, 시스템의 모든 것을 완전히 제어합니다. 그만큼 내용이 방대하니, 지금은 이정도만 알아두시는 것이 좋습니다.

뒤의 챕터에서 더 자세하게 배울 내용이니 간단하게만 보세요!
1. hello라는 프로그램을 실행하라는 명령을 받으면, 쉘은 시스템 콜(system call)이라는 특수 함수를 호출하여 운영체제로 제어권을 넘겨줍니다.
2. 운영체제는 쉘의 컨텍스트를 저장하고 새로운 hello 프로세스와 컨텍스트를 생성한 뒤 제어권을 새 hello 프로세스로 넘겨줍니다.
3. hello가 종료되면 운영체제는 쉘 프로세스의 컨텍스트를 복구시키고 제어권을 넘겨주면서 다음 명령 줄 입력을 기다립니다.

  • 시스템 콜(시스템 호출)이란 운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스입니다. 한마디로 프로세스가 운영체제에게 운영체제 기능을 요청(call)하는 것입니다.
  1. 컴퓨터는 파일 읽기나 쓰기와 같은 syscall을 실행해서 커널에 제어를 넘겨줍니다.
  2. 커널은 요청된 작업을 수행하고 응용프로그램으로 리턴합니다.

예를들어, 운영체제는 회사의 관리자이고, 프로세스는 말단 직원입니다. 프로세스3이란 직원이 자신의 프로그램 코드를 실행중이었는데, 만약 자기 프로그램 외 특정 파일 데이터를 필요로 한다라는 상황을 가정해봅시다. 이 상황에서 프로세스3은 독자적으로 파일 데이터를 읽어올 수 있을까요? 아닙니다. 관리자 운영체제에게 허락을 요청(call)해야 합니다.

프로그램을 실행했을 때(syscall), 이런 창을 보신 적이 있나요? 해당 프로그램이 컴퓨터의 자원(디스크, 메모리, 네트워크 등)에 접근(액세스)하려는 것을 windows(OS)에서 차단하는 것입니다. 내 컴퓨터의 리소스(자원)은 운영체제만 사용할 수 있기 때문이죠. 대략적으로 이런 느낌!

  • 가상 메모리는 각 프로세스들이 가지는 균일한 메모리 공간을 추상화한 것입니다. 메인 메모리 전체를 몇 개의 정의된 영역으로 추상화할 수 있습니다. 영역에 대해 간단하게 살펴봅시다. (우리가 소스 코드를 작성할 때를 생각하며 읽어보세요!)
  1. Loaded from hello executable file은 프로그램 코드와 데이터들입니다.
  2. Run-time heap은 크기가 고정되어 있는 코드, 데이터 역영과 달리, C 표준 함수인 malloc이나 free를 호출하여 동적으로 크기가 변합니다.
  3. 공유 라이브러리는 C 표준 라이브러리(예를 들어 stdio.h와 같은)나 수학 라이브러리와 같은 라이브러리 코드와 데이터를 저장하는 영역입니다.
  4. Stack은 컴파일러가 함수 호출을 구현하기 위해 사용하는 영역입니다. 힙과 마찬가지로 동적으로 크기가 변합니다. 함수가 호출될 때는 스택이 커지고, 함수가 리턴될 때는 줄어듭니다.
  5. 커널 가상메모리로 커널을 위해 예약되어 있는 주소 공간입니다.

예를 들어, C 언어에서 int a = 10; 으로 전역 변수를 할당하면 Data 영역에 할당됩니다.
지역변수나 매개변수를 선언하면 이는 stack 영역에 할당됩니다.
heap 영역은 컴파일러가 컴파일하는 동안에 사용자 요구에 맞게 메모리를 할당해주는 (예를 들어 int arr[10] -> 40byte 필요) 영역입니다. 이를 위해 malloc 함수로 동적 할당을 해줍니다.

와 어렵네요! chapt 1.에서는 이정도만 아셔도 충분합니다.

  • 마지막으로, 파일은 연속된 바이트들입니다. 디스크, 키보드, 디스플레이 등의 모든 입출력장치는 파일로 모델링합니다. 시스템의 모든 입출력은 유닉스 I/O라는 시스템 콜들을 이용하여 파일을 읽고 쓰는 형태로 이루어집니다.

소결

이처럼 시스템이라는 것은 단지 하드웨어 그 이상의 것입니다. 응용프로그램의 실행이라는 궁극의 목적을 달성하기 위해 협력해야 하는 하드웨어와 시스템 소프트웨어의 상호작용을 배워봤습니다.

요약해볼까요?
1. 프로그램은 ASCII 문자로 시작해서 컴파일러와 링커에 의해 바이너리 실행파일들로 번역되는 방식으로, 다른 프로그램들에 의해 다른 형태로 번역됩니다.

  1. 프로세서는 메인 메모리에 저장된 바이너리 인스트럭션을 읽고 해석합니다.
  1. 프로그래머들은 메모리 계층 구조를 이해하고 활용해서 자신이 작성한 C 프로그램의 성능을 최적화할 수 있습니다.
  1. 커널은 응용프로그램과 하드웨어 사이에서 중간자의 역할을 합니다.

출처
https://ko.wikipedia.org/wiki/ARM_%ED%99%80%EB%94%A9%EC%8A%A4
https://wikidocs.net/111325
https://terms.tta.or.kr/dictionary/dictionaryView.do?word_seq=036124-2
https://hongong.hanbit.co.kr/%ec%bb%b4%ed%93%a8%ed%84%b0-%ea%b5%ac%ec%a1%b0%ec%99%80-%ec%9a%b4%ec%98%81%ec%b2%b4%ec%a0%9c%eb%a5%bc-%ec%95%8c%ec%95%84%ec%95%bc-%ed%95%98%eb%8a%94-%ec%9d%b4%ec%9c%a0/
https://www.fmkorea.com/index.php?document_srl=4954556241&cpage=4
https://dsnight.tistory.com/50
https://mamu2830.blogspot.com/2021/01/whatIsSystemCall.html

profile
고민하고 고뇌하는 개발자 (점심, 저녁 메뉴를)

0개의 댓글