운영체제 1주차
운영체제 섹션1
1. 운영체제가 하는 일
운영체제(OS, Operating System)는 하드웨어와 소프트웨어 자원을 효율적으로 관리하고 사용자에게 서비스를 제공하는 소프트웨어 계층입니다.
프로세스 관리
여러 프로그램(프로세스)을 동시에 실행하도록 스케줄링(우선순위 관리 등)
프로세스 간 문맥 교환(Context Switching) 및 자원 할당
메모리 관리
프로세스에 필요한 메모리 공간을 할당/회수, 가상 메모리 등으로 메모리 활용을 극대화
하드웨어 관리
CPU, 입출력 장치(I/O), 저장장치 등의 자원 할당
디바이스 드라이버를 통해 하드웨어와 통신 중재
파일 시스템 관리
디스크 등에 파일을 읽고, 쓰고, 삭제 등 수행
2. 운영체제의 구조
커널(Kernel)
운영체제의 핵심 영역으로, 프로세스 관리, 메모리 관리, 저장장치 관리 등 주요 기능 담당
인터페이스를 통한 커널 접근
GUI: 아이콘, 창, 마우스 등 그래픽 요소로 소통
CLI: 터미널에 명령어로 소통 (예: Bash, Powershell 등)
시스템 콜(System Call)
사용자 프로그램(애플리케이션)이 OS 커널 서비스를 요청하는 인터페이스
예)
open(),fork(),socket()등(코드 예시)
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd = open("example.txt", O_RDONLY); // 시스템 콜을 통해 파일 오픈 if (fd < 0) { // 에러 처리 } // 파일 읽기, 처리 등... close(fd); return 0; }
하드웨어 드라이버
OS와 실제 하드웨어를 연결하는 소프트웨어
새 하드웨어 인식이나 장치 제어 시 필요
3. 폰 노이만 구조 (Von Neumann Architecture)
탄생 배경
프로그램마다 하드웨어 배선을 일일이 교체하던 비효율을 개선하고자 등장
CPU와 메모리가 버스로 연결, 프로그램과 데이터를 같은 메모리에 저장
구성 요소
CPU: 산술 논리 연산장치(ALU), 제어 장치(CU), 레지스터
메모리(RAM, ROM), 버스(데이터/주소/제어 버스)
프로그램 내장 방식
프로그램(기계어 코드)을 메모리에 올린 뒤 CPU가 순차적으로 명령어를 읽어 실행
4. CPU의 처리 방식
폴링(Polling)
CPU가 주기적으로 I/O 완료 여부를 확인하는 방식
(코드 예시 / 의사코드)
while (1) { if (device_status == READY) { // 처리 로직 } // 다른 작업 }비효율적일 수 있음
인터럽트(Interrupt)
I/O가 완료되면 하드웨어가 CPU에 신호(Interrupt Request) 전송 -> CPU가 즉시 처리 루틴으로 이동
운영체제 섹션2 : 프로세스와 쓰레드
1. 프로그램과 프로세스
프로그램: 저장장치에 있는 명령어 집합 (정적)
프로세스: 메모리에 적재되어 실행 중인 프로그램 (동적)
프로세스 구조
코드 영역: 실행할 명령어
데이터 영역: 전역 변수, 정적 변수
스택 영역: 함수 호출 시 지역 변수, 반환 주소 등
힙 영역: 동적 메모리 할당 영역
코드가 프로세스가 되는 과정
전처리 → 컴파일 → 링킹 → 실행 파일 생성 → 메모리에 적재 → 프로세스
2. 멀티프로그래밍과 멀티프로세싱
멀티프로그래밍(Multiprogramming)
메모리에 여러 프로그램을 동시에 올려두고, CPU가 I/O 대기 시 다른 프로그램 실행
멀티태스킹(Multitasking)
CPU 시간을 쪼개서 여러 프로세스를 빠르게 교차 실행 -> 동시에 돌아가는 것처럼 보임
멀티프로세싱(Multiprocessing)
여러 CPU(코어)가 각각 다른 프로세스를 병렬로 처리
3. PCB (Process Control Block)
정의
프로세스를 관리하기 위해 운영체제가 사용하는 자료구조
프로세스 식별자, 상태, 레지스터 값, 메모리 정보 등 포함
(ASCII 다이어그램 예시)
+-----------------------------------+ | Process ID (PID) = 1234 | | State = RUNNING | | Program Counter = 0x100057 | | CPU Registers = [... ] | | Memory Info (Base, Limit ...) | | Open File List, etc. | +-----------------------------------+PCB 관리
프로세스 생성 시 PCB를 만들고, 프로세스 종료 시 PCB를 제거
4. 프로세스의 상태
생성(New) → 준비(Ready) → 실행(Running) → 대기(Waiting) → 완료(Terminated)
+-----+ | New | (프로세스 생성 상태) +-----+ | | (OS가 프로세스를 준비 큐로 편입) v +--------+ (준비 상태; CPU 할당 대기) | Ready | +--------+ | | (CPU 스케줄러가 프로세스에게 CPU 할당) v +--------+ (실행 상태; CPU를 점유 중) |Running | +--------+ | \ | \ (입출력 또는 이벤트가 필요) | \ | v | +--------+ (대기 상태; I/O 완료 등 이벤트를 기다림) | |Waiting | | +--------+ | | | | (I/O 혹은 이벤트 완료 시) | v | +--------+ | | Ready | (다시 CPU 할당 대기 상태로 복귀) | +--------+ | | (프로세스 실행이 종료될 때) v +-----------+ (완료 상태; 프로세스 종료) |Terminated | +-----------+
5. 컨텍스트 스위칭 (Context Switching)
현재 실행 중인 프로세스의 레지스터, PC 등을 PCB에 저장
새롭게 실행할 프로세스의 PCB 정보를 불러와 레지스터, PC 설정
빈번하면 오버헤드(시간 낭비) 증가
// 단순 의사코드
save_state_of(curr_process); // 레지스터, PC -> curr_process.PCB
curr_process.state = READY;
curr_process = next_process_from_ready_queue();
load_state_of(curr_process); // 레지스터, PC <- next_process.PCB
curr_process.state = RUNNING;
6. 프로세스의 생성과 종료
프로세스 생성
리눅스의 경우
fork()시스템 콜로 부모 프로세스를 복제#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid == 0) { // 자식 프로세스 printf("Child Process: PID=%d\n", getpid()); } else { // 부모 프로세스 printf("Parent Process: PID=%d, Child PID=%d\n", getpid(), pid); } return 0; }
좀비 프로세스(Zombie)
자식이 종료되었지만 부모가
wait()로 회수하지 않아 PCB가 남아있는 상태
7. 쓰레드(Thread)
등장 배경
탭마다 프로세스를 새로 만들면 자원 낭비
하나의 프로세스 내 여러 실행 흐름(쓰레드)을 사용
공유 자원
같은 프로세스 내부의 쓰레드들은 코드, 데이터, 힙을 공유하지만 스택은 개별
#include <pthread.h> #include <stdio.h> #include <stdlib.h> void* thread_func(void* arg) { printf("Thread %ld is running!\n", (long)arg); return NULL; } int main() { pthread_t threads[2]; for (long i = 0; i < 2; i++) { pthread_create(&threads[i], NULL, thread_func, (void*)i); } for (int i = 0; i < 2; i++) { pthread_join(threads[i], NULL); } return 0; }프로세스 vs 쓰레드
안정성: 프로세스는 서로 독립, 쓰레드는 한 프로세스 내부 공유
속도/자원: 프로세스 생성/소멸 오버헤드 > 쓰레드 생성/소멸 오버헤드
운영체제 섹션3 : CPU 스케줄링
1. 스케줄링의 개념
CPU가 여러 프로세스를 언제, 어떻게 실행할지 결정하는 정책
목표: CPU 활용도 극대화, 공평성, 처리량 극대화, 대기/응답시간 최소화
2. 다중큐 (Multi-Queue)
여러 개의 대기열(Queue)을 두고, 우선순위 또는 도착 순서에 따라 프로세스를 배치
(이미지 예시)
여러 줄(큐)에 프로세스가 나누어져 있는 그림
예시 이미지 링크
3. 대표적인 CPU 스케줄링 알고리즘
FIFO (First In First Out)
도착 순서대로 CPU 할당
구현은 단순하지만, 긴 작업이 뒤의 작업들을 오래 기다리게 할 수 있음
SJF (Shortest Job First)
실행 시간이 짧은 작업부터 먼저 처리
실제 실행 시간을 미리 알기 어려우며, 긴 작업이 계속 밀릴 위험(Starvation)
RR (Round Robin)
Time Slice(할당 시간)만큼 CPU를 할당, 시간 만료 시 다음 프로세스로 전환
while (ready_queue is not empty) { process = dequeue(ready_queue) run(process, TIME_SLICE) if (process not finished) { enqueue(ready_queue, process) } }
MLFQ (Multi-Level Feedback Queue)
여러 단계(레벨)의 큐를 두고, CPU 사용량에 따라 우선순위를 변경
I/O 바운드라면 높은 우선순위 유지, CPU 바운드라면 우선순위가 점점 낮아짐