본문 바로가기
전공백서/전기정보공학부

전기정보공학부: 컴퓨터 시스템 프로그래밍

by STEMSNU 2024. 7. 1.
Welcome file

안녕하세요! 공우 15기, 전기정보공학부 장지환입니다. 이 과목은 '22-'23년도에는 대학원 과목인 ‘시스템소프트웨어특강’의 한 세부과목으로 개설되었으나, '24년도부터는 ‘컴퓨터 시스템 프로그래밍’이라는 이름의 강좌가 학부에서 개설됩니다. 처음에 대학원 강좌로 열린 것은 개설 절차가 복잡했기 때문이고, 이전에도 강의 내용은 학부 수준을 지나지 않았습니다. 이 점 참고 바랍니다.

1. 과목에서 배울 수 있는 내용

1.1 과목의 전반적인 개요

컴퓨터와 프로그래밍에 대해 조금씩 공부를 하다 보면 연결이 되지 않거나 의문이 드는 부분이 있습니다. 구체적인 예시와 함께 살펴보죠.

위 사진은 변수 a를 선언하고 a의 주소를 출력하는 간단한 프로그램입니다. 여기서 주소란, 해당 변수의 정보가 메모리에서 몇 번째 byte부터 저장되어 있는 지를 의미하는데요, 16 진법으로 7ffd 858c c234번째, 10 진법으로 140,726,844,047,924번째 byte가 존재하려면 최소 141 TB의 메모리(그것도 디스크가 아닌 RAM)를 가져야 합니다. 하지만 고작 16 GB의 메모리를 가진 제 노트북에서 아무 문제 없이 작동하죠.

다음 코드는 동일한 2차원 배열을 서로 다른 방향으로 순차적으로 접근합니다. 사진에서는 생략된 코드를 통해 각 방식에서 소요되는 평균 시간을 출력하도록 했는데요, 무려 4배에 가까운 차이가 발생하는 것을 확인할 수 있습니다. 코드의 기능이 아닌 효율성은 C언어의 문법에서는 절대 알 수 없는 부분이죠.

이 모든 질문을 하기 전, 근본적으로 접근해볼 수도 있습니다. 컴퓨터는 0과 1을 기반으로 동작한다는 이야기는 많이 들어보셨을 텐데요. 우리가 작성하는 코드는 알파벳과 특수 문자가 가득한 텍스트일 뿐, 0/1과는 거리가 있어 보입니다. 컴퓨터가 이해한다는 이진수는 어디에서 볼 수 있으며, 어떤 규칙으로 이루어져 있을까요?

이 모든 의문에 대해 아키텍쳐부터 운영체제, 컴파일러 레벨까지 넘나들며 컴퓨터 시스템에 대한 전반적인 시야를 갖추도록 하는 과목이 시스템프로그래밍이라고 할 수 있겠습니다.

1.2 키워드 별 개념 설명

1) Assembly/Machine Code

우리가 Python으로 코드를 짤 때 사용하는 함수들을 떠올려봅시다. 터미널에 문자열을 출력하는 print(), 리스트의 요소의 총합을 계산하는 sum(), 리스트를 정렬해주는 sort(), … 이 함수들이 가지는 기능은 어떻게 구현되어 있을까요? 이 모든 것을 하드웨어로 구현하려고 한다면, 수많은 함수 각각에 대해 전용 논리 회로가 필요합니다. 칩이 너무 복잡해지는 것은 물론, 추후 오류 수정이나 기능 추가의 측면에서도 자유도가 떨어집니다.

따라서 HW는 굉장히 한정적인 일만 하도록 하고, 컴퓨터는 프로그래머가 작성한 코드를 하드웨어가 할 수 있는, 작고 귀여운 명령의 조합으로 번역해서 실행합니다. 이때 번역하는 과정을 컴파일이라고 하며, 번역기의 역할을 하는 프로그램을 컴파일러, 그리고 번역된 코드의 언어를 바로 assembly라고 합니다.

어셈블리 명령어들은 크게 세 종류로 나눌 수 있습니다. 메모리의 값을 읽고 쓰는 명령, 읽어온 값에 다양한 연산을 취하는 명령, 그리고 코드 흐름의 분기를 유발하는 명령입니다. 여기서 흐름의 분기는 코드가 순차적으로 실행되지 않는 경우를 의미하는데요. 조건문, 반복문의 가장 밑에서 위로 되돌아가는 경우, 함수 호출 및 반환 등이 해당합니다.

2) Memory/Virtualization

메모리에는 프로그래머가 선언하는 변수의 값뿐만 아니라, 프로그램의 코드도 함께 저장됩니다. 이 정보들이 섞이게 되면 큰 문제가 생기겠죠? 따라서 영역을 나눠 서로 다른 역할의 정보를 다른 영역에 저장하게 됩니다.

요즘은 잘 등장하지 않지만, 예전에 컴퓨터에서 프로그램을 실행하다가 이런 에러 메세지와 함께 프로그램이 강제로 종료되는 모습 한 번 씩 보신 적이 있을 겁니다. 보안을 위해 값을 읽거나 쓸 수 없게 설정되어있는 영역이 있는데요, 코드의 오류로 인해 포인터 변수가 제대로 된 주소가 아닌 다른 변수의 값을 갖게 되는 경우, 높은 확률로 접근할 수 없는 영역을 가리키게 되면서 오류가 발생하는 겁니다.

하지만, 앞에서 이야기했듯이 지금까지 이야기하는 모든 주소가 실제 메모리 주소일 수가 없습니다. 각 프로그램이 독립적인 메모리 공간을 가질 때 시스템이 더 안전해지고, 컴파일러가 고려해야 할 사항도 확연히 줄어듭니다. 따라서 각 프로그램이 가상의, 독립된 메모리 공간을 가지도록 하고, 가상 메모리와 실제 메모리를 mapping함으로 이를 구현했는데요. 이를 **virtualization(추상화)**이라고 합니다. 수업에서는 virtualization이 가지는 이점과 overhead, 그리고 그 overhead를 줄일 수 있는 기법을 소개합니다.

3) Process/Thread

지금까지는 '프로그램’이라는 단어를 써왔는데요, 사실 정확한 용어가 아닙니다. 프로그램은 정적인 코드, 즉 design-time entity에 해당합니다. 반면 run-time entity, 즉 실행 중인 프로그램을 나타내는 용어는 process입니다.

4) Exception/Interrupt

프로그램을 수행하다 보면 오류에 의해, 또는 악의적인 의도가 감지되어 더 이상 실행될 수 없는 경우가 있습니다. 수를 0으로 나눌 때, 혹은 위에서 이야기한, 금지된 메모리 영역에 접근할 때 등이 있는데요, 이 때는 사용자 프로그램이 아닌 운영체제가 개입하여 상황을 정리하게 됩니다. 구체적으로 어떤 경우가 '오류’로 판단되는지, 기존에 실행 중이던 프로세스의 다음 명령이 아닌 운영체제의 코드가 실행되도록 하는 전환하는 것은 어떻게 이루어지는지 배웁니다.

한편, 컴퓨터는 단순히 명령의 실행 만으로 동작하지 않습니다. 키보드나 마우스로부터 입력을 받고, 모니터나 스피커 등으로 정보를 출력하기도 하죠. 하지만 입력이 clock cycle에 맞춰서 발생하는 것이 아닙니다. 이렇게 비동기적으로 발생하는, 외부로부터의 신호가 발생했을 때 이를 기억해두었다가, 준비가 되었을 때 프로세스를 잠시 멈추고 외부 신호를 처리하기 위한 루틴을 실행해야 합니다.

이렇게 다양한 예외 상황을 그 특성에 따라 크게 exceptioninterrupt로 나눌 수 있습니다. 용어의 구체적인 정의는 자료에 따라 조금씩 다를 수 있는데, 일반적으로 exception은 소프트웨어에서 발생한 예상치 못한 오류를, interrupt는 하드웨어나 소프트웨어에서 특정 상황에 대한 처리를 지시하기 위해 의도적으로 발생시키는 신호를 의미합니다.

5) Synchronization

은행의 서버를 설계하는 상황을 가정해봅시다. ATM에서 누군가가 현금 10만원을 인출할 때 은행 서버의 동작은 (1) 메모리에서 잔고 불러오기 (2) 10만원 빼기 (3) 다시 메모리에 저장하기의 순서로 이루어질 것입니다. 계좌 잔고가 20만원인 사람 A가 있다고 해보죠. 서로 다른 ATM에서 비슷한 시간대에 인출을 시도하는 상황을 가정해볼까요?

[data race]

시나리오에 따라 최종 결과가 달라지는 경우 발생하는 것을 확인할 수 있습니다. 이를 data race라고 합니다. 이 외에도 여러 프로그램이 동시에 수행될 때 이들이 공유하는 메모리로 인해서, 혹은 이들이 공유하는 HW 자원의 배분에 의해서 새롭게 생겨나는 문제들이 있습니다. 이러한 문제를 해결하기 위해 구현되어 있는 기능을 배우게 됩니다.

앞서 이야기한 은행 문제는 어떻게 해결할 수 있을까요? (1)~(3)의 동작을 묶음으로 묶어서 한 번에 한 ATM만 수행할 수 있도록 하면 됩니다.

2. 선배의 조언

시스템 프로그래밍은 전기과에서 개설된 지 얼마 되지 않았지만, 컴퓨터 분야에서 필수적인 과목입니다. 하지만 아키텍쳐나 운영체제 등의 과목과는 지향하는 방향이 다른데요. 컴퓨터조직론(컴퓨터 구조)나 운영체제에서는 각 계층을 설계자의 관점에서 바라봅니다. 이 계층이 어떤 기능을 가져야 하는지, 그 기능이 어떻게 구현되어 있는지, 구현하는 기법이 여러 개라면 그들의 장단점은 무엇이고, 어떤 상황에 어떤 기법이 더 적합한 지 등을 배웁니다. 반면, 시스템 프로그래밍에서는 프로그래머의 관점에서, 각 기능이 어떻게 구현되어 있는지를 앎으로 내 프로그램이 더 효율적으로 동작하도록 하거나, 자칫 뜬금없어 보일 수 있는 오류의 원인을 찾고 해결할 수 있도록 하는 것이죠.

때문에 다른 과목의 실습이나 프로젝트는 해당 시스템의 원시적인, 혹은 간소화된 형태를 직접 구현해보는 형태를 가진다면, 시스템 프로그래밍은 현대 아키텍쳐의 표준이라고 할 수 있는 x86-64를 사용하여 실습이 이루어집니다. 실습 과제는 컴구/시프 교과서의 정석인 CS:APP에 수록된 실습의 일부와 교수님께서 자체적으로 만드신 실습 하나로 구성되어있고, 이들은 어셈블리 코드의 해석 또는 C 프로그래밍으로 이루어져 있습니다. 어셈블리는 수업에서 다루기 때문에 문제가 되지 않지만, C에서는 포인터와 type casting의 개념 정도는 알고 있어야 과제 진행이 가능합니다.

컴퓨터 분야의 과목을 수강하다 보면, 어려운 선행 지식을 요하거나 유도되는 과정이 복잡한 지식이 등장하는 경우는 드뭅니다. 때문에 '그렇구나~'하면서 쉽게 넘기기 쉬운 내용들이 많은데, 하나하나 멈춰서 low-level에서 설정된 기본적인 원칙들의 조합이 어떻게 각 기능을 구현하고 완성하는지 따져봐야 진정한 이해가 가능합니다. 이 과정에서 겹겹이 쌓여 있는 abstraction layer 사이를 오가며 각 지식을 연결하고 전체적인 시야를 갖추는 것이 다른 컴퓨터 과목을 듣기 위해서도 중요합니다.

3. 진로 선택에 도움되는 점

앞에서도 말했듯, 이 과목은 컴퓨터가 어떻게 돌아가는 지에 대한 시야를 제공해주는 과목입니다. 컴퓨터의 모든 분야의 밑바탕이 되어주는 과목인 셈이죠.

다만 이병영 교수님께서 시스템 보안에 관련된 연구를 진행하고 계시다 보니, 수업 중간 중간 각 주제와 연관된, 실제로 이슈가 된 취약점 등을 가져와 보여주시는 경우도 있고, 연구실에서 직접 개발한, 낮은 확률로 나타나는 오류를 재현하기 위한 툴을 소개해주시기도 합니다. ‘보안’이라는 분야가 막연하게 다가온다면, 맛 보기에 좋은 수업이라고 생각합니다.

Refernces

[1] ZW3D Installation and Uninstallation, https://confluence.zwcad.com/pages/viewpage.action?pageId=125219345

댓글