총 4일간, 실시간으로 컴퓨터 공학 개론 수업을 들었다. 온라인 강의를 포함하면 7일 정도. 실시간 강의에서는 자료구조를 제외하곤 실습이 많지는 않았지만 강사님의 해박 전공 지식과 명확한 설명 덕분에 컴퓨터 공학의 전반적인 이해를 쌓을 수 있었다. 해당 분야에서 박사 과정을 준비 중이라고 하셨는데 그런 만큼 다양한 관점에서 많은 내용을 전달해 주셨다.
Computer Science and Engineering CES
CES는 여러가지 분야를 다루지만 그 중에서도 중요하게 다루는 분야는 아래와 같다.
- Basic Concenpts of Programming Language
- Data Structure and Algorithms
- Computer Architecture
- Operating System
- Computer Network
- Database System
- Software Engineering
이 외에도 다양한 분야들을 공부하게 되는데 특히, Computer System과 Programming에 대한 내용을 깊이 배운다. CSE는 컴퓨터를 이용하여 주어진 문제를 계산 가능하게 하는 학문이다. 이를 위해서는 컴퓨터가 이해할 수 있게 문제를 정의하고(Computational Thinking), 잘 정의된 문제를 컴퓨터가 계산할 수 있게 명령을 내리는 과정(Problem Solving)이 필요하다.
Data Structure
자료구조는 데이터를 구성, 저장, 조작하는 방법을 의미한다. 데이터를 효율적으로 관리하고 활용할 수 있도록 설계된 다양한 구조를 포함한다. 이를 통해 데이터의 특성에 맞는 효율적인 접근과 수정이 가능하다. 대표적으로는 Array, List, Stack, Queue, Tree, Graph가 있다. 소프트웨어 개발에서 성능을 극대화하고, 데이터 처리 속도를 개선하기 위해 필수적으로 알아야 하는 내용이다.
Abstarct Data Type ADT
ADT(추상 자료형)는 복잡한 데이터나 시스템을 추상화하여 핵심적인 개념과 기능만을 간추려 논리적으로 정의한 것이다. 실제 데이터의 형태를 그대로 다루는 것이 아니라 데이터의 명세(specification)를 기반으로 특성을 나타내는 변수나 필요한 연산 등이 포함된다.
Stack
Stack(스택)은 LIFO(Last-In First-Out) 구조를 가진 자료구조이다. 삽입과 삭제가 항상 제일 뒤(최근) 요소에서 이루어지며 가장 최근에 삽입한 요소를 top이라고 한다. 이러한 Stack 구조는 컴퓨터 가상메모리의 Stack 영역에서 사용되는데, 함수가 호출되면서 다시 복귀할 주소를 저장하거나, 지역변수, 매개변수 등을 임시로 저장하는데에 쓰인다.
# numpy를 이용한 stack
import numpy as np
class Stack:
def __init__(self, max_size=5):
self.stack = np.zeros(max_size, dtype=object)
self.max = max_size - 1
self.top = -1
def isEmpty(self):
return self.top == -1
def isFull(self):
return self.top == self.max
def append(self, item):
if self.isFull():
print("Stack is Full!")
return False
else:
self.top += 1
self.stack[self.top] = item
return True
def pop(self):
if self.isEmpty():
print("Stack is Empty!")
return None
else:
pop_item = self.stack[self.top]
self.stack[self.top] = 0
self.top -= 1
return pop_item
Queue
Queue(큐)는 FIFO(First-In First-Out) 구조를 가진 자료구조이다. 먼저 들어온 요소를 먼저 내보내며 front와 rear로 가장 먼저 들어온 요소와 제일 마지막에 들어온 요소에 접근한다.
# numpy를 이용한 queue
import numpy as np
class Queue:
def __init__(self, max_size):
self.queue = np.zeros(max_size, dtype=object)
self.front = 0
self.rear = 0
self.max = max_size
self.count = 0
def isEmpty(self):
return self.count == 0
def isFull(self):
return self.count == self.max
def enqueue(self, item):
if self.isFull():
print("Queue is full!")
return False
else:
self.queue[self.rear] = item
self.rear = (self.rear + 1) % self.max
self.count += 1
return True
def dequeue(self):
if self.isEmpty():
print("Queue is empty!")
return None
else:
item = self.queue[self.front]
self.queue[self.front] = 0
self.front = (self.front + 1) % self.max
self.count -= 1
return item
Hash Table
Hash Table(해시 테이블)은 데이터를 효율적으로 저장하고 검색하기 위해 key를 hash function을 사용해 hash value로 변환하고, 이 값을 인덱스로 활용하는 자료구조이다. 평균적으로 O(1)의 시간 복잡도로 데이터에 접근할 수 있어 매우 효율적인 자료구조이다. Python의 dict가 내부적으로 Hash Table 기반으로 구현되어 있다. Hash Function을 통해 저장할 곳을 구분하므로 어떤 함수를 쓰느냐에 따라 성능 차이가 난다. Hash Table은 정해진 메모리에서 여러 원소를 효율적으로 저장하고 인덱싱 성능을 O(1)에 가깝게 만드는 게 목적이다.
Hash Function을 통해 hasing을 하게 되면 다른 원소가 같은 index를 가지는 Hash Collision(해시 충돌)이 발생한다. 이를 해결하기 위해서 사용되는 두 가지 방법이 있다.
- Separate Chaining : 해시 테이블의 각 인덱스에 연결 리스트나 다른 자료 구조를 사용하여 여러 개의 key-value 쌍을 저장. 간단하고 저장 공간을 동적으로 조절할 수 있음.
- Open Addressing : 충돌이 발생하면 다른 빈 슬롯을 찾아 데이터를 저장. 추가적인 자료구조가 필요하지 않아 메모리 효율성이 높음.
The Components of A Computer System
컴퓨터 시스템은 다양한 하드웨어와 소프트웨어로 이루어져있다. 주요 구성 요소는 다음과 같다.
- CPU (Central Processing Unit) : 프로그램의 명령어를 해석하고 실행하는 역할을 한다. 산술 논리 연산 장치(ALU), 제어 장치, 레지스터 등으로 구성되어 있다.
- Main Memory : 프로그램이나 데이터를 저장하는 공간으로, CPU가 작업을 수행할 때 필요한 명령어나 데이터가 저장되어 있다. 주 메모리인 RAM이 있다.
- Input Device : 사용자가 컴퓨터에 데이터나 명령을 입력하는 장치로, 키보드, 마우스 등이 있다.
- Output Device : 컴퓨터에서 처리한 결과를 사용자에게 보여주는 장치로, 모니터, 스피커 등이 있다.
- Storage (Secondary Memory) : 데이터를 임시적, 반영구적으로 저장하는 장치로, 하드 디스크 드라이브, SSD, USB 플래시 드라이브 등이 있다.
- Bus : 컴퓨터 내부에서 데이터와 명령어를 전송하는 통로이다. 버스는 데이터 버스, 주소 버스, 제어 버스 등으로 나뉘어져 있다.
- (Mother)Board : CPU, 메모리, 버스 등의 하드웨어 요소들이 담겨 있는 회로 기판이다. 보드는 다양한 부품들이 부착되어 있어서 시스템의 기능과 성능을 결정한다.
Von Neumann Architecture
폰 노이만 아키텍처는 컴퓨터의 기본적인 구조 중 하나로, 프로그램과 데이터가 동일한 메모리 공간에서 저장되고 처리되는 방식을 가진다. 대부분의 일반적인 컴퓨터 시스템은 이 아키텍처를 기반으로 설계되었다.
Instruction Cycle
Instruction Cycle은 CPU가 메모리에서 명령어를 가져와 실행하고, 결과를 저장하기까지의 연속적인 과정이다. 이 과정은 CPU가 프로그램을 실행하는 기본 원리로 5단계로 구성된다.
- Fetch (인출) : 메모리에서 다음에 실행할 명령어를 가져온다. CPU는 메모리 주소 버스를 통해 메모리 주소를 지정하고, 데이터 버스를 통해 명령어(1 Word)를 인출한다. 1 Word는 CPU가 처리하는 최소 정보처리 단위를 말한다.
- Decode (해독) : 인출된 명령어를 해독하여 명령어가 어떤 작업을 수행해야 하는지 결정한다. 오퍼랜드(operand)와 연산자(operator)를 구분하고 해당 명령어에 필요한 레지스터나 메모리 주소 등을 결정한다.
- Execute (실행) : CPU가 결정된 명령어를 실행한다.
- Memory (Access, 메모리 접근) : CPU가 메모리에 접근하여 데이터를 읽거나 쓸 수 있다. 이때, CPU는 결과를 저장할 위치를 결정하고, 데이터 버스를 통해 결과를 저장한다.
- Write Back (결과 저장) : 실행 결과를 레지스터(Register)에 저장한다. 이때, CPU는 결과를 저장할 위치를 결정하고, 데이터 버스를 통해 결과를 저장한다.
Architecture of Operating System
운영체제는 총 4가지의 구성요소로 이루어져 있다.
- Kernel : 컴퓨터 자원에 대한 핵심 관리 기능을 담당. 프로세스 관리, 메모리 관리, 저장공간 관리, 장치 관리 등 컴퓨터에 속한 자원들에 대한 접근을 중재.
- Interface : 사용자와 운영체제 간의 소통 창구 역할. 사용자가 명령을 입력하면 이를 운영체제가 처리하고 결과를 반환.운영체제가 제공하는 대표적인 인터페이스는 GUI(Graphical User Interface), CLI(Command Line Interface)가 있음.
- System Call : 사용자 프로그램과 커널 간의 중간 다리 역할. 사용자나 프로그램이 직접적으로 컴퓨터 자원에 접근하는 것을 막고 커널을 보호하기 위해 만든 코드 집합.
- Driver : 하드웨어 장치와 운영체제 간의 통신을 가능하게 하는 소프트웨어. 프린터 드라이버, 키보드 드라이버 등이 있음.
운영체제는 안정적이고 효율적인 시스템 동작을 보장하기 위해 사용자 또는 응용프로그램이 직접 하드웨어에 접근하는 것을 제한한다. 이를 위해 User Mode와 Kernel Mode라는 두 가지 실행 모드를 사용하며 각 모드는 역할과 권한이 다르다. User Mode와 Kernel Mode 사이는 System Call과 Interrupt을 통해서 전환된다.
Process States and Transitions
Process는 Job이나 Task라고도 불리며 PCB(Process Control Block)이라는 걸로 process들을 관리한다.
- new : 새롭게 생성된 프로세스
- ready : CPU에서 실행되기를 기다리는 상태
- running : CPU를 할당받아 명령어를 실행 중인 상태
- waiting : I/O 작업이나 scheduling에 의한 대기 상태
- terminated : 프로세스가 실행을 완료한 상태
Context Switching
Context Switching은 운영체제가 프로세스를 종료하거나 Scheduling에 의해서 종료할 때 발생한다. 이전의 프로세스 상태를 저장하고 새로운 프로세스의 PCB를 가져오는 역할을 한다. 하지만 오버헤드가 심하다는 단점이 있으며 이를 최소화하기 위해 스케줄링 알고리즘 최적화와 효율적인 자원 관리가 필요하다.
Threads and Multi-Threading
Thread는 하나의 프로세스 내에서 실행 흐름의 단위를 의미하며 프로세스와 하위 프로세스로 이해할 수 있다. Thread는 각자의 레지스터 상태(registe state)와 스택(stack)을 가지고 있다. CPU 스케줄링의 기본 단위이며 하나의 process는 하나 이상의 thread를 가지고 있다. Process는 프로세스 간 전환에는 PCB에 접근해서 Process address space 복사 등으로 인해 overhead가 크지만 thread는 creation과 switching에 드는 시간이 적다는 장점이 있다. Multi-Threading은 하나의 프로세스 내에서 여러 개의 thread를 만들어 동시성(concurrency)을 추구하는 기법이다. Multi-Processing은 두 개 이상의 프로세서(처리장치)가 협력적으로 작업을 동시에 처리하는 방식이다.
Scheduling Algorithms
Scheduling Algorithms은 CPU와 같은 컴퓨터 자원을 프로세스에 할당하는 방식을 결정하는 운영체제의 핵심 기능 중 하나이다. 스케줄링 방식은 크게 두 가지로 나뉜다.
- Non-Preemptive Scheduling (비선점형 스케줄링) : 프로세스가 자원을 반납하기 전까지 다른 프로세스가 해당 자원을 사용할 수 없음. 실행 시간이 긴 프로세스가 자원을 점유하면 다른 프로세스들이 자원을 할당받지 못하는 기아 상태(Starvation)에 빠질 수 있음. ex) FCFS, SJF, Priority Scheduling
- Preemptive Scheduling (선점형 스케줄링) : 프로세스는 제한된 시간(Time Quantum) 동안만 자원을 할당받고, 우선순위에 따라 언제든지 자원이 회수될 수 있음. 우선순위가 높은 프로세스가 도착하면, 현재 실행 중인 프로세스는 중단되고 자원을 반환.우선순위가 낮은 프로세스는 기아 상태에 빠질 수 있음. ex) Round-Robin, Multilevel Queue Scheduling
- FCFS(First Come, First Served) Scheduling :선착순 스케줄링으로, 먼저 도착한 프로세스를 먼저 실행하는 가장 단순하고 직관적인 방식. 평균 대기 시간이 길어질 가능성 높음.
- SJF(Shortest Job First) Scheduling : 실행 시간이 가장 짧은 프로세스를 다음 실행 대상으로 선택하는 방식. Ready Queue에 프로세스를 새로 삽입할 때 CPU Burst Time이 가장 짧은 것을 다음 프로세스로 지정할 수 있도록 제일 앞에 삽입. 짧은 작업이 계속 도착하면 긴 작업은 기아 상태에 빠질 수 있음. 비선점형 방식뿐만 아니라 선점형 방식으로도 이용.
- RR(Round-Robin) Scheduling : 각 프로세스에 동일한 시간 할당량(Time Quantum) 동안 CPU 자원을 차례대로 배정하는 방식. Time Quantum이 너무 길면 FCFS와 동일하게 작동하고, 너무 짧으면 Overhead가 생김. 따라서 적절한 Time Quantum을 찾는 것이 중요함. Response Time 줄이고 싶을 때 사용.
당연히 여기에서 정리한 내용 외에도 정말 정말 많은 내용들을 배웠다. 강의를 듣는 동안 '이게 개론이라니...'라는 생각이 여러 번 들었다. 전공자들의 공부량은 상상 이상일 것 같다. 또한, 강사님께서 컴퓨터와 관련된 다양한 지식을 설명해 주셨는데, 이 분야에서 배워야 할 것이 정말 많다는 걸 실감했다. 앞서 들었던 통계 수업처럼 한번에 많은 내용을 배우다보니 다소 부담스럽지만 조금씩이라도 꾸준히 복습하며 새로운 내용을 차근차근 쌓아가야겠다.