Reporting Date: April. 02, 2025
OS 구현에 대해 다루고자 한다.
목차01 시스템 디버
02 성능 조정
03 시스템 구현
04 OS 생성
05 시스템 부팅
06 통신 구조
07 프로세스
08 C 프로그램의 메모리 구조
01 시스템 디버깅
(Debugging)
OS는 실행 중에 다양한 H/W
또는 S/W 오류로 인해 에러가 발생할 수 있다.
시스템 디버깅이란 이러한 에러(Error)
또는 버그(Bug)를 탐지하고 수정하는 행위를 말한다.
프로세스가 비정상적으로 종료될 경우,
OS는 해당 프로세스가 사용하던 메모리의
상태를 기록한 코어 덤프(Core Dump)를 생성한다.
이 파일은 프로그래머가 디버거라는 분석 도구를 사용해
프로세스의 코드와 메모리 상태를 상세히 분석하도록 도와준다.
뿐만 아니라, OS 커널에서 발생하는 중대한 오류,
즉 커널 패닉(Kernel Panic), 시스템 충돌 발생 시
OS는 커널 메모리 전체를 캡처한
크래시 덤프(Crash Dump) 파일을 생성한다.
이 파일은 커널 수준의 디버깅을 위한 중요한 자료가 된다.
OS 디버깅과 프로세스 디버깅은 디버깅 대상과
접근 방식에 차이가 있어, 서로 다른 도구와 기법이 사용된다.
예를 들어, Windows 작업 관리자(Task Manager)는
사용자가 프로세스 상태를 관찰하고, 비정상적인 App을
종료하는 데 활용할 수 있는 대표적인 디버깅 도구 중 하나다.
Kernighan의 법칙
(Kernighan’s Law)
컴퓨터 과학자 브라이언 커니건(Brian Kernighan)은
다음과 같은 통찰력 있는 말을 남겼다.
"디버깅은 코드를 작성하는 것보다 두 배는 더 어렵다."
이 말은 단순한 유머가 아니라,
소프트웨어 개발의 본질을 꿰뚫는 중요한 교훈이다.
즉, 어떤 코드가 디버깅될 수 있을 정도로
복잡하거나 난해하게 작성되었다면,
그것을 처음부터 그렇게 작성한 개발자 본인조차도
이를 제대로 이해하거나 수정하지 못할 수 있다는 의미다.
이로부터 도출되는 결론은 명확하다.
"디버깅이 어려운 작업이라면,
처음부터 이해하기 쉽고 유지보수 가능한 코드 작성이 필수다."
다시 말해, 지나치게 '스마트하게' 코드를 짜려는 시도는
오히려 자신이 나중에 고생하게 되는 지름길이 될 수 있다.
결국, 가장 현명한 접근은 단순하고 명료한 코드를 작성하는 것이다.
02 성능 조정
(Performance Tuning)
넓은 의미에서 디버깅의 한 영역으로 볼 수 있다.
시스템이 실행되는 과정에서 발생하는
병목 현상(bottleneck)을 찾아 제거함으로써
전반적인 성능을 향상시키는 것이 핵심 목적이다.
이를 위해 OS는 시스템 동작 측정 및 시각화 기능을
제공해야 하며 사용자는 이를 통해 시스템의 다양한
구성 요소의 상태를 모니터링할 수 있다.
이러한 기능은 성능 분석 및 조정에 활용되는
대화형 도구들을 통해 실현된다.
대표적인 예로는 Windows의 작업 관리자가 있으며,
이를 통해 CPU, 메모리, 디스크, 네트워크 등의 사용
현황을 실시간 확인 및 병목을 진단할 수 있다.
DTrace
Solaris, FreeBSD, Mac OS X 등에서
제공되는 강력한 디버깅 및 성능 분석 도구로,
실행 중인 시스템에 대해 사용자 프로세스와
커널 모두에 동적으로 탐색점(probe)을
추가할 수 있는 기능을 제공한다.
이 도구는 사용자 수준 코드와 커널 코드 간의
상호 작용을 추적하고 분석하는 데 매우 유용하다.
특히 다음과 같은 다양한 시스템 자원에 대한
전반적인 개요를 제공할 수 있다.
- 네트워크 자원 사용 현황
- 메모리 사용량, CPU 시간
- 실행 중인 프로세스들이 사용하는 파일 시스템
예를 들어, XEventsQueued와 같은 시스템 호출에 대한
탐색점을 삽입하여 이벤트 큐 대기 상황을 분석할 수 있다.
DTrace 코드 예시 및 출력
DTrace를 활용하여 UserID 101번이 소유한
각 프로세스가 사용한 CPU 시간(ns 단위)을 추적할 수 있다.
이 출력 결과는 다음과 같은 정보를 포함한다.
- 프로세스 고유 ID
- 해당 프로세스의 CPU 사용 시간
이처럼 DTrace는 고성능, 실시간 분석이 필요한
OS 레벨의 디버깅에 매우 강력한 도구로 평가받는다.
03 시스템 구현
(System Implementation)
OS나 시스템 소프트웨어의 설계 작업이 완료되면,
이를 실제로 구현하는 단계가 뒤따른다.
이 단계에서는 설계된 내용을 바탕으로
적절한 프로그래밍 언어를 선택하고,
코드로 작성하는 작업이 이루어진다.
이때 사용할 수 있는 언어는 크게
어셈블리어와 고급 언어로 나눌 수 있다.
1 . 어셈블리 언어
기계어와 1:1 대응되는 저수준 언어로,
하드웨어에 친숙하고 빠르게 실행된다는 장점이 있다.
하지만 프로그래밍이 어렵고 복잡하며,
하드웨어 아키텍처에 따라 달라져 이식성이 낮다.
예를 들어, 하나의 CPU 아키텍처에서 동작하는
어셈블리 코드는 다른 아키텍처에서는 사용할 수 없다.
이는 어셈블리어로 작성된 코드가 번역되는 기계어가
CPU마다 다르기 때문이다.
2 . 고급 언어
반면, 고급 언어(C, C++, Rust 등)는 1:다 관계를 가진다.
즉, 하나의 고급 언어 명령어가 여러 기계어 명령어로 변환될 수 있다.
프로그래머에게 더 친숙하고 유지보수가 쉬우며,
다양한 하드웨어로의 이식성이 뛰어나다.
단, 실행 속도가 어셈블리어보다 느릴 수 있으며,
메모리 사용량이 더 많다는 단점이 있으나
이는 현대의 고성능 컴파일러들이 대부분 보완해 준다.
대부분의 현대 OS는 핵심 부분(커널 등)은 고급 언어로 작성되며,
특정 하드웨어 제어가 필요한 부분은 어셈블리어로 구현되는 혼합 방식을 사용한다.
3 . OS 구현
오늘날 대부분의 OS는 고급 언어,
특히 C 언어를 기반으로 구현된다.
그 이유는 C 컴파일러만 있으면
다양한 하드웨어 플랫폼에 이식이 쉬워지기 때문이다.
실제로 윈도우, macOS, 리눅스 등 주요 OS는
대부분 C 언어를 중심으로 구현되어 있으며,
일부는 C++도 병행해 사용된다.
OS 설계 시 해당 설계를 실제로 구현해야 하는데,
과거에는 대부분 어셈블리어로 작성되었다.
하지만 현재는 코드 작성 속도, 유지보수성,
이식성 등을 고려해 고급 언어 작성이 일반적이다.
Windows | 주로 C와 일부 C++, 소수의 어셈블리어로 구현된다. |
macOS | Objective-C가 사용되며, C와 C++도 함께 활용된다. |
Microsoft App 개발 환경 | C#이 널리 사용된다. |
핵심 관점
OS 커널의 성능은 단순히 어떤 언어를 사용했는가보다,
어떤 자료구조와 알고리즘을 어떻게 적용했는가에 따라 크게 좌우된다.
한계
OS는 Microsoft, Apple과 같은
소수의 거대 기업이 주도하므로
일반 사용자가 다양한 OS 구현 방식을
비교 및 선택할 수 있는 여지는 적다.
이로 인해 OS 구현 방식에 대한 분석과 비교가
제한적이라는 점도 한 가지 단점으로 꼽을 수 있다.
04 OS 생성
(OS Generation)
OS를 사용하기 전에, 시스템은 특정 하드웨어
성능에 맞춰 설정 및 최적화되는 과정을 거친다.
이를 SYSGEN이라고도 한다.
1 . SYSGEN
(System Generation)
OS는 각 컴퓨터 시스템의 구성에 맞게 설정되어야 한다.
이 과정을 수행하는 SYSGEN 프로그램은 다음과 같다.
- 파일로부터 정보를 읽어들이기
- 운영자에게 직접 하드웨어 구성 정보를 입력받기
- 시스템에 직접 접근해 하드웨어 구성 테스트하기 등
이 과정을 통해 OS는 해당 시스템의
CPU, 메모리 크기, 디스크 구성, 입출력 장치 등의
정보를 바탕으로 가장 적절한 설정으로 초기화된다.
즉, 시스템에 맞게 환경이 조성된다고 볼 수 있다.
이 과정을 시스템 초기화 또는 부팅 시 설정 최적화라고도 한다.
SYSGEN 이후, 하드웨어는 OS를
부팅하여 실제로 사용할 수 있는 상태로 만든다.
2 . 부팅
(Booting)
OS의 핵심인 커널을 메모리에 적재해
시스템을 실행 가능한 상태로 만드는 과정이다.
이 과정은 컴퓨터 전원을 켤 때 자동으로 수행되며,
하드디스크에 저장된 커널이 메인 메모리로 적재된다.
이때 OS는 정적인 형태로 하드디스크에 저장되어 있다.
05 시스템 부팅
(System Boot)
컴퓨터가 전원을 켜거나 재부팅 등의 리셋 이벤트를 받으면,
전기적인 신호에 의해 명령 레지스터(Instruction Register)는
미리 지정된 특정 메모리 위치를 가리키게 되고, 이 위치에서부터 실행이 시작된다.
이때 가장 먼저 실행되는 것은 BIOS 또는 UEFI이다.
Basic Input/Output System
오래된 방식의 펌웨어로, 컴퓨터를 켰을 때
가장 먼저 실행되어 하드웨어를 초기화하고 OS를 부팅하는 역할을 한다.
텍스트 기반 인터페이스를 사용하고, 저장 공간은 1MB 이하로
제한되어 있어 기능이 제한적이다.
Unified Extensible Firmware Interface
BIOS를 대체하기 위해 나온 최신 펌웨어로,
그래픽 인터페이스, 마우스 지원, 더 큰 하드디스크 지원(2TB 이상),
보안 기능(Secure Boot), 더 빠른 부팅과 유연한 확장성을 제공한다.
이 BIOS는 POST(Power-On Self Test) 하드웨어 진단 테스트를 수행한다.
POST 성공 시 | BIOS는 ROM에 저장된 코드로 하드디스크에 있는 부트 섹터(Boot Sector)를 찾는다. |
POST 실패 시 | 부팅이 중단되고 오류 메시지를 출력하거나 멈춘다. |
부트스트랩 프로그램
(Bootstrap Program)
부팅 과정에서 가장 처음 실행되는 프로그램.
이 프로그램은 ROM(Read-Only Memory)에
저장되어 있으며, 시스템 부팅 시 자동 실행된다.
실행 시 디스크에서 커널을 찾아
메인 메모리로 적재 및 실행을 시작한다.
이후 시작화면이 나타나고, 사용자가 입력할 수 있는 상태가 된다.
이 프로그램은 매우 작으며, 대부분의 시스템에서는
이를 부트스트랩 로더(bootstrap loader)
또는 간단히 로더(loader)라고 부른다.
2단계 부팅 과정
일부 시스템에서는 부트스트랩 프로그램이
직접 커널을 적재하는 대신, 더 복잡한
부트 로더 프로그램을 먼저 적재한 후
이 부트 로더가 다시 커널을 적재하는
2단계 부팅 방식을 사용한다.
이에 대한 예시는 다음과 같다.
GRUB (GRand Unified Bootloader): | Linux 계열 시스템에서 사용하는 대표적인 부트 로더. |
LILO (LInux LOader): | GRUB 이전에 사용되었던 부트 로더로, 현재는 GRUB로 대체되는 추세. |
이러한 부트 로더는 파일 시스템을 탐색하여
OS 커널을 찾아 메모리로 적재하고, OS의 실행을 시작한다.
부팅 완료 후
커널이 메모리에 적재되어 실행되면,
OS는 각종 하드웨어 장치를 사용할 수 있도록 설정하고,
사용자가 명령을 입력할 수 있는 초기 상태로 전환된다.
예를 들어, Windows에서는 로그인 화면이나 GUI 초기화면,
Linux에서는 셸(Shell) 또는 GUI 로그인 매니저가 나타난다.
이 시점을 시스템이 실행 중(Running)이라고 하며,
OS가 모든 사용자 입력을 처리할 준비가 완료된 상태이다.
06 통신 구조
초기의 싱글 태스킹(single-tasking) 방식은
동시에 하나의 작업만 처리할 수 있으므로 비효율적이며,
여러 작업을 동시에 처리할 수 없는 단점이 있었다.
이러한 한계를 극복하기 위해
멀티태스킹(multitasking) 구조로 발전하였고,
이때 프로세스 스케줄링이 중요한 역할을 하게 된다.
⌎여러 프로세스들 사이에서 CPU 사용 시간을
어떻게 배분할지를 결정하는 핵심 메커니즘이다.
OS는 다음과 같은 프로세스 관리 서비스를 제공한다.
프로세스의 생성, 종료, 일시 중단과 재개 등 실행 중인
프로그램을 효율적으로 관리하는 기능은
가장 빈번하게 수행되는 작업이다.
프로세스 간 통신 (IPC)
서로 다른 프로세스가 정보를 주고받기 위해
사용하는 메커니즘으로, 크게 두 가지 방법이 있다:
공유 메모리(shared memory): | 동일한 메모리 공간을 공유하여 빠르게 데이터 교환 가능 |
메시지 전달(message passing): | 메시지를 송수신하는 방식으로, 물리적으로 분리된 프로세스 간 통신도 지원 |
또한, 파이프라인 통신과 같은 기법은
로컬 통신(같은 시스템 내)과 원격 통신(다른 시스템 간)의 차이를 보여준다.
OS가 서로 다르거나, 하드웨어 구조가 다를 경우
동일한 방식으로 통신하는 데에는 제약이 있으므로,
데이터 포맷을 변환하고 통신 프로토콜을 맞추는 작업이 필요하다.
이러한 시스템을 통합 통신 시스템이라고 할 수 있다.
그리고 현대의 서버 환경에서는
다양한 컴퓨터가 협업해야 하는 구조가 일반화되면서,
분산 시스템(distributed system)이라는 새로운 방식이 도입되었다.
⌎여러 노드에 걸쳐 자원을 분산시키고, 마치 하나의 시스템처럼
동작하도록 만들어 효율성과 확장성을 동시에 달성한다.
07 프로세스
(Process)
실제로 프로그램을 실행하는 장치인
CPU의 하나의 실행 단위이다.
개념 및 용어 설명
OS를 논의할 때, 이러한 CPU의 모든 활동을
어떻게 지칭할지는 사용하는 시스템 환경에 따라 달라질 수 있다.
예를 들어, 배치 처리 시스템(batch processing system)에서는
이러한 단위를 작업(Job)이라 하며, 여러 작업을 묶어 일괄적으로 처리한다.
반면, 시분할 시스템(time-sharing system)에서는
이를 사용자 프로그램 또는 태스크(Task)라고 하며,
CPU 시간을 잘게 나누어 여러 작업을 빠르게 전환하며 처리한다.
이때의 시간은 CPU Time을 의미한다.
일반적으로 교재에서는 작업(Job)과
프로세스(Process)를 거의 같은 의미로 사용한다.
시스템의 종류에 따라 용어는 달라질 수 있지만,
실제로는 CPU가 실행하는 하나의 독립적인 활동 단위를
뜻하는 점에서는 동일한 개념이다.
실행 중인 프로그램은 액티브한 상태를 가지며,
메모리에 로드되어 제어권(Control)을 받아 CPU에서 실행된다.
명령어를 순차적으로 실행되며, 하나의 단일 프로세스
내에서는 명령어를 병렬로 실행해서는 안 된다.
실행 중인 프로그램은 프로그램 카운터(PC)를 통해
다음 명령어의 위치를 관리한다.
여러 메모리 영역으로 구성되는데, 주요 구성은 다음과 같다:
텍스트(코드) 섹션(Text or Code Section): | 실행 코드 자체가 저장된 영역 |
스택(Stack): | 메서드의 매개변수, 복귀 주소, 지역 변수 등 임시 데이터를 저장 |
데이터 섹션(Data Section): | 전역 변수들이 저장된 영역 |
히프(Heap): | 실행 도중 동적으로 할당되는 메모리 공간 |
즉, 프로그램은 디스크에 저장된
수동적인(Passive) 엔티티이며,
반면 프로세스는 메모리에 로드되어 실제로 실행 중인
능동적인(Active) 엔티티이다.
프로그램은 GUI에서 아이콘 클릭,
명령어 줄에서 실행 명령 입력 등 다양한 방식으로 실행되며,
이러한 단순한 동작 이면에는 OS가
수행하는 복잡한 준비 작업들이 포함되어 있다.
또한, 하나의 프로그램으로부터 여러 개의 프로세스가 생성될 수 있다.
예를 들어, 사용자 A와 사용자 B가 각각 동일한 프로그램 실행 시
이는 동일한 실행 파일로부터 각각 독립된 프로세스가 생성된 것이며
이를 멀티 유저 시스템이라고 한다.
멀티 유저 시스템
(Multi-User System)
여러 사용자가 동시에 시스템에
접속해 각각의 작업을 수행할 수 있다.
이를 가능하게 하는 핵심 요소 중 하나는
다중 셸(Multiple Shells)의 개념이다.
각 사용자는 독립적인 터미널 또는 세션을 통해
OS와 상호작용하며, 그 중심에는 커널이 있다.
커널이 먼저 탑재되고 각 사용자의 명령을
처리하기 위한 프로세스가 만들어진다.
OS는 메인 메모리를 사용자별로 나누어 각자가
자신만의 프로그램을 실행할 수 있는 공간을 제공한다.
이는 다음과 같은 예시로 설명할 수 있다.
사용자 A | 자신의 터미널에서 sh a.out 명령어 실행 → a.out 프로그램 수행 |
사용자 B | 또 다른 터미널에서 시스템에 전원을 켜고 로그인 → 자신의 작업 시작 |
사용자 1이 로그인을 하고 명령어를 입력하면,
쉘(shell)이라는 명령어 해석기 프로그램이 실행되며,
이 쉘 자체가 하나의 프로세스가 된다.
이때, 사용자 2의 쉘은 포크(fork) 시스템 호출을 통해 만들어지며,
사용자 1의 쉘은 부모 프로세스, 사용자 2의 쉘은 자식 프로세스가 되어
트리 형태의 구조를 형성하게 된다.
이러한 구조는 초기 조상 프로세스(예: init 또는 systemd)로부터
점차 분기되며 전체 시스템 프로세스 트리를 구성하게 된다.
각 사용자는 커널과 1:1의 관계로 독립적인 프로세스를 가지며,
커널은 이를 효율적으로 관리하는 구조를 갖추고 있다.
이때 실행되는 프로그램은 메인 메모리에 적재되어 실행되며,
해당 프로그램의 소스 파일은 일반적으로 하드디스크에 저장되어 있다.
하드디스크에는 탐색기, HWP, 웹 브라우저 등의
다양한 프로그램이 저장되어 있으며,
사용자의 요청에 따라 메모리로 로드되어 실행된다.
OS의 커널은 이러한 프로세스 생성을
비롯한 모든 자원 관리의 중심 역할을 한다.
이 프로세스를 커널이 생성하고 관리하며,
이에 대한 정보를 담은 프로세스 제어 블록(PCB)도 함께 만들어진다.
커널 내부에는 메모리 관리, 파일 시스템, 장치 드라이버, 스케줄러 등
다양한 기능들이 통합되어 있으며, 이러한 기능이 유기적으로 작동함으로써
사용자 각각이 마치 자신만의 시스템을 사용하는 것처럼 느끼게 해 준다.
결론적으로, 멀티유저 시스템은 커널을 중심으로 각각의 사용자에게
독립적인 환경(셸과 메모리)을 제공함으로써 동시에 여러 명이
효율적으로 작업할 수 있도록 지원하는 구조이다
따라서, 여러 사용자가 같은 프로그램을 동시에 실행할 수 있으며,
이 경우 하나의 프로그램에 대해 여러 개의 프로세스가 존재하게 된다.
08 C 프로그램의 메모리 구조
(Memory Layout of a C Program)
C 프로그램이 실행되면, OS는 해당 프로그램을
다음과 같은 메모리 영역으로 나누어 배치한다.
High Memory
┌────────────────────────────┐
│ Command-line Arguments │ (예: argc, argv[])
├────────────────────────────┤
│ Stack │ (지역 변수, 함수 호출 정보 등)
│ 예: int i; │
├────────────────────────────┤
│ Heap │ (동적 메모리 할당 영역)
│ 예: malloc()로 할당한 값 │
├────────────────────────────┤
│ Data Segment │
│ ├─ Initialized Data │ (초기화된 전역변수)
│ │ 예: int y = 15; │
│ └─ Uninitialized Data │ (초기화되지 않은 전역변수)
│ 예: int x; │
├────────────────────────────┤
│ Text Segment │ (코드 영역: 함수 및 명령어들)
│ 예: main(), printf() 등 │
└────────────────────────────┘
Low Memory
프로세스 내부 구조
하드디스크에 저장되어 있던 프로그램이 실행되면,
OS는 그것을 여러 메모리 영역에 나누어
프로세스 형태로 메모리에 적재한다.
이때 각각의 코드와 데이터는 다음과 같은 영역에 들어가게 된다.
#include <stdio.h>
#include <stdlib.h>
int x; // 전역 변수 (초기화 X) → BSS 영역
int y = 15; // 전역 변수 (초기화 O) → Data 영역
int main(int argc, char *argv[]) { // 매개변수 → Stack
int i; // 지역 변수 → Stack
int *values; // 지역 변수 → Stack
values = (int *)malloc(sizeof(int) * 5); // 동적 할당된 메모리 → Heap
// 지역변수: 포인터 변수 → Stack
for (i = 0; i < 5; i++) {
values[i] = i; // Heap 영역에 데이터 저장
}
return 0;
}
다만, 시스템 콜과는 다른 개념이므로 헷갈리지 않아야 한다.
시스템 콜은 OS와 커널과의 상호작용을 의미한다.
결론적으로, 하나의 프로그램이지만, 실행되면 실제 메모리에서
Text, Data, BSS, Stack, Heap 등으로 나뉘어 저장된다.
프로세스 상태
상태(State) | 설명 |
생성 (New) | 새로운 프로세스를 생성 중인 상태. OS가 초기화 작업과 자원 할당을 수행 |
준비 (Ready) | 메모리에 적재되어 있고, CPU 할당을 기다리는 상태. 실행은 아직 아님. |
실행 (Running) | CPU를 할당받아 명령어를 실제로 실행 중이며, 제어권을 가진 상태. |
대기 (Waiting) | 입출력(I/O) 등 외부 이벤트를 기다리는 상태. CPU는 다른 프로세스에 할당 |
종료 (Terminated) | 프로세스 실행이 완료되어 자원이 회수되는 상태. |
프로세스 제어 블록 (PCB) 구성 정보
구성 요소 | 설명 |
프로세스 상태 | 현재 프로세스의 상태 (예: 생성, 준비, 실행, 대기, 종료 등) |
프로그램 카운터 | 다음에 실행할 명령어의 주소를 저장 |
CPU 레지스터 | 프로세스 실행에 필요한 모든 CPU 레지스터 정보 (누산기, 스택 포인터 등 포함) |
CPU 스케줄링 정보 | 우선순위, 스케줄 큐 포인터, 타임 슬라이스 등 스케줄링에 필요한 정보 |
메모리 관리 정보 | 페이지 테이블, 세그먼트 테이블, 기준/한계 레지스터 등 메모리 관련 정보 |
계정(Accounting) 정보 | CPU 사용 시간, 실행 시간, 계정 번호, 프로세스 번호 등 자원 사용 내역 |
입출력 상태 정보 | 사용 중인 I/O 장치, 열린 파일 목록 등 입출력 관련 정보 |
프로세스 식별자 (PID) | 해당 프로세스를 고유하게 식별하는 번호 |
'2025 - 1학기 > 플랫폼OS' 카테고리의 다른 글
OS 협력 (2) | 2025.04.18 |
---|---|
OS 메세지 (0) | 2025.04.17 |
OS 설계 구조 (8) | 2025.04.14 |
OS 명령어 처리 과정 (4) | 2025.04.13 |
OS 서비스 (4) | 2025.04.11 |