운영체제 완전 정복

프로세스 관리, CPU 스케줄링, 동기화, 메모리 관리, 파일 시스템까지 운영체제의 핵심 개념을 체계적으로 학습합니다. 컴퓨터공학 전공자, GATE 시험 준비생, 시스템 프로그래머를 위한 필수 과정입니다.

Operating System,CS,컴퓨터공학중급
20시간
16개 항목
학습 진행률0 / 16 (0%)

학습 항목

1. Operating System
초급
운영체제 소개 및 역할 완벽 가이드
2. Operating System
초급
운영체제 유형 완벽 가이드
3. Operating System
초급
사용자 모드와 커널 모드 완벽 가이드
4. Operating System
초급
프로세스 개념과 구조 완벽 가이드
5. Operating System
초급
프로세스 상태 전이 완벽 가이드
6. Operating System
초급
CPU 스케줄링 기초 완벽 가이드
7. Operating System
초급
CPU 스케줄링 알고리즘 완벽 가이드 (1)
8. Operating System
초급
CPU 스케줄링 알고리즘 (2)
9. Operating System
초급
프로세스 동기화 완벽 가이드
10. Operating System
초급
동기화 도구 완벽 가이드
11. Operating System
초급
교착 상태 (Deadlock) 완벽 가이드
12. Operating System
초급
메모리 관리 기초 완벽 가이드
13. Operating System
초급
페이징과 세그멘테이션 완벽 가이드
14. Operating System
초급
가상 메모리 완벽 가이드
15. Operating System
초급
파일 시스템 완벽 가이드
16. Operating System
초급
디스크 스케줄링 완벽 가이드
1 / 16
🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

이미지 로딩 중...

운영체제 소개 및 역할 완벽 가이드 - 슬라이드 1/7
상세 보기

운영체제 소개 및 역할 완벽 가이드

컴퓨터의 핵심 소프트웨어인 운영체제가 무엇인지, 어떤 역할을 하는지 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 폰 노이만 아키텍처부터 운영체제의 발전 역사까지 체계적으로 다룹니다.


목차

  1. 운영체제란_무엇인가
  2. 운영체제의_역할과_목적
  3. 폰_노이만_아키텍처
  4. 메인_메모리의_중요성
  5. 하드웨어와_소프트웨어_인터페이스
  6. 운영체제의_발전_역사

1. 운영체제란 무엇인가

어느 날 김개발 씨가 새 노트북을 샀습니다. 전원 버튼을 누르자 화면에 Windows 로고가 나타났습니다.

"운영체제가 뭐길래 컴퓨터를 켜면 항상 먼저 나타나는 걸까?" 문득 궁금해졌습니다.

**운영체제(Operating System)**는 한마디로 컴퓨터 하드웨어와 사용자 사이를 연결해주는 핵심 소프트웨어입니다. 마치 호텔의 프론트 데스크 직원이 투숙객과 호텔 시설 사이를 연결해주는 것과 같습니다.

운영체제가 없다면 우리는 복잡한 하드웨어를 직접 다뤄야 하는데, 이것은 일반 사용자에게 거의 불가능한 일입니다.

다음 코드를 살펴봅시다.

import os
import platform

# 현재 운영체제 정보 확인하기
os_name = os.name  # 'posix' (Linux/Mac) 또는 'nt' (Windows)
system_info = platform.system()  # 'Linux', 'Windows', 'Darwin' 등

# 운영체제가 제공하는 기본 기능 활용
current_directory = os.getcwd()  # 현재 작업 디렉토리 조회
file_list = os.listdir('.')  # 디렉토리 내 파일 목록 조회

# 운영체제를 통해 환경 변수 접근
home_path = os.environ.get('HOME', os.environ.get('USERPROFILE'))

print(f"운영체제: {system_info}")
print(f"현재 경로: {current_directory}")
print(f"홈 디렉토리: {home_path}")

김개발 씨는 입사 첫날, 개발 환경을 설정하느라 정신이 없었습니다. 터미널을 열고 명령어를 입력하고, 파일을 만들고, 프로그램을 설치했습니다.

그런데 문득 이런 생각이 들었습니다. "내가 지금 하는 이 모든 작업은 누가 처리해주는 걸까?" 선배 개발자 박시니어 씨가 커피를 건네며 말했습니다.

"지금 자네가 하는 모든 작업은 운영체제가 뒤에서 처리해주고 있는 거야. 운영체제가 없으면 컴퓨터는 그냥 비싼 철덩어리에 불과해." 그렇다면 운영체제란 정확히 무엇일까요?

쉽게 비유하자면, 운영체제는 마치 대형 호텔의 프론트 데스크와 같습니다. 투숙객이 방을 예약하고, 청소를 요청하고, 식당을 이용하려면 항상 프론트 데스크를 거쳐야 합니다.

프론트 데스크 직원은 투숙객의 요청을 받아 적절한 부서에 전달하고, 호텔의 모든 자원이 효율적으로 사용되도록 관리합니다. 운영체제도 마찬가지입니다.

사용자나 프로그램이 컴퓨터 자원을 사용하려면 반드시 운영체제를 통해야 합니다. 운영체제가 없던 초창기 컴퓨터 시절에는 어땠을까요?

프로그래머들은 기계어로 직접 하드웨어를 제어해야 했습니다. 데이터를 저장하려면 하드디스크의 어느 섹터에 어떻게 기록할지 일일이 지정해야 했고, 키보드 입력을 받으려면 키보드 컨트롤러와 직접 통신해야 했습니다.

한 번에 하나의 프로그램만 실행할 수 있었고, 그마저도 실행이 끝나면 다음 프로그램을 위해 컴퓨터를 다시 설정해야 했습니다. 바로 이런 불편함을 해결하기 위해 운영체제가 등장했습니다.

운영체제는 하드웨어 추상화라는 마법을 부립니다. 복잡한 하드웨어 동작을 감추고, 프로그래머에게 단순하고 일관된 인터페이스를 제공합니다.

덕분에 우리는 파일을 저장할 때 하드디스크의 물리적 구조를 알 필요가 없습니다. 그냥 "저장" 버튼만 누르면 됩니다.

위의 코드를 살펴보겠습니다. 우리는 단 몇 줄의 코드로 현재 운영체제 정보를 확인하고, 파일 시스템에 접근하고, 환경 변수를 읽어올 수 있습니다.

이 모든 것이 가능한 이유는 운영체제가 복잡한 하드웨어 작업을 대신 처리해주기 때문입니다. 실제 현업에서 운영체제의 역할은 더욱 중요합니다.

웹 서버를 운영한다고 가정해봅시다. 수천 명의 사용자가 동시에 접속해도 서버가 정상적으로 동작하는 것은 운영체제가 CPU 시간과 메모리를 효율적으로 배분해주기 때문입니다.

하지만 주의할 점도 있습니다. 개발자가 운영체제의 특성을 이해하지 못하면 예상치 못한 문제를 만날 수 있습니다.

예를 들어 Windows에서 개발한 프로그램이 Linux에서 다르게 동작하는 경우가 종종 있습니다. 파일 경로 구분자가 다르고, 줄바꿈 문자가 다르고, 파일 시스템의 대소문자 구분 방식도 다르기 때문입니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"그래서 개발할 때 운영체제를 고려해야 하는 거군요!" 운영체제를 이해하면 더 안정적이고 호환성 높은 프로그램을 만들 수 있습니다.

실전 팁

💡 - 개발 시 대상 운영체제의 특성을 미리 파악하고 시작하세요

  • 운영체제에 독립적인 코드를 작성하려면 os 모듈 같은 추상화 계층을 활용하세요
  • 서버 개발자라면 Linux 운영체제에 익숙해지는 것이 필수입니다

2. 운영체제의 역할과 목적

김개발 씨가 회사에서 처음으로 서버 관리를 맡게 되었습니다. 서버 모니터링 화면을 보니 CPU 사용률, 메모리 사용량, 디스크 I/O 등 복잡한 지표들이 가득했습니다.

"이 모든 걸 누가 관리하는 거지?" 갑자기 막막해졌습니다.

운영체제의 핵심 역할은 **자원 관리자(Resource Manager)**와 확장된 기계(Extended Machine) 두 가지입니다. 마치 회사의 총무팀이 사무용품을 배분하고, 회의실을 예약해주고, 각종 편의시설을 관리하는 것과 같습니다.

운영체제는 CPU, 메모리, 저장장치, 입출력 장치 등 모든 하드웨어 자원을 효율적으로 관리하며, 프로그램들이 이 자원을 안전하게 사용할 수 있도록 보장합니다.

다음 코드를 살펴봅시다.

import psutil
import os

# 운영체제의 자원 관리 역할 확인하기

# 1. CPU 관리 - 프로세스들에게 CPU 시간 배분
cpu_percent = psutil.cpu_percent(interval=1)
cpu_count = psutil.cpu_count()

# 2. 메모리 관리 - 프로그램들에게 메모리 공간 할당
memory = psutil.virtual_memory()
memory_used_gb = memory.used / (1024 ** 3)
memory_total_gb = memory.total / (1024 ** 3)

# 3. 프로세스 관리 - 현재 실행 중인 프로세스 수
process_count = len(psutil.pids())

# 4. 파일 시스템 관리 - 디스크 사용 현황
disk = psutil.disk_usage('/')

print(f"CPU 사용률: {cpu_percent}% (코어 {cpu_count}개)")
print(f"메모리: {memory_used_gb:.1f}GB / {memory_total_gb:.1f}GB")
print(f"실행 중인 프로세스: {process_count}개")
print(f"디스크: {disk.percent}% 사용 중")

김개발 씨는 서버 모니터링 화면 앞에서 한숨을 쉬었습니다. 수십 개의 프로세스가 동시에 실행되고 있는데, 어떻게 이 모든 것이 충돌 없이 돌아가는 걸까요?

박시니어 씨가 옆에 앉으며 설명을 시작했습니다. "운영체제를 이해하려면 먼저 그 역할을 알아야 해.

크게 두 가지로 나눌 수 있어." 첫 번째 역할은 자원 관리자입니다. 컴퓨터에는 여러 가지 자원이 있습니다.

CPU, 메모리, 하드디스크, 네트워크 카드 등이 대표적입니다. 문제는 이 자원들이 한정되어 있다는 것입니다.

여러 프로그램이 동시에 CPU를 사용하고 싶어 하고, 메모리 공간을 차지하려 합니다. 누군가 이 자원들을 공정하게 배분해야 합니다.

마치 회사의 회의실 예약 시스템과 같습니다. 회의실은 몇 개 안 되는데 여러 팀이 사용하고 싶어 합니다.

예약 시스템이 없다면 회의실을 두고 매일 다툼이 벌어질 것입니다. 운영체제는 바로 이 예약 시스템의 역할을 합니다.

어떤 프로그램이 언제 얼마나 CPU를 사용할지, 메모리의 어느 영역을 차지할지 결정합니다. 두 번째 역할은 확장된 기계입니다.

하드웨어를 직접 다루는 것은 굉장히 복잡합니다. 하드디스크에 데이터를 저장하려면 디스크 컨트롤러에 명령을 보내고, 헤드를 이동시키고, 데이터를 기록하고, 오류를 검사해야 합니다.

프로그래머가 이런 작업을 매번 직접 한다면 정작 본업인 비즈니스 로직 개발은 언제 할까요? 운영체제는 이런 복잡한 하드웨어 동작을 감추고, 프로그래머에게 단순한 인터페이스를 제공합니다.

파일을 저장하고 싶으면 그냥 "write" 함수를 호출하면 됩니다. 나머지는 운영체제가 알아서 처리합니다.

마치 자동차 운전자가 엔진의 내부 동작을 몰라도 액셀을 밟으면 차가 나아가는 것처럼요. 위의 코드에서 psutil 라이브러리를 통해 시스템 정보를 조회하고 있습니다.

이 간단한 코드 뒤에서 운영체제는 CPU 레지스터를 읽고, 메모리 매핑 테이블을 조회하고, 프로세스 테이블을 스캔하는 복잡한 작업을 수행합니다. 하지만 우리는 그런 세부사항을 알 필요가 없습니다.

운영체제의 또 다른 중요한 목적은 보호와 보안입니다. 한 프로그램이 다른 프로그램의 메모리 영역을 침범하면 안 됩니다.

일반 사용자가 시스템 파일을 마음대로 삭제하면 안 됩니다. 운영체제는 각 프로그램과 사용자에게 적절한 권한을 부여하고, 그 권한을 벗어나는 행동을 차단합니다.

실제로 서버를 운영하다 보면 운영체제의 역할이 얼마나 중요한지 실감하게 됩니다. 메모리 누수가 발생하면 운영체제가 해당 프로세스를 종료시켜 시스템 전체가 다운되는 것을 막습니다.

디스크 공간이 부족해지면 운영체제가 경고를 보냅니다. 김개발 씨는 이제 서버 모니터링 화면이 다르게 보이기 시작했습니다.

"이 모든 숫자들이 운영체제가 자원을 관리하고 있다는 증거군요!" 박시니어 씨가 웃으며 말했습니다. "맞아.

좋은 개발자가 되려면 운영체제가 어떻게 일하는지 이해해야 해."

실전 팁

💡 - 서버 모니터링 시 CPU, 메모리, 디스크 I/O를 함께 살펴보세요

  • 프로세스가 비정상적으로 자원을 많이 사용하면 메모리 누수나 무한 루프를 의심하세요
  • top, htop, Task Manager 같은 도구로 운영체제의 자원 관리 현황을 확인할 수 있습니다

3. 폰 노이만 아키텍처

김개발 씨가 컴퓨터 구조 책을 펼쳤습니다. 첫 장에 "폰 노이만 아키텍처"라는 용어가 나왔습니다.

"1940년대에 만들어진 구조가 아직도 쓰인다고?" 김개발 씨는 의아했습니다. 대체 이 아키텍처가 뭐길래 80년이 넘도록 컴퓨터의 기본 구조로 남아있는 걸까요?

폰 노이만 아키텍처는 프로그램과 데이터를 같은 메모리에 저장하고, CPU가 순차적으로 명령어를 가져와 실행하는 컴퓨터 구조입니다. 마치 요리사가 레시피(프로그램)와 재료(데이터)를 같은 주방(메모리)에 두고, 레시피를 한 줄씩 읽으며 요리하는 것과 같습니다.

이 단순하면서도 강력한 구조는 현대 컴퓨터의 근간이 되었습니다.

다음 코드를 살펴봅시다.

# 폰 노이만 아키텍처 시뮬레이션
class VonNeumannComputer:
    def __init__(self):
        # 메모리: 프로그램과 데이터를 함께 저장
        self.memory = [0] * 256
        # CPU 레지스터
        self.accumulator = 0  # 연산 결과 저장
        self.program_counter = 0  # 다음 실행할 명령어 주소

    def load_program(self, program, start_address=0):
        # 프로그램을 메모리에 적재 (저장 프로그램 개념)
        for i, instruction in enumerate(program):
            self.memory[start_address + i] = instruction

    def fetch(self):
        # 명령어 인출: 메모리에서 명령어를 가져옴
        instruction = self.memory[self.program_counter]
        self.program_counter += 1
        return instruction

    def execute(self, instruction):
        # 명령어 실행: 명령어를 해석하고 수행
        opcode = instruction // 100  # 연산 코드
        operand = instruction % 100  # 피연산자 주소

        if opcode == 1:  # LOAD: 메모리에서 값 로드
            self.accumulator = self.memory[operand]
        elif opcode == 2:  # ADD: 덧셈
            self.accumulator += self.memory[operand]
        elif opcode == 3:  # STORE: 메모리에 저장
            self.memory[operand] = self.accumulator

김개발 씨는 컴퓨터 역사 책을 읽다가 존 폰 노이만이라는 수학자를 알게 되었습니다. 1945년, 이 천재 수학자는 컴퓨터 설계에 관한 혁명적인 아이디어를 제시했습니다.

폰 노이만 이전의 컴퓨터는 어땠을까요? 초기 컴퓨터인 ENIAC은 프로그램을 변경하려면 전선을 뽑았다 꽂았다 해야 했습니다.

마치 전화 교환원이 전화선을 연결하듯이요. 새로운 계산을 하려면 며칠씩 걸려 배선을 다시 해야 했습니다.

프로그램이 하드웨어에 고정되어 있었던 것입니다. 폰 노이만은 획기적인 아이디어를 냈습니다.

"프로그램도 데이터처럼 메모리에 저장하면 어떨까?" 이것이 바로 저장 프로그램(Stored Program) 개념입니다. 프로그램 명령어를 메모리에 저장하면, 새 프로그램을 실행할 때 배선을 바꿀 필요가 없습니다.

그냥 메모리에 새 프로그램을 로드하면 됩니다. 이것은 정말 혁명적인 발상이었습니다.

폰 노이만 아키텍처는 크게 네 가지 구성요소로 이루어집니다. 첫째, **중앙처리장치(CPU)**입니다.

연산을 수행하고 명령어를 해석하는 컴퓨터의 두뇌입니다. 둘째, 메모리입니다.

프로그램과 데이터를 저장하는 공간입니다. 셋째, 입력장치입니다.

키보드나 마우스처럼 외부에서 데이터를 받아들입니다. 넷째, 출력장치입니다.

모니터나 프린터처럼 결과를 보여줍니다. CPU의 동작 방식도 이해해야 합니다.

CPU는 인출-해석-실행 사이클을 반복합니다. 먼저 메모리에서 명령어를 **인출(Fetch)**합니다.

프로그램 카운터가 가리키는 주소에서 명령어를 가져옵니다. 그다음 명령어를 **해석(Decode)**합니다.

이 명령어가 덧셈인지, 저장인지, 분기인지 파악합니다. 마지막으로 명령어를 **실행(Execute)**합니다.

실제 연산을 수행합니다. 위의 코드는 이 과정을 단순화해서 보여줍니다.

memory 배열에 프로그램과 데이터가 함께 저장되고, fetch 메서드가 명령어를 가져오고, execute 메서드가 실행합니다. 하지만 폰 노이만 아키텍처에는 한계도 있습니다.

바로 **폰 노이만 병목(Von Neumann Bottleneck)**입니다. CPU와 메모리 사이에 단 하나의 버스만 있어서, CPU가 아무리 빨라도 메모리에서 데이터를 가져오는 속도에 제한됩니다.

마치 8차선 고속도로가 1차선 좁은 길로 합쳐지는 것과 같습니다. 이 문제를 해결하기 위해 현대 컴퓨터는 캐시 메모리, 파이프라이닝, 병렬 처리 등 다양한 기술을 사용합니다.

하지만 기본 구조는 여전히 폰 노이만 아키텍처를 따릅니다. 김개발 씨는 책을 덮으며 생각했습니다.

"80년 전 아이디어가 지금도 쓰인다니, 정말 대단한 발명이구나." 좋은 아키텍처는 시간이 지나도 그 가치를 잃지 않습니다.

실전 팁

💡 - 프로그램이 메모리에 로드되어 실행된다는 점을 항상 기억하세요

  • 메모리 접근 속도가 프로그램 성능에 큰 영향을 미칩니다
  • CPU 캐시의 원리를 이해하면 더 빠른 코드를 작성할 수 있습니다

4. 메인 메모리의 중요성

김개발 씨의 컴퓨터가 갑자기 느려졌습니다. 크롬 탭을 20개쯤 열어두고 VS Code로 개발하고 있었는데, 마우스 커서조차 버벅거리기 시작했습니다.

작업 관리자를 열어보니 메모리 사용량이 95%였습니다. "메모리가 대체 뭐길래 이렇게 중요한 거지?"

**메인 메모리(RAM)**는 현재 실행 중인 프로그램과 데이터를 저장하는 고속 저장장치입니다. 마치 책상 위 공간과 같습니다.

책상이 넓으면 여러 책을 펼쳐놓고 동시에 참고할 수 있지만, 좁으면 책을 자주 치우고 꺼내야 합니다. 메모리가 부족하면 컴퓨터는 하드디스크를 메모리 대신 사용하는데, 이것이 바로 시스템이 느려지는 원인입니다.

다음 코드를 살펴봅시다.

import sys

# 메모리 사용량 확인하기
small_list = [1, 2, 3, 4, 5]
large_list = list(range(1000000))  # 백만 개의 정수

# 객체의 메모리 사용량 측정 (바이트 단위)
small_size = sys.getsizeof(small_list)
large_size = sys.getsizeof(large_list)

print(f"작은 리스트 크기: {small_size} bytes")
print(f"큰 리스트 크기: {large_size:,} bytes ({large_size/1024/1024:.1f} MB)")

# 메모리 효율적인 방법: 제너레이터 사용
def number_generator(n):
    # 한 번에 하나씩만 메모리에 로드
    for i in range(n):
        yield i

# 제너레이터는 전체 데이터를 메모리에 올리지 않음
gen = number_generator(1000000)
gen_size = sys.getsizeof(gen)
print(f"제너레이터 크기: {gen_size} bytes")  # 훨씬 작음!

김개발 씨는 새 노트북을 사면서 고민에 빠졌습니다. RAM 8GB 모델과 16GB 모델, 가격 차이가 꽤 났습니다.

"8GB면 충분하지 않을까?" 박시니어 씨에게 물어봤습니다. "개발자라면 무조건 16GB 이상을 추천해.

메모리는 컴퓨터에서 가장 중요한 자원 중 하나거든." 메모리가 왜 그렇게 중요할까요? 이를 이해하려면 메모리 계층 구조를 알아야 합니다.

컴퓨터에는 여러 종류의 저장장치가 있습니다. CPU 내부의 레지스터가 가장 빠르지만 용량이 아주 작습니다.

그 다음이 캐시 메모리, 그 다음이 메인 메모리(RAM), 그리고 가장 느리지만 용량이 큰 것이 하드디스크(또는 SSD)입니다. 비유하자면 이렇습니다.

레지스터는 손에 들고 있는 것, 캐시는 책상 서랍, 메인 메모리는 책장, 하드디스크는 창고입니다. 손에 들고 있는 것은 바로 사용할 수 있지만 많이 들 수 없습니다.

창고에는 많이 보관할 수 있지만 가져오려면 시간이 걸립니다. 프로그램이 실행되려면 반드시 메인 메모리에 로드되어야 합니다.

이것이 폰 노이만 아키텍처의 핵심입니다. 하드디스크에 저장된 프로그램은 그 자체로는 실행될 수 없습니다.

메모리로 복사되어야만 CPU가 명령어를 읽어와 실행할 수 있습니다. 메모리가 부족하면 어떤 일이 벌어질까요?

운영체제는 **가상 메모리(Virtual Memory)**라는 기술을 사용합니다. 물리적 메모리가 부족하면 하드디스크의 일부를 메모리처럼 사용합니다.

Windows에서는 이것을 "페이지 파일", Linux에서는 "스왑 공간"이라고 부릅니다. 문제는 하드디스크가 메모리보다 수백 배 느리다는 것입니다.

SSD라 해도 메모리 속도에는 비교가 안 됩니다. 그래서 가상 메모리를 많이 사용하면, 즉 "스와핑"이 자주 발생하면 컴퓨터가 극도로 느려집니다.

김개발 씨의 컴퓨터가 버벅거린 이유가 바로 이것입니다. 위의 코드에서 메모리 효율성의 중요성을 보여줍니다.

백만 개의 숫자를 리스트로 만들면 수 메가바이트의 메모리를 차지합니다. 하지만 제너레이터를 사용하면 한 번에 하나의 숫자만 메모리에 올리므로 메모리 사용량이 극히 적습니다.

실무에서 메모리 관리는 정말 중요합니다. 서버 애플리케이션에서 메모리 누수가 발생하면 시간이 지남에 따라 메모리 사용량이 계속 증가하다가 결국 시스템이 다운됩니다.

대용량 데이터를 처리할 때 전체를 메모리에 올리면 "Out of Memory" 오류가 발생합니다. 좋은 개발자는 메모리를 의식하며 코드를 작성합니다.

필요 없는 객체는 빨리 해제하고, 대용량 데이터는 스트리밍 방식으로 처리하고, 메모리 프로파일링 도구로 누수를 점검합니다. 김개발 씨는 결국 16GB 모델을 구매했습니다.

개발하면서 IDE, 브라우저, Docker, 데이터베이스를 동시에 돌리니 8GB로는 턱없이 부족했을 것입니다. "메모리에 투자한 건 정말 잘한 선택이었어."

실전 팁

💡 - 대용량 데이터 처리 시 제너레이터나 스트림을 활용하세요

  • 메모리 프로파일러로 정기적으로 메모리 사용 패턴을 점검하세요
  • 개발용 컴퓨터는 최소 16GB RAM을 권장합니다

5. 하드웨어와 소프트웨어 인터페이스

김개발 씨가 파이썬으로 파일을 저장하는 코드를 작성했습니다. "open() 함수 한 번 호출하면 끝인데, 실제로 하드디스크에 어떻게 기록되는 걸까?" 하드웨어는 0과 1밖에 모르는데, 고수준 프로그래밍 언어와 어떻게 소통하는 걸까요?

**시스템 콜(System Call)**은 응용 프로그램이 운영체제의 서비스를 요청하는 인터페이스입니다. 마치 은행 창구와 같습니다.

고객(프로그램)이 돈을 인출하려면 직접 금고에 접근할 수 없고, 반드시 창구 직원(운영체제)을 통해야 합니다. 이렇게 해야 보안이 유지되고, 자원이 안전하게 관리됩니다.

다음 코드를 살펴봅시다.

import os
import ctypes

# 고수준 파일 작업 (내부적으로 시스템 콜 사용)
# Python의 open()은 내부적으로 운영체제의 open 시스템 콜을 호출

# 파일 쓰기 과정 (추상화됨)
with open('/tmp/test.txt', 'w') as f:
    f.write('Hello, OS!')  # 내부적으로 write 시스템 콜 호출

# 저수준 파일 디스크립터 사용
fd = os.open('/tmp/low_level.txt', os.O_CREAT | os.O_WRONLY)
os.write(fd, b'Low level write')  # 직접 시스템 콜에 가까운 인터페이스
os.close(fd)

# 프로세스 관련 시스템 콜
pid = os.getpid()  # getpid 시스템 콜
ppid = os.getppid()  # getppid 시스템 콜

print(f"현재 프로세스 ID: {pid}")
print(f"부모 프로세스 ID: {ppid}")

# 새 프로세스 생성 (fork 시스템 콜)
# child_pid = os.fork()  # Unix 계열에서만 동작

김개발 씨가 간단한 파일 저장 코드를 작성했습니다. 단 세 줄이면 됩니다.

그런데 이 세 줄 뒤에서 무슨 일이 벌어지는지 궁금해졌습니다. 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.

"컴퓨터 시스템은 계층 구조로 되어 있어. 위에서 아래로 내려갈수록 하드웨어에 가까워지지." 가장 위에는 응용 프로그램이 있습니다.

우리가 작성하는 Python, Java 프로그램이 여기에 해당합니다. 그 아래에 라이브러리가 있습니다.

프로그래밍 언어가 제공하는 표준 라이브러리입니다. 그 아래에 운영체제가 있습니다.

그리고 맨 아래에 하드웨어가 있습니다. 여기서 중요한 개념이 **사용자 모드(User Mode)**와 **커널 모드(Kernel Mode)**입니다.

CPU에는 이 두 가지 실행 모드가 있습니다. 일반 프로그램은 사용자 모드에서 실행됩니다.

이 모드에서는 할 수 있는 일이 제한됩니다. 메모리의 특정 영역에 접근하거나 하드웨어를 직접 제어할 수 없습니다.

반면 운영체제 커널은 커널 모드에서 실행됩니다. 이 모드에서는 모든 것이 가능합니다.

왜 이렇게 나누었을까요? 보안과 안정성 때문입니다.

만약 아무 프로그램이나 하드디스크에 직접 쓸 수 있다면, 악성 프로그램이 다른 프로그램의 데이터를 덮어쓸 수 있습니다. 시스템 파일을 망가뜨릴 수도 있습니다.

그렇다면 프로그램이 파일을 저장하려면 어떻게 해야 할까요? 바로 시스템 콜을 사용합니다.

시스템 콜은 사용자 모드에서 커널 모드로 전환하는 유일한 합법적인 방법입니다. 프로그램이 open(), write() 같은 함수를 호출하면, 내부적으로 CPU에 특별한 명령(인터럽트)이 발생합니다.

CPU는 커널 모드로 전환하고, 운영체제 코드가 실행됩니다. 운영체제가 실제 하드웨어 작업을 수행한 후, 다시 사용자 모드로 돌아와 프로그램 실행이 계속됩니다.

위의 코드에서 두 가지 방식으로 파일에 접근하고 있습니다. 첫 번째는 Python의 고수준 open() 함수입니다.

사용하기 편리하지만 내부적으로는 여러 시스템 콜을 호출합니다. 두 번째는 os.open()을 사용한 저수준 방식입니다.

이것은 운영체제의 시스템 콜에 더 가깝습니다. 시스템 콜은 비용이 듭니다.

모드 전환에 시간이 걸리기 때문입니다. 그래서 성능이 중요한 프로그램은 시스템 콜 횟수를 줄이려고 노력합니다.

파일을 읽을 때 한 바이트씩 읽지 않고 버퍼 단위로 읽는 이유가 여기에 있습니다. 김개발 씨는 이제 file.write()가 단순한 함수 호출이 아니라는 것을 알게 되었습니다.

그 뒤에는 사용자 모드에서 커널 모드로의 전환, 운영체제의 파일 시스템 코드 실행, 디스크 드라이버 호출, 실제 하드웨어 동작이라는 복잡한 과정이 숨어 있습니다.

실전 팁

💡 - 시스템 콜 오버헤드를 줄이기 위해 버퍼링을 활용하세요

  • 리눅스에서 strace 명령으로 프로그램의 시스템 콜을 추적할 수 있습니다
  • 성능 크리티컬한 코드에서는 시스템 콜 횟수를 최소화하는 것이 좋습니다

6. 운영체제의 발전 역사

김개발 씨가 유튜브에서 1950년대 컴퓨터 영상을 봤습니다. 방 하나를 가득 채우는 거대한 기계, 수많은 전구와 스위치, 천공 카드를 들고 줄 서 있는 사람들.

"저 시절에는 운영체제가 없었다고?" 도대체 어떻게 컴퓨터를 썼던 걸까요?

운영체제의 역사는 컴퓨터 발전의 역사와 함께합니다. 일괄 처리 시스템에서 시작해 시분할 시스템, 개인용 컴퓨터 운영체제, 그리고 오늘날의 분산 시스템까지 발전해왔습니다.

마치 교통수단이 마차에서 자동차, 비행기로 발전한 것처럼, 운영체제도 시대의 요구에 맞춰 진화해왔습니다.

다음 코드를 살펴봅시다.

# 운영체제 발전 시뮬레이션

# 1세대: 일괄 처리 시스템 (1950년대)
class BatchProcessingOS:
    def __init__(self):
        self.job_queue = []

    def submit_job(self, job):
        # 작업을 큐에 추가, 순서대로 실행
        self.job_queue.append(job)

    def run_all_jobs(self):
        # 모든 작업을 순차적으로 실행
        for job in self.job_queue:
            job.execute()  # 한 작업이 끝나야 다음 작업 시작

# 2세대: 시분할 시스템 (1960년대)
class TimeShareOS:
    def __init__(self, time_slice=100):
        self.processes = []
        self.time_slice = time_slice  # 각 프로세스에 할당할 시간

    def run_round_robin(self):
        # 라운드 로빈: 각 프로세스에 공평하게 CPU 시간 배분
        while self.processes:
            process = self.processes.pop(0)
            process.run(self.time_slice)
            if not process.is_done():
                self.processes.append(process)

# 현대: 멀티태스킹 운영체제
import threading
import time

def background_task(name):
    print(f"{name} 시작")
    time.sleep(0.1)
    print(f"{name} 완료")

# 동시에 여러 작업 실행
threads = [threading.Thread(target=background_task, args=(f"작업{i}",))
           for i in range(3)]
for t in threads:
    t.start()

김개발 씨는 컴퓨터 역사 다큐멘터리를 보면서 놀라움을 금치 못했습니다. 지금은 당연한 것들이 과거에는 상상도 못할 일이었습니다.

운영체제의 발전 역사를 따라가 봅시다. 1세대: 운영체제가 없던 시절 (1940년대-1950년대 초) 초기 컴퓨터에는 운영체제가 없었습니다.

프로그래머가 직접 기계어로 프로그램을 작성하고, 스위치를 조작하거나 천공 카드를 넣어 실행했습니다. 한 번에 한 사람만 컴퓨터를 사용할 수 있었고, 프로그램 교체에 많은 시간이 걸렸습니다.

컴퓨터는 비싸고 귀한 자원이었는데, 대부분의 시간을 프로그램 준비에 낭비했습니다. 2세대: 일괄 처리 시스템 (1950년대-1960년대 초) 여러 작업을 미리 모아서 한꺼번에 처리하는 일괄 처리(Batch Processing) 시스템이 등장했습니다.

프로그래머들은 천공 카드로 된 작업을 제출하고, 컴퓨터 운영자가 비슷한 작업들을 모아서 순차적으로 실행했습니다. 결과는 몇 시간 후에 받을 수 있었습니다.

이 시기에 최초의 운영체제가 등장합니다. IBM의 IBSYS 같은 시스템이 작업 간 전환을 자동화했습니다.

하지만 여전히 한 번에 하나의 프로그램만 실행할 수 있었습니다. 3세대: 다중 프로그래밍과 시분할 (1960년대-1970년대) 한 프로그램이 입출력을 기다리는 동안 CPU가 놀고 있다는 문제가 있었습니다.

이를 해결하기 위해 **다중 프로그래밍(Multiprogramming)**이 도입되었습니다. 메모리에 여러 프로그램을 로드해두고, 한 프로그램이 대기 상태가 되면 다른 프로그램을 실행합니다.

시분할(Time-Sharing) 시스템도 등장했습니다. 여러 사용자가 터미널을 통해 동시에 컴퓨터를 사용합니다.

각 사용자에게 짧은 시간씩 CPU를 배분하니, 마치 각자 전용 컴퓨터를 쓰는 것 같은 착각을 줍니다. MIT의 CTSS, 멀틱스(Multics) 등이 대표적입니다.

바로 이 시기에 **유닉스(Unix)**가 탄생합니다. 1969년 벨 연구소의 켄 톰슨과 데니스 리치가 개발했습니다.

유닉스는 단순하고 우아한 설계로 큰 인기를 얻었고, 현대 운영체제의 시조가 되었습니다. 오늘날의 Linux, macOS, Android 모두 유닉스의 영향을 받았습니다.

4세대: 개인용 컴퓨터 (1980년대-1990년대) 컴퓨터가 작아지고 저렴해지면서 개인용 컴퓨터(PC)가 등장했습니다. MS-DOS, Windows, macOS 같은 운영체제가 탄생합니다.

이 시기의 운영체제는 그래픽 사용자 인터페이스(GUI)를 제공하기 시작했습니다. 더 이상 명령어를 외울 필요 없이 마우스로 아이콘을 클릭하면 됩니다.

5세대: 네트워크와 분산 시스템 (1990년대-현재) 인터넷의 발전과 함께 운영체제도 네트워크 기능이 핵심이 되었습니다. 클라우드 컴퓨팅, 컨테이너 기술, 마이크로서비스 아키텍처가 등장하면서 운영체제의 역할도 확장되었습니다.

Docker, Kubernetes 같은 기술은 운영체제 위에서 또 다른 추상화 계층을 제공합니다. 오늘날에는 스마트폰(iOS, Android), IoT 기기, 클라우드 서버 등 다양한 환경에서 각각의 목적에 맞는 운영체제가 활약하고 있습니다.

김개발 씨는 역사를 훑어보며 깨달았습니다. "운영체제의 발전은 곧 사용자 편의성의 발전이구나." 수십 년에 걸친 수많은 개발자들의 노력 덕분에 우리는 지금처럼 편리하게 컴퓨터를 사용할 수 있게 되었습니다.

실전 팁

💡 - 유닉스 철학(간단하고 한 가지 일만 잘하는 프로그램)은 지금도 유효합니다

  • 운영체제의 역사를 알면 현재 기술의 설계 결정을 이해하기 쉬워집니다
  • Linux는 무료이고 오픈소스이므로 서버 운영체제로 배워두면 좋습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#OperatingSystem#Kernel#VonNeumann#Memory#Hardware#Operating System