컴퓨터시스템이 무엇일까? 


대부분의 컴공 수업들은 추상화를 강조한다. 인터페이스를 설계할때, 시간복잡도를 다룰때 프로그래머가 몰라도 되는 부분들은 제외하고 중요한 부분만 남긴다. 


하지만 버그가 날때, 코드가 생각한대로 동작하지 않을때 추상화의 한계가 드러난다.


이때는 추상화를 벗겨내고 아래에서 컴퓨터가 실제로 어떻게 돌아가고 있는지를 알아야 버그를 고칠 수 있다. 또한 프로그램의 성능을 최적화하고 싶을때에도 로우-레벨 지식은 필요하다. 


이때 필요한게 시스템에 대한 지식이다. 시스템은 애플리케이션의 반대말로, 앱이라는 기차가 잘 돌아갈 수 있게 밑받침이 되어주는 선로라고 볼 수 있겠다.


시스템 프로그램은 하드웨어와 가까워서 주로 C, C++, Rust 등의 언어를 쓴다. 또 시스템 프로그램은 절대 에러가 나면 안된다. 열차 선로가 고장나면 그 즉시 열차와 작별인사를 해야 할것이다.


시스템에 대해 배움으로써 연관된 과목들, 예를 들면 운영체제, 네트워크, 임베디드, 보안, 데이터베이스 등을 이해하기 위한 기초 지식이 쌓인다. 


다음 강의에 본격적으로 자료를 배우기 전에 이 연재글의 테마, 핵심 개념을 잠시 설명하겠다.




1. 핵심 개념


핵심 개념 1 : int는 정수가 아니고 float는 실수가 아니다


x^2 ≥ 0일까? x가 float타입이라면 그렇다. 하지만 x가 int타입이라면 이건 참이 아닐 수도 있다. 


x = 50,000이라면 오버플로우가 나서 x^2 = -1794967296이 나온다 (직접 C로 실험해보자!).


(x + y) + z = x + (y + z)일까? int / unsigned int타입이라면 그렇다. 하지만 float이라면 이건 아닐수도 있다. 


(1e20 + -1e20) + 3.14 = 3.14지만 1e20 + (-1e20 + 3.14) = 0이 된다. (1e20 = 100...0 (0 이 20개))


이런 문제들이 발생하는 이유는, 무한히 많은 수를 컴퓨터 안의 유한한 공간에 저장하려 들기 때문이다. 만약 보안이나 컴파일러같이 정확성 높은 코드를 써야 한다면 이런 것들을 잘 알고 있어야 한다.



핵심 개념 2 : 어셈블리어를 알라


아마 살면서 우리가 어셈블리어로 프로그램을 작성해야 할일은 없을 것이다. 하지만 어셈블리어로 된 프로그램을 읽을 줄은 알아야 한다. 프로세서가 어떻게 작동하는지 이해할 수 있기 때문이다.



핵심 개념 3 : 메모리는 중요하다

메모리는 유한하며, 할당되고 관리되어야 한다. 또한 성능 향상을 위해 캐시와 가상 메모리에 대해 알고 있어야 한다. 또한 메모리를 잘못 다뤘을때는 개같은 버그와 마주할 수도 있다.

typedef struct {
  int a[2];
  double d;
struct_t;
double fun(int i) {
  volatile struct_t s;
  s.d = 3.14;
  s.a[i= 1073741824; /* 메모리 참조가 잘못될 가능성이 있음 */
  return s.d;
}
/* fun(0) = 3.14
 * fun(1) = 3.14
 * fun(2) = 3.1399998664856
 * fun(3) = 2.00000061035156
 * fun(4) = 3.14
 * fun(6) = Segmentation fault
 */


이처럼 메모리를 잘못 참조하면 프로그램이 전혀 예상치 못한 방향으로 흘러갈 수 있으며 C/C++를 다룰때 제일 조심해야 하는것중 한가지이다. 


Valgrind등의 툴을 이용해 이러한 에러를 조금은 예방할 수 있으나 에러의 책임은 프로그래머에게 있다.



핵심 개념 4 : 시간복잡도가 다가 아니다


시간복잡도에선 시간이 2n이 걸리든 3n이 걸리든지 간에 둘다 O(n)으로 표시한다. 하지만 실제로는 이 상수계수도 중요하다. 또한 최적화에는 로우-레벨 지식도 필요하다. 다음의 코드를 보자.


똑같이 배열을 복사하는 코드지만, 왼쪽은 4.3ms이 걸렸고 오른쪽은 81.8ms가 걸렸다. 왜일까? 


이건 혼자 한번 생각해보자 (힌트 : 이중배열은 메모리 안에 일차원 배열처럼 저장되어있다.)



핵심 개념 5 : 컴퓨터는 프로그램을 실행하는것 외에도 다양한 일을 한다.


데이터를 입출력 하기도 하고 다른 컴퓨터와 네트워크를 통해 통신을 한다. 




이렇게 다섯가지의 핵심 개념이 있다... 물론 내가 말한건 아니고, 랜달 브라이언트라는 교수님이 컴퓨터 시스템에 대해 말씀하신 내용이다. 


랜달 브라이언트가 누구냐고? CSAPP(Computer Systems - A Programmer's Perspective) 이라는 책의 저자이다.  


아마 여러분이 이 책을 들어봤을 수도 있겠다. 여러 학교에서 교재로 쓰이고 있기 때문이다. 


사실 꽤 많은 대학교에서 교재로 쓰이고 있다. 다음 사진을 보자.


빨간 점이 모두 이 책을 쓰고 있는 학교이다. 카이스트, 포항공대, 서울대, 하버드, 스탠포드 등 여러 대학교가 이 책을 교재로 채택했으며 이렇게 전세계적인 교재로 채택된 것에는 그만한 이유가 있을거라 생각한다 (전체 대학 리스트를 보고싶으면 여기 있다).


이 책은 카네기멜론 대학에서 컴공 학생들이 배우는 15213 - Introduction to Computer Systems의 강의 노트에서 탄생했으며, 그 강의영상프로그래밍 랩은 무료로 풀려있다. 강의영상에 나오는 사람이 랜달 브라이언트 교수님 되시겠다. 


세계적인 저자가 가르치는 수업이 무료로 풀려있다? 이보다 독학하기 좋은 환경은 없다고 생각한다. 따라서 이 연재글 시리즈는 저 강의영상과 책을 번역하며 따라갈 것이다. 


찾아보니 한국어 번역본이 시중에 나와있다 (컴퓨터 시스템, Randal E. Bryant). 평가...는 복합적이지만. 





이 책의 핵심은 프로그래밍 랩이라고 저자들은 말한다. 일반적인 문제보다 더 어려운 코딩 과제이며(대부분 1주~2주의 기한이 있음) 저자들이 시작코드와 랩 설명서를 무료로 풀어놨다 (답안지는 없음). 


물론 구글링 해보니까 쫘르륵 나오긴 하는데… 개인적으로 안보고 도전해보는걸 추천한다. 


좋은 과제는 만드는데 시간이 꽤 걸리고, 과제를 풀때 거치는 시행착오가 나를 더 나은 프로그래머로 만들거라 생각하기 때문이다. 그런 이유로 랩의 해설은 올리지 않을거다.


다만 랩의 번역은 올릴 것이다. 찾아보니까 말록 랩 빼고 번역된게 없더라. 랩이 수업의 핵심인데 대 AI시대에 번역이 안되어있다는게 화가 나서 내가 할 것이다. DeepL에 넣고 돌린다음 검토하는 식으로 하면 되겠지. 


이 수업은 선수과목으로 C를 요구한다. C를 까먹었더라도 나중에 C 복습 강의가 있을 예정이니 너무 걱정하진 말자. 다만 C를 모른다면 한동안 C를 먼저 배우는걸 추천한다. 안그러면 중후반의 강의가 무슨뜻인지 이해가 잘 안될 것이다. 




2. 강의 개요

각 대단원에서 무엇을 배우는지 개요를 살펴보자. 


첫번째 대단원에서는 비트 연산과 어셈블리에 대해 배운다. 이 단원에는 다음 세개의 랩이 있다.

(1) 데이터랩 : 비트 연산자만 이용하여 다양한 함수를 구현해본다.

(2) 폭탄랩 : 주어진 바이너리 파일에 알맞은 문자열을 입력해 “폭탄”을 해체한다. 틀리면 폭탄이 “터지고” 점수가 깎인다. 바이너리 파일밖에 주어지지 않기 때문에 문자열은 어셈블리어 디버깅으로 알아내야 한다.

(3) 어택랩 : 현대 해커들이 스택을 이용해 공격하는걸 따라해본다.


두번째 대단원에서는 메모리 계층에 대해 배운다.

(4) 캐시랩 : C로 캐시를 시뮬레이션 해보는 프로그램을 만들고 최적화해본다.


세번째 대단원에서는 예외적인 제어흐름에 대해 배운다. 하드웨어 예외, 프로세스, 유닉스 시그널 등에 대해 배운다.

(5)셸랩 : 나만의 유닉스 셸을 개발해본다.


네번째 대단원에서는 가상메모리, 동적 할당에 대해 배운다.

(6)말록랩 : malloc과 free를 구현해본다. 이 수업에서 가장 어려운 과제로, 평균적으로 주당 40시간의 시간이 걸린다고 한다. 물론 우리는 원하는 만큼 걸려도 상관없다. 


마지막 대단원에서는 I/O, 네트워크(소켓), 동시성에 대해 배운다.

(7) 프록시랩 : 나만의 웹 프록시를 개발해본다.




3. 랩 세팅하기

랩을 자기 컴퓨터에서 쓰고 싶다면 두가지 방법이 있다. 첫번째는 여기서 원하는 랩의 Writeup(랩 설명)과 Self-Study Handout(코드가 압축된 tar파일)를 다운받고, 압축해제하면 필요한 코드가 다 나올 것이다. 근데 이 랩은 우분투 & 인텔 CPU에서 작동하도록 만들어졌다. 내 컴퓨터에서 안돌아가서 나는 두번째 방법을 써야 했다.


***이 두번째 방법으로 M1 Mac에서 docker를 돌릴경우 두번째 랩이 안되는, 현재 해결되지 않은 오류가 있다. 가능하다면 윈도우나 다른 컴퓨터에서 랩 실습을 할 것을 권장한다. ***


두번째 :

  1. 여기 가서 도커 다운받아라

  2. 도커 데스크탑 프로그램을 켜놓고 터미널에 docker pull xieguochao/csapp 친다.

  3. docker run -p 7777:7777 -v "$PWD/labs:/home/csapp/project" xieguochao/csapp

    를 쳐서 도커 컨테이너를 실행시킨다.

  4. 웹 브라우저에 http://localhost:7777 에 들어가서 웹버전 vscode를 연다 (패스워드는 csapp)

  5. 왼쪽 위에 메뉴에서 터미널을 열어서, cd projectgit clone https://github.com/bmadone/csapp-labs-starter.git 을 친다. 이러면 labs 폴더가 생길 것이다.

  6. 이제 웹버전 vscode대신 앱으로 쓰고싶다면 vscode 앱에서 docker 익스텐션을 다운받아서 Ctrl+shift+P를 누르고 Dev containers: attach to running container를 누른다.

  7. csapp-labs-starter/labs 폴더를 연다.

  8. 끝! 모든 랩이 다 다운된걸 확인할 수 있다.

  9. 도커 앱이 켜진 동안만 이게 작동된다. 도커 앱에서 stop을 누르면 vscode에서 연결이 끊어졌다고 뭐라 할텐데 그럼 다시 도커 앱을 키고 3번 커맨드를 다시 실행하고 vscode를 켜서 6번을 하면 된다.

    1. 여기서 stop 대신 remove을 누르면 안된다! 그러면 네가 한 작업이 다 사라진다. 이걸 방지하고 싶으면 docker commit 에 대해 알아보자. (이게 정확한 정보인지 나도 잘 모른다. 난 도커 뉴비니까 날 너무 신뢰하지 말고 직접 찾아보자)

  10. **맥이라면 잘 설치됐는지 확인하기 위해 docker version 을 쳐봐라. docker : command not found 가 뜬다면 docker가 $PATH에 없는 것이니 ~/.zshrc 파일을 열어서 맨 마지막에 export PATH="$PATH:/Applications/Docker.app/Contents/Resources/bin/" 를 추가한뒤 vscode를 재시작하면 될 것이다.

이렇게 상대적으로 쉽게 우분투 환경을 설치할 수 있다 (vm 쓰는것 보다는야…).




이게 두번째로 연재글 쓰는건데, 이번에는 진짜 맨땅에 헤딩이라서 함수형 프로그래밍만큼 빨리 연재되진 않을 것이다. 틀린 부분도 더 많을테니 댓글로 설명해줘라. 


글 읽으면서 따라올 사람은 느긋하게 랩을 즐겨주시면 되겠다. 과제를 어떻게 즐기냐고? 채점받는것도 아닌데 뭐. 안하고 싶으면 안해도 상관없는게 독학의 장점이다. 


긴글 읽어줘서 고맙고 다음에는 정수와 부동소수점을 다뤄보자.


1강 : 정수와 부동소수점

실습 1 : datalab

2강 : 어셈블리어 - 기초와 제어문

실습 2 : bomblab

3강 : 어셈블리어 - 프로시저와 데이터

실습 3 : attacklab



참고자료 1 (강의영상 01)

참고자료 2 (수업 홈페이지)