컴퓨터 프로그래밍 공부/컴퓨터 구조 + 운영체제

시스템 프로그래밍의 이해와 접근

뽀또치즈맛 2024. 11. 24. 22:30

언어와 프로그래밍

 

본격적으로 시스템 프로그래밍을 설명하기 전에 앞서,

언뜻 보면 C언어나 C++과 같은 언어를 많이 공부한다. 

물론 Java를 공부하거나 그 외 다른 언어를 공부하시는 분들 또한 많을 것이다.

 

이러한 것들은 컴퓨터 프로그래밍 언어인 것이지

컴퓨터 시스템에서 기능을 십분 활용할 수 있는 것들은 아니다.

이러한 언어를 이용해서 시스템 프로그래밍이란 것을 공부한다.

 

그렇게 되면,

시스템 프로그래밍..? 어

그거 시스템 프로그래밍만 전문적으로 하는 사람이 배워야 하는 거 아닌가?

싶을 수 있는데...

모든 프로그래머가 알아둬야 하는,

모든 응용 프로그램에 들어가는 요소가

이런 시스템 프로그래밍이다.

 

우리가 C언어를 공부하고 난 다음에,

내가 컴퓨터 위에서 돌아가는 무언가를 작성해야겠다!

라는 다짐이 생긴다면

시스템 프로그래밍을 공부하는 것은 필수이다.

해야만 한다는 것이다.

 

시스템 프로그래밍이란 결국 무엇일까?

 

우리가 윈도우 위에서 돌아가는 것들을 만들겠다. 하면,

윈도우즈 시스템 프로그래밍을 공부하면 되는 것이고,

리눅스다 하면, 리눅스 시스템 프로그래밍을 공부하면 되는 것이다.

 

컴퓨터 구조와 운영체제를 알아야 시스템 프로그래밍을 공부할 수 있다.

시스템 프로그래밍이라는 것 자체가 운영체제에 종속적인 것이다.

 

해당 시스템 프로그래밍을 공부해나가기 위해

해당 책의 내용을 비율을 따지자면

컴퓨터 구조 15~20%

운영체제 25~30%

SP 50% 

정도가 적절하다.

 

 

시스템 프로그래밍이란?

  • 시스템(컴퓨터 시스템)의 범위
    • 하드웨어 + 운영체제
  • 시스템 프로그래밍
    • 컴퓨터 시스템을 활용하는 소프트웨어 개발
    • Windows 운영체제 자체의 기능을 십분 활용하는 프로그래밍
  • 응용 소프트웨어 개발의 차이점
    • 시스템 프로그래밍은 모든 응용 프로그램에 포함되는 요소이다.

시스템이란?

시스템이란 = 하드웨어 + 운영체제를 포괄하는 개념이다.

A라는 시스템이다 라면, CPU는 인텔 기반이고, 운영체제는 윈도우다.

라는 개념이다.

 

시스템 == 하드웨어는 틀린 명제이다.

시스템 != 하드웨어이다.

시스템 == 하드웨어 + 운영체제 이다.

 

그럼 시스템 프로그래밍은?

컴퓨터 시스템을 활용하는 소프트웨어를 개발하는 것이다.

 

응용 프로그램에서는 항상 시스템 프로그래밍이 내포되어 있다.

그럼 응용 프로그램에서 시스템 프로그래밍이 어떤 의미를 가질까?

운영체제의 기능을 십분 활용하는 프로그래밍이란 의미를 가진다.

즉 응용 프로그램이 운영체제를 잘 활용하기 위해서 쓰는 것들이

시스템 프로그래밍이다.

 

그럼 응용 소프트웨어 개발과의 차이점이 있을까?

없다. 내포관계이다.

 

컴퓨터 시스템의 주요 구성요소

  • CPU, 캐쉬
    • 컴퓨터 하드웨어 구조
  • 운영체제
    • 메인 메모리
      • 메모리 관리 기법
    • 하드디스크
      • 파일 I/O (다양한 I/O 포함)

대학에서 위 그림의

첫 번째, 두 번째 구성요소에 해당하는 CPU와 캐쉬에 대한 내용을 

컴퓨터 구조라는 과목으로 다루고,

 

세 번째, 네 번째 구성요소에 해당하는 메인 메모리와 하드 디스크에 대한 내용을

운영체제 과목에서 다루는 것이 보통이다.

 

때문에 이 두 과목은 독립된 두개의 과목이 아니라

하나의 과목으로 인식하는 것이 좋다.

 

이 둘을 알아야 전체 시스템을 이해할 수 있기 때문이다.

컴퓨터 구조와 운영체제를 이해하게 되면,

시스템 프로그래밍만 가능하게 되는 것이 아니다.

 

언어의 문법적 요소만을 이해하고 바로 프로그래밍 하는 프로그래머보다

효율적인(속도를 높이고, 메모리를 효율적으로 사용하는)

프로그램을 구현하는 것이 가능해진다.

 

결과적으로 컴퓨터 잡지의 제목으로 등장하는 "Power Programmer"가 되는 것이다.

 

컴퓨터 구조에 관련해서는 큰 그림 위주로 설명할 것이며,

컴퓨터를 설계하는 엔지니어가 아니라면

컴퓨터 구조에 대해서는 요소요소에 대한 깊이 있는 이해보다는

큰 그림에 대한 이해가 더 중요하다.

 

그러나 운영체제에 관련해서는 상대적으로 깊이 있는 설명을 전개할 것이다.

 

 

컴퓨터 하드웨어 구성 전체

 

이번 게시글에서는 컴퓨터 구조에 대한 큰 그림을 그려 보겠다.

다음 그림에서는 컴퓨터 하드웨어 구성을 보여주고 있다.

 

위 그림을 보면 상당히 복잡해 보일지 모르겠다.

그러나 이 그림은 하드웨어 구성을 제법 간략화 해놓은 것이다.

절대 복잡한 그림은 아니다.

우선 각각의 구성요소에 대해서 간단히 정의만 하고 넘어가겠다.

 

CPU(Central Processing Unit)

우리가 흔히 말하는 중앙처리장치가 바로 CPU이다.

기본적으로 CPU가 연산을 담당한다는 것 정도는 알고 있을 것이다.

CPU는 컴퓨터 프로그램의 실행에 있어서 핵심적인 역할을 담당한다.

따라서 컴퓨터의 머리에 해당한다고 생각하면 되겠다.

 

우리는 프로그래머이므로 이보다 더 구체적으로 이해하고 있어야 한다.

이 구체적 내용은 메인 메모리와 입출력 버스를 간략히 설명한 뒤에 다시 설명하겠다.

 

메인 메모리(Main Memory)

램(RAM)이라는 저장장치로 구성되는 메인 메모리는

컴파일이 완료된 프로그램 코드가 올라가서 실행되는 영역이라고 간단히 정의할 수 있다.

게임을 인터넷을 통해 내려받으면 게임은 하드 디스크에 저장되고,

이 게임을 실행시키기 위한 프로그램의 메모리는 메인 메모리로 올라가서 실행된다.

이렇듯 메인 메모리는 프로그램 실행을 위해 존재하는 메모리라고 생각하면 된다.

 

입출력 버스(Input/Output But)

 

입출력 버스는 컴퓨터를 구성하는 구성요소 사이에서

데이터를 주고 받기 위해 사용되는 경로이다.

고속도로에 비유되는 것이 보통인데,

주고 받는 데이터의 종류와 역할에 따라서

어드레스 버스(Address Bus), 데이터 버스(Data Bus), 컨트롤 버스(Control Bus) 

이렇게 세 가지로 구분이 된다.

 

버스가 세 가지로 나뉜다는 내용 보다는

버스의 역할에 관심을 가지길 바란다.

그림에서 보여주듯

하드디스크, 메인 메모리, CPU 등등이 모두 버스에 연결되어 있다.

 

때문에 이러한 버스 시스템을 기반으로

하드디스크에 있는 데이터를 메인 메모리로, 

메인 메모리에 있는 데이터를 하드디스크로 전송하는 것이 가능하고,

메인 메모리와 CPU 사이에서 입출력도 가능하다.

이러한 기능성을 제공하는 것이 바로 버스 시스템이다.

 

버스가 CPU 에 값을 전달할 때는

버스인터페이스와 상호작용을 한다.

버스인터페이스를 통해서 값을 받는 것은 레지스터이다. 

 

CPU에 대한 이해

이제 본격적으로 CPU 내부를 들여다볼 차례이다.

CPU를 이루는 주요 구성요소는 다음과 같다.

이들이 어떻게 상호 작용하면서 동작하는지 대해서는 예를 들어가며 설명할 것이다.

구성요소 하나 하나가 어떠한 기능을 제공하는지 파악하길 바란다.

 

ALU(Arithmetic Logic Unit)

프로그램이 실행되는 곳은 어디인가? 라고 물으면 보통 CPU라고 대답한다.

정확한 대답이다.

CPU는 덧셈이나 뺄셈과 같은 연산을 진행하는 주체가 되기 때문이다.

 

그런데 CPU 내부에는 실제 연산을 담당하는 ALU라는 블록이 있다.

그 밖에 나머지 블록들은 연산을 하는데 도움을 주는 블록들이다.

 

ALU가 처리하는 기본적인 연산은 크게 두 가지로 나뉜다.

하나는 덧셈이나 뺄셈과 같은 산술 연산이고,

나머지 하나는 AND나 OR와 같은 논리 연산이다.

우리가 작성하는 아주 복잡한 프로그램도 CPU입장에서는

대부분 이 두 가지 형태의 연산으로 이뤄진다.

 

 

컨트롤 유닛(Control Unit)

프로그래머가 작성한 프로그램을 컴파일하면 실행파일이 생성된다.

이 실행파일에는 CPU에게 일을 시키기 위한 명령어들이 저장되어 있다.

어떤 경로를 거치던 간에 이 명령어가

CPU 내부로 흘러들어가야 CPU에게 일을 시킬 수 있다.

 

아직 그 과정은 잘 모르지만,

명령어가 CPU 내부로 흘러 들어가는데 성공했다고 가정 하자.

앞에서 실제 연산을 담당하는 블록을 ALU라고 하였으니,

정확한 표현을 위해 CPU 내부의 ALU로 명령어가 전송되었다 가정하자.

명령어의 형태는 다음과 같다.

1과 0으로 모든 것을 처리하는 것이 컴퓨터이니

32비트 명령어라면 다음과 같이 구성될 것이다.

"10011010 00011010 10011110 10010011"

 

ALU가 이 명령을 이해할 수 있겠는가? 덧셈이라는 건지, 뭘 하라는 건지

ALU는 이해할 수 없다.

산술 연산과 논리 연산만 할 줄 아는 ALU는 그만큼 단순하다.

따라서 ALU를 대신해서 누군가가 이 명령어를 해석해 줘야 한다.

이러한 도움을 주는 구성요소가 바로 컨트롤 유닛이다.

 

컨트롤 유닛이 하는 일은 CPU가 처리해야 할 명령어들을 해석하는 것이다.

컨트롤 유닛은 명령어를 해석하고,

그 해석된 결과에 따라 적절한 신호를 CPU의 다른 블록에 보내는 일을 한다.

CPU의 총 사령관 정도로 생각하면 되겠다.

 

CPU 내부에 존재하는 레지스터들(Register Set)

명령어와 피연산자를 CPU의 어느 요소로 보내면 좋겠는가?

명령어는 컨트롤 유닛으로, 피연산자는 ALU로 보내면 된다.

그러나 현재 ALU는 연산 중이고

컨트롤 유닛은 앞서 들어온 명령어를 해석하고 있는 상황이라면

이것이 불가능할 수도 있다.

 

그래서 이보다는 컨트롤 유닛이나 ALU가 필요로 하는 명령어 및 데이터들을

어디엔가 저장해두고 상황이 허락될 때 직접 가져가도록 하면 좋을 것이다.

 

다시 말해서 CPU 내부에도 임시적으로 데이터를 저장하기 위한

조그마한 메모리 공간이 필요하다는 뜻이다.

CPU에 이러한 매모리가 존재한다.

레지스터(Register)라는 아주 작은 메모리가 CPU 내에 존재한다.

 

레지스터란 CPU 내부에 존재하는 2진 데이터(Binary Data) 저장을 위한 저장장치이다.

크기가 큰 메인 메모리나 하드디스크와 달리,

CPU에 따라서 16비트, 32비트, 64비트 정도의 데이터를

저장할 수 있는 크기로 구성된다.

 

이러한 레지스터들은 CPU 내부에 여러 개가 존재하는데

CPU의 종류에 따라서 그 개수와 형태가 다양하다.

각각의 용도가 정해져 있는 것이 일반적이며

이들은 CPU가 연산을 하기 위해서 반드시 필요하다.

아주 기본적인 레지스터만 해도 8개가 넘는다.

 

버스 인터페이스 (Bus Interface)

앞에서 명령어와 데이터들이 CPU 안으로 잘 흘러 들어갔다고 가정하였는데,

이것이 가능한 이유는 버스 인터페이스가 있기 때문이다.

그렇다면 버스 인터페이스가 무엇인지 살펴보자.

 

컴퓨터 뚜껑을 열어보면 CPU, HHD, RAM, 사운드 카드, GPU 등

대충 어렵지 않게 찾을 수 있다.

이들은 서로 독립적으로 동작하는 것이 아니라

서로 데이터를 주고받으면서 동작한다.

 

그렇다면 서로 데이터를 주고 받기 위해서는

어떠한 매개체가 있어야 할 것인데,

이것이 바로 I/O 버스이다.

위 그림을 보면 CPU는 I/O 버스에 접근이 가능한 것으로 셜명되고 있다.

이 버스에 접근해서 데이터를 전송하기도 하고, 입력 받기도 한다.

그런데 너무 쉽게만 생각해서는 안 된다.

지하철이나 비행기도 이용방법을 알아야 탑승이 가능한 것처럼

I/O 버스의 통신방식을 이해하지 못하면 데이터를 보낼 수도, 받을 수도 없다.

즉, CPU 내에는 I/O 버스의 통신방식을 이해하고 있는 그 무엇인가가 있어야만 한다.

 

이 역할을 하는 것이 버스 인터페이스이다.

이 버스 인터페이스 장치는 버스가 어떻게 데이터를 전송하는지,

그에 대한 프로토콜 혹은 통신방식을 알고 있는 녀석이다.

따라서 CPU는 버스 인터페이스를 통해서 CPU 내부에 저장되어 있는 데이터를

(레지스터에 저장되어 있는 데이터를) I/O 버스에 실어 내보내기도 하고,

I/O 버스를 통해서 전송되어 오는 데이터를 수신하기도 한다.

 

클럭 신호 (Clock Pulse)

클럭 신호는 CPU를 구성하는 요소는 아니다.

그래서 위 그림에서도 클럭 신호에 대한 내용은 보여주지 않았다.

그러나 CPU를 구성하는 구성 요소에

제공되어야 하는 신호로서 아주 중요한 의미를 지닌다.

 

클럭 신호는 타이밍을 제공하기 위해서 필요한 것이다.

CPU는 매 클럭이 발생할 때 마다 그 클럭에 맞춰서 일을 한다.

즉 클럭 속도가 높으면 초당 처리하는 명령어의 개수가 많아지므로

컴퓨터의 전체적인 성능은 좋아지기 마련이다.

 

예를 들어 박자가 빠른 춤을 추는 무용사는 빨리 움직이고,

박자가 느린 춤을 추는 무용사는 느리게 움직이는 것과 같다.

 

클럭발생기에 의해 발생되는 클럭 신호는 CPU를 구성하는 요소에 제공되며

이 신호에 맞춰서 CPU가 일을 한다.

 

클럭 신호가 발생할 때에만 일을 한다니까 CPU가 많이 논다는 생각이 들기도 한다.

클럭이 발생을 하든지 안하든지 힘 닿는 데까지 일하면 훨씬 빠를텐데 말이다.

하지만 컴퓨터 시스템은 동기화를 필요로 한다.

이러한 정확한 이유를 이해하기 위해서는 디지털논리회로를 공부하면 좋다.

 

다음은 동기화의 필요성을 그림 예시이다.

 

위 그림과 같은 장비가 있다고 가정해 보자.

대략 덧셈만 가능한 계산기의 일부라 해도 좋겠다.

이 장비를 구성하는 3가지 주요 요소는 + 연산장치, 연산결과를 저장하는 버퍼,

그리고 연산 결과를 출력해주는 출력장치다.

 

기본적인 동작과정은 이렇다.

우선 연산장치는 입력된 두 개의 정수값을 더해서 그 결과를 버퍼에 가져다 놓는다.

그러면, 출력장치는 버퍼에 존재하는 데이터를 가져다가 사용자에게 그 결과를 보여준다.

언뜻 보면 아무런 문제가 없는 듯하다.

 

현재 input1 과 input2를 통해서 계속해서 데이터가 들어오고 있다.

따라서 + 연산장치는 계속해서 '+' 연산을 처리해야 하며,

출력 장치는 BUFFER에 존재하는 데이터를 계속해서 가져다가 출력해야 한다.

그런데 문제는 + 연산장치가 연산하는 속도와

출력장치가 데이터를 가져가는 속도가 일치하지 않는다는 것이다.

 

만약에 출력장치가 데이터를 가져가는 속도가 더 빠르다면,

출력장치는 이미 한번 가져간 데이터를

다시 가져다가 출력하는 문제점을 드러낼 것이고,

+ 연산장치가 더 빠르다면,

버퍼를 덮어쓰게 되어 연산결과의 일부가 출력되지 않는 문제점이 발생할 것이다.

이러한 문제를 해결해야만 하다면 어떻게 해야할까?

간단하다. 속도가 느린 장치의 장단에 맞춰주면 된다.

 

클럭 신로 발생속도를 + 연산장치에 넣고

이 신호에 맞춰서 데이터를 이동시키면 되는 것이다.

지금까지 언급한 내용은 컴퓨터 시스템에도 그대로 적용된다.

이와 유사한 이유로 클럭 신호를 필요로 하는 것이다.

 

임베디드 시스템이 하나의 큰 이슈가 되면서

ARM이나 MIPS와 같은 코어들을 직접 다루는 개발자들이 늘고 있다.

이러한 작업을 하게 되면 코어들의 클럭을

프로그램 코드상에서 직접 조절하는 경험을 해볼 수 있다.

이때에 클럭을 높이되 어디까지 높이느냐가 종종 관건이 되기도 한다.

최대한 클럭을 높이면 좋겠지만 특정 범위 이상으로 높여버리면

시스템이 불안정하게 동작하기 시작한다.