Skip to content
Published on

[운영체제] 02. 운영체제 구조와 서비스

Authors

운영체제 서비스

운영체제는 사용자와 프로그래머에게 다양한 서비스를 제공한다. 이 서비스들은 크게 두 범주로 나눌 수 있다.

사용자를 위한 서비스

1. 사용자 인터페이스(UI)

  • CLI(Command-Line Interface): 텍스트 기반 명령어 입력. bash, zsh 등의 셸
  • GUI(Graphical User Interface): 마우스와 아이콘 기반 인터페이스. Windows, macOS
  • 터치스크린 인터페이스: 모바일 기기에서 제스처 기반 조작

2. 프로그램 실행

시스템은 프로그램을 메모리에 로드하고 실행할 수 있어야 한다. 프로그램은 정상적으로 또는 비정상적으로(오류 표시와 함께) 종료될 수 있다.

3. I/O 연산

실행 중인 프로그램이 I/O를 요청할 수 있다. 보안과 효율성을 위해 사용자 프로그램이 직접 I/O 장치를 제어할 수 없고, OS가 I/O 수단을 제공한다.

4. 파일 시스템 조작

파일과 디렉터리의 읽기, 쓰기, 생성, 삭제, 검색, 권한 관리 등을 수행한다.

5. 통신

프로세스 간 정보 교환이 필요하다. 두 가지 모델이 있다.

  • 공유 메모리(Shared Memory): 여러 프로세스가 메모리 영역을 공유
  • 메시지 전달(Message Passing): OS를 통해 프로세스 간 메시지를 주고받음

6. 오류 탐지

CPU, 메모리, I/O 장치, 사용자 프로그램의 오류를 지속적으로 감시한다.

시스템 효율을 위한 서비스

  • 자원 할당: 여러 프로세스가 동시에 실행될 때 자원을 적절히 배분
  • 로깅: 어떤 프로그램이 어떤 자원을 얼마나 사용하는지 기록
  • 보호와 보안: 다중 사용자 환경에서 프로세스 간 간섭을 방지하고, 외부 접근을 제어

시스템 콜

시스템 콜(System Call)은 OS가 제공하는 서비스에 대한 프로그래밍 인터페이스다. 사용자 프로그램이 커널의 기능을 요청할 때 사용한다.

시스템 콜 처리 과정

[시스템 콜 동작 흐름]

사용자 프로그램        C 라이브러리         커널
     |                    |                  |
     |-- open() 호출 ---->|                  |
     |                    |-- 레지스터에       |
     |                    |   시스템 콜 번호   |
     |                    |   설정            |
     |                    |                  |
     |                    |-- trap 명령어 --->|
     |                    |   (모드 전환)      |
     |                    |                  |-- 시스템 콜 테이블
     |                    |                  |   에서 핸들러 조회
     |                    |                  |
     |                    |                  |-- sys_open() 실행
     |                    |                  |
     |                    |<-- 결과 반환 -----|
     |<-- fd 반환 --------|   (모드 복원)      |
     |                    |                  |

시스템 콜 API

프로그래머는 보통 시스템 콜을 직접 호출하지 않고, API(Application Programming Interface)를 통해 사용한다.

  • POSIX API: Unix, Linux, macOS에서 사용
  • Windows API: Windows 시스템에서 사용
  • Java API: JVM이 하부 OS의 시스템 콜로 변환
// POSIX API를 사용한 파일 복사 예시
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 4096

int main(int argc, char *argv[]) {
    int src_fd, dst_fd;
    ssize_t bytes_read;
    char buffer[BUF_SIZE];

    // open() - 파일 시스템 관련 시스템 콜
    src_fd = open(argv[1], O_RDONLY);
    if (src_fd == -1) {
        perror("원본 파일 열기 실패");
        exit(EXIT_FAILURE);
    }

    dst_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dst_fd == -1) {
        perror("대상 파일 생성 실패");
        exit(EXIT_FAILURE);
    }

    // read(), write() - I/O 관련 시스템 콜
    while ((bytes_read = read(src_fd, buffer, BUF_SIZE)) > 0) {
        if (write(dst_fd, buffer, bytes_read) != bytes_read) {
            perror("쓰기 실패");
            exit(EXIT_FAILURE);
        }
    }

    // close() - 파일 디스크립터 해제 시스템 콜
    close(src_fd);
    close(dst_fd);

    return 0;
}

시스템 콜의 유형

분류예시 (POSIX)설명
프로세스 제어fork(), exec(), wait(), exit()프로세스 생성, 실행, 종료
파일 관리open(), read(), write(), close()파일 열기, 읽기, 쓰기, 닫기
장치 관리ioctl(), read(), write()장치 요청, 해제, 읽기/쓰기
정보 유지getpid(), alarm(), time()시간, 날짜, 프로세스 정보
통신pipe(), shmget(), mmap()파이프, 공유 메모리, 메모리 맵
보호chmod(), chown(), umask()권한 설정, 소유자 변경

시스템 콜 매개변수 전달 방법

시스템 콜에 매개변수를 전달하는 세 가지 방법이 있다.

  1. 레지스터: 매개변수를 CPU 레지스터에 직접 전달 (가장 빠르지만 레지스터 수 제한)
  2. 메모리 블록: 매개변수를 메모리의 블록에 저장하고, 블록의 주소를 레지스터로 전달
  3. 스택: 프로그램이 매개변수를 스택에 push하고, OS가 pop하여 사용

운영체제 구조

모놀리식 구조 (Monolithic)

가장 단순한 구조로, 커널의 모든 기능이 하나의 주소 공간에서 실행된다.

[모놀리식 커널]

+------------------------------------------+
|             사용자 프로그램                 |
+------------------------------------------+
|              시스템 콜 인터페이스           |
+------------------------------------------+
|                                          |
|  파일 시스템 | 프로세스 관리 | 메모리 관리  |
|  네트워킹    | 장치 드라이버  | I/O 관리   |
|                                          |
|        모두 커널 공간에서 실행              |
+------------------------------------------+
|               하드웨어                     |
+------------------------------------------+

: 전통적인 UNIX, Linux
  • 장점: 시스템 콜 오버헤드가 적어 성능이 좋음
  • 단점: 구현과 유지보수가 어려움, 하나의 버그가 전체 시스템에 영향

계층형 구조 (Layered)

OS를 여러 계층으로 나누고, 각 계층은 바로 아래 계층의 기능만 사용한다.

[계층형 구조]

+-------------------+
| 계층 N: 사용자 UI  |
+-------------------+
| 계층 N-1          |
+-------------------+
|       ...         |
+-------------------+
| 계층 1: 메모리 관리|
+-------------------+
| 계층 0: 하드웨어   |
+-------------------+

각 계층은 아래 계층만 호출 가능
  • 장점: 구현과 디버깅이 쉬움
  • 단점: 계층 간 호출로 인한 오버헤드, 계층 정의가 어려움

마이크로커널 (Microkernel)

커널에서 최소한의 기능만 남기고, 나머지를 사용자 공간으로 이동시킨다.

[마이크로커널 구조]

사용자 공간:
+--------+ +----------+ +---------+ +--------+
|파일    | |장치      | |네트워크  | |메모리  |
|서버    | |드라이버   | |서버     | |서버    |
+--------+ +----------+ +---------+ +--------+
     |          |            |          |
+------------------------------------------+
|  마이크로커널: IPC + 기본 스케줄링 + 메모리 |
+------------------------------------------+
|               하드웨어                     |
+------------------------------------------+

: Mach, QNX, MINIX 3
  • 장점: 확장성이 좋고, 이식이 쉬우며, 신뢰성이 높음
  • 단점: 사용자 공간과 커널 공간 간 메시지 전달 오버헤드

하이브리드 구조 (Hybrid)

현대 대부분의 OS는 여러 구조를 결합한 하이브리드 방식을 사용한다.

  • Linux: 기본적으로 모놀리식이지만, 로드 가능한 커널 모듈(LKM) 지원
  • Windows: 마이크로커널 설계에 영향을 받았지만 성능을 위해 많은 기능을 커널에 포함
  • macOS: Mach 마이크로커널 + BSD UNIX 커널 = XNU 하이브리드 커널
[macOS/iOS의 XNU 커널]

+------------------------------------------+
|         사용자 경험 계층 (Aqua/Cocoa)      |
+------------------------------------------+
|         응용 프레임워크 (Core Foundation)   |
+------------------------------------------+
|              커널 환경                     |
|  +--------+  +--------+  +---------+     |
|  | Mach   |  | BSD    |  | I/O Kit |     |
|  | (IPC,  |  | (POSIX |  | (드라이  |     |
|  | 스레드, |  |  API,  |  |)    |     |
|  | VM)    |  | 네트워크|  |         |     |
|  +--------+  +--------+  +---------+     |
+------------------------------------------+
|               하드웨어                     |
+------------------------------------------+

로드 가능한 커널 모듈 (LKM)

Linux 커널은 동적으로 모듈을 로드/언로드할 수 있다.

// Linux 커널 모듈 예시 (hello.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("Hello World 커널 모듈");

// 모듈 로드 시 호출
static int __init hello_init(void) {
    printk(KERN_INFO "Hello, Kernel World!\n");
    return 0;
}

// 모듈 언로드 시 호출
static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, Kernel World!\n");
}

module_init(hello_init);
module_exit(hello_exit);
# 커널 모듈 관리 명령어
sudo insmod hello.ko    # 모듈 로드
lsmod                   # 로드된 모듈 목록
sudo rmmod hello        # 모듈 언로드
modinfo hello.ko        # 모듈 정보 확인

시스템 부팅

컴퓨터 전원을 켜면 다음 과정을 거쳐 OS가 시작된다.

[부팅 과정]

1. 전원 ON
      |
      v
2. BIOS/UEFI 실행 (펌웨어)
   - POST(Power-On Self Test) 수행
   - 하드웨어 초기화
      |
      v
3. 부트 로더 실행 (GRUB)
   - 부트 디스크의 MBR/GPT에서 로드
   - 커널 이미지 선택
      |
      v
4. 커널 로드 및 초기화
   - 하드웨어 탐지
   - 드라이버 초기화
   - 루트 파일 시스템 마운트
      |
      v
5. init/systemd 실행 (PID 1)
   - 시스템 서비스 시작
   - 로그인 프롬프트 제공

GRUB 부트 로더

GRUB(GRand Unified Bootloader)은 Linux에서 가장 널리 사용되는 부트 로더다.

# GRUB 설정 파일 예시 (/boot/grub/grub.cfg의 일부)
menuentry 'Ubuntu' {
    set root='(hd0,gpt2)'
    linux /vmlinuz root=/dev/sda2 ro quiet splash
    initrd /initrd.img
}

menuentry 'Ubuntu (Recovery Mode)' {
    set root='(hd0,gpt2)'
    linux /vmlinuz root=/dev/sda2 ro recovery nomodeset
    initrd /initrd.img
}

UEFI 기반 시스템에서는 UEFI 펌웨어가 ESP(EFI System Partition)에서 부트 로더를 찾아 실행한다. UEFI는 Secure Boot 기능을 제공하여 서명된 부트 로더만 실행하도록 제한할 수 있다.


정리

운영체제는 다양한 서비스를 제공하며, 시스템 콜을 통해 사용자 프로그램이 커널 기능에 접근한다. OS 구조는 모놀리식에서 마이크로커널까지 다양하며, 현대 OS는 성능과 유연성을 모두 추구하는 하이브리드 방식을 채택하고 있다. 부팅 과정에서는 펌웨어, 부트 로더, 커널이 순차적으로 제어를 넘기며 시스템을 초기화한다.