logo
logo

Sunghun Son / Operating System / 운영체제 구조

Created Sun, 02 Jul 2023 14:04:42 +0900
5129 Words

운영체제를 살피기 위한 세 가지 관점이 있다. 하나씩 상세히 살펴보자.

  1. 운영체제가 제공하는 서비스에 초점을 맞춘다.
  2. 운영체제가 사용자 및 프로그래머에게 제공하는 인터페이스에 초점을 맞춘다.
  3. 시스템의 구성요소와 그들의 상호 연결에 초점을 맞춘다.

운영체제 서비스

  • 사용자 인터페이스(UI) : 명령어를 입력하는 CLI와 그래픽을 조작하는 GUI가 보편적이다.
  • 프로그램 실행 : 시스템은 프로그램을 메모리에 적재해 실행하고, 종료할 수 있어야 한다.
  • 입출력 연산 : 프로세스는 입출력을 요구해 입출력 장치와 간접적으로 상호작용할 수 있다.
  • 파일시스템 조작 : 파일과 디렉터리를 읽고 쓸 수 있다. 또한 생성 및 삭제, 권한 설정도 가능해야 한다.
  • 통신 : 프로세스끼리 혹은 컴퓨터 시스템끼리 통신할 수 있어야 한다. 통신은 공유 메모리나 메시지 전달 기법으로 구현될 수 있다.
  • 오류 탐지 : CPU, 메모리, 입출력 장치, 또는 사용자 프로그램에서 일어날 수 있는 오류에 적당한 조치를 취한다.
  • 자원 할당 : 사용자와 작업별로 자원을 할당하며, 적절한 스케줄링으로 자원을 효율적으로 관리해야 한다.
  • 회계 : 사용자의 자원 사용을 추적할 수 있어야 한다.
  • 보호와 보안 : 소유주는 정보를 통제할 수 있어야 한다. 부적합한 접근을 차단하고 칩입을 탐지한다.

사용자 운영체제 인터페이스

명령어 라인 인터페이스 (CLI)

어떤 운영체제는 커널에 명령 해석기(command interpreter)를 둬 사용자의 명령을 시스템이 해석할 수 있도록 한다. 이 명령 해석기를 셸(shell)이라고 한다. 사용자가 명령을 입력하면 셸은 내장 명령어나 시스템 프로그램을 검색해 적절한 명령을 수행한다. 시스템 관리자에게 더 효율적인 방식이다.

그래픽 사용자 인터페이스 (GUI)

두번째 방식은 사용자 친화적인 GUI이다. 마우스를 움직이거나 터치를 통해 운영체제와 상호작용할 수 있다.

시스템 호출

파일을 복사 프로그램을 예시로 시스템 호출을 살펴보자. 빨간색이 시스템 호출을 한 것이다.

프로그램을 실행하고 프롬프트를 띄워 사용자에게 파일 이름을 키보드로 입력받는다. 접근이 금지된 파일이라면 콘솔에 메시지를 출력하고, 비정상적으로 종료해야 한다. 출력 파일이 이미 존재하는 경우 프로그램을 중단해야 한다. 혹은 기존 파일을 삭제하고 새로운 파일을 생성할 수도 있다. 두 개의 파일이 준비되면 반복적으로 입력 파일을 읽고, 출력 파일에 쓰는 루프를 돌려야 한다. 파일의 끝에 도달해 읽기가 실패하면 두 파일을 닫고 콘솔에 메시지를 출력하고 정상 종료한다.

개발자에게 직접 시스템 호출을 사용하지 않고 제공된 API를 제공한다. 윈도우에는 Windows API, POSIX 기반 시스템에서는 POSIX API가 사용된다. 프로그램이 API를 호출하면 내부적으로 적절한 시스템 호출을 부른다. 직접 시스템 호출을 부를 수도 있지만 API를 선호하는 이유는 운영체제별로 시스템 호출 명세가 다르고 커널에 종속이라 개발자가 사용하기에는 불편하기 때문이다. 그래서 API를 통해 세부 구현을 감추고 동일한 함수 호출을 할 수 있게 해, 코드의 변경을 최소화할 수 있다.

운영체제에 매개변수를 전달하기 위해서는 세 가지 방식을 일반적으로 사용한다.

  • 가장 간단한 방법은 레지스터를 통해 바로 전달하는 것이다.
  • 하지만레지스터는 전달할 수 있는 정보가 적기 때문에, 정보를 메모리 내의 스택이나 테이블에 넣고 주소만 레지스터로 넘길 수도 있다.

시스템 호출 유형

보라색 영어는 시스템 호출 함수이다.

프로세스 제어

실행 중인 프로그램은 정상(end) 또는 비정상적으로(abort) 멈출 수 있다. 비정상적으로 중지하거 트랩이 발생할 경우, 메모리 덤프가 만들어지고 디버거에 의해 검사될 수 있다. 한 프로그램 혹은 잡이 다른 프로그램을 적재(load)하고 실행(exec/fork)할 수 있다. 새로운 프로그램은 기존의 프로그램과 동기 혹은 비동기적으로 실행될 수 있다.

우리는 새로운 잡이나 프로세스의 우선순위 등의 속성을 읽고 설정할 수 있어야 한다. 또 필요없다면 이들을 종료할 수 있어야 한다 (terminate_process). 일정 시간 기다리거나(wait_time) 특정 이벤트를 기다릴 수 있다(wait_event). 이 경우 잡이나 프로세스에서 이벤트 발생 신호를 보내야 한다. 여러 프로세스가 하나의 데이터를 공유할 때 한 프로세스가 데이터를 잠그고(acquire_lock) 혼자 쓰다가 잠금을 해제할 수도 있다(release_lock).

파일 관리

파일을 생성(create)하고 삭제(delete)할 수 있다. 파일을 열고(open) 닫을(close) 수 있다. 또한 읽고(read) 쓸(write) 수 있다. 파일의 끝으로 이동하거나 되감기(rewind)를 하는 등 위치 변경(reposition)을 할 수도 있다. 파일 속성을 가져오거나 설정할 수 있다(get_file_attribute, set_file_attribute). 그 외에도 파일을 이동하거나(move) 복사하는(copy) 등 많은 시스템 호출이 있다.

장치 관리

장치에는 하드디스크 같은 물리적인 장치 뿐만 아니라 파일 같이 가상의 장치를 포함할 수도 있다. 우선 장치의 독점적인 사용을 요청(request)하고 사용이 끝나면 방출(release)해야 한다. 이는 파일을 열고 닫는 것과 비슷하다. 운영체제가 요청에 응답해 장치를 할당하면, 우리는 읽고(read) 쓸(write) 수 있다. 또한 경우에 따라 위치 변경(reposition)도 할 수 있다.

보면 장치 관리와 파일이 거의 비슷하다. UNIX는 이를 근거로 파일과 장치를 하나로 통합해 파일-장치 구조를 만들었다. 입출력 장치는 특별한 이름과 위치를 사용하고 파일 속성으로 식별된다.

정보 유지

프로세스는 운영 체제의 버전, 현재 시간(time), 날짜(date)를 호출하거나 디버깅을 위해 메모리를 덤핑할 수 있다(dump). 운영체제는 모든 프로세스들의 정보를 가지고 있으며, 이 정보를 조회하고 재설정할 수 있다. (get_process_attribute, set_process_attribute)

타이머 인터럽트를 발생하면 그때마다 프로그램 카운터를 기록한다. 이 기록을 분석해 프로그램의 실행을 추적한 것을 시간 프로파일이라고 하고, 대부분 운영체제가 이를 지원한다.

프로세스 간 통신 (IPC)

메시지 전달 모델

두 프로세스가 정보 교환을 위해 메시지를 직간접적으로 주고 받는다. 각 컴퓨터는 호스트 이름을 가지고, 프로세스는 프로세스 이름을 가진다. 이 둘을 조합해 같은 CPU에 있는 프로세스든 다른 CPU의 프로세스든 정확히 식별해 통신할 수 있다(get_hostid, get_processid). 이후 송신 프로세스는 open, close 명령을 보내고, 수신 프로세스는 accept_connection 시스템 호출을 통해 허락한다. 연결이 만들어졌으면 read_message, write_message 시스템 호출로 정보를 주고받고 마지막에 close_connection 호출로 통신을 종료한다.

이 모델은 충돌이 없기 때문에 구현이 쉽고 소량의 데이터를 교환할 때 유리하다.

공유 메모리 모델

shared_memory_create와 shared_memory_attach 호출로 공유 메모리를 만들고 접근한다. 프로세스들은 동일한 위치에 동시에 쓰지 않도록 보장해야만 한다.

이 모델은 속도가 빠르고 편리하지만 보호와 동기화 문제를 해결하는 것이 어렵다.

보호

보호는 set_permission, get_permission 호출을 통해 권한을 설정하고, allow_user, deny_user 호출로 사용자의 접근이 허가된 접근인지 확인한다.

시스템 프로그램

현대 시스템은 ‘하드웨어 → 시스템 프로그램 → 응용 프로그램’으로 계층 구조이다. 시스템 프로그램 혹은 시스템 유틸리티는 시스템 호출에 대한 사용자 인터페이스를 제공하거나 더 복잡한 작업을 지원하는 프로그램이다. 사용자는 시스템 호출이 아닌 시스템 프로그램과 응용 프로그램을 조작하는 경우가 대부분이다.

  • 파일 관리 : 파일과 디렉터리를 생성, 삭제, 복사, 이름 바꾸기, 인쇄, 덤프, 리스트 하는 등의 동작을 한다.
  • 상태 정보 : 날짜, 통신 상태 등 시스템의 방대한 정보를 관리, 표시한다.
  • 파일 변경 : 문서편집기와 특수 명령어를 지원한다.
  • 프로그래밍언어 지원 : 컴파일러, 어셈블러, 디버거 및 해석기를 지원한다.
  • 프로그램 적재와 실행 : 시스템은 절대 로더, 재배치 가능한 로더, 링키지 에디터, 중첩 로더 등을 제공한다.
  • 통신 : 다른 사용자의 화면에 메시지를 띄우거나, 원격 접속을 할 수 있다.
  • 백그라운드 서비스 : 서비스, 서브시스템, 데몬으로 알려진, 특정 프로세스를 시스템이 정지할 때까지 동작시킨다.

운영체제 구조

크고 복잡한 시스템은 대부분 역할별로 요소를 쪼개 정의한다. 이 모듈은 입출력과 기능이 잘 정의되어 있어야 하고 모듈끼리의 상호 연결도 유기적으로 되어야 한다.

간단한 구조

소프트웨어 카탈로그의 구성

간단한 구조 (왼쪽) / 좀 있다 설명할 마이크로커널 (오른쪽)

최초의 UNIX는 커널과 시스템 프로그램으로 구성되어 있다. 시스템 호출 인터페이스 아래와 물리적 하드웨어 위의 모든 것이 커널이다. 엄청나게 많은 기능이 계층 분리 없이 모놀리식(monolithic)하게 구성되었기 때문에 구현과 유지 보수가 모두 어려웠다. 그러나 한 뭉치 안에서 동작하기 때문에 통신에 오버헤드가 거의 없고 성능이 좋은 장점을 가지고 있긴 하다.

계층적 접근

간단한 구조에서 벗어난다는 것은 시스템을 적절한 조각으로 나눠 개발하는 방식을 말한다. 이때 하향식 접근을 사용하면, 시스템의 전체적인 기능을 결정하고 기능별로 구성요소를 나눌 수 있다. 구성 요소들은 인터페이스를 통해 소통하는데, 이렇게 하면 다른 구성요소들의 구현을 은닉할 수 있다. 개발자는 다른 요소의 구현에 신경을 쓰지 않고 구현할 수 있다.

계층적 접근은 모듈화의 한 가지 방법인데, 시스템을 계층으로 나누고 각 계층은 자신의 바로 아래 계층만 호출하는 방식이다. 하드웨어는 최하층을, 사용자 인터페이스는 최상층을 차지한다. 이렇게 구성하면 각 층은 바로 아래 계층만 신경 쓰면 되기 때문에 검증과 디버깅이 쉽다. 또한, 하위층이 디버깅이 잘 되었다는 걸 전제로 하기 때문에 오류를 자기 계층에서만 찾으면 된다.

이 방식의 단점은 실제 시스템이 계층구조에 딱 맞지는 않으며 호율성이 낮다는 점이다. 각 층은 시스템 호출에 오버헤드를 추가하고 성능을 저하시킨다.

마이크로커널

마이크로커널은 커널은 최소한의 통신 설비와 프로세스, 메모리 관리 기능만 구현하고, 나머지는 사용자 수준 프로그램으로 구현하여 운영하는 방식이다. 이 방식의 가장 중요한 기능은 이 서비스들 간의 통신을 제공하는 것이고메시지 전달 모델을 지원한다. 이 방식의 장점은 확장에 용이하고 커널 자체의 용량이 작아 이식이 쉽다는 것이다. 단점은 통신에 오버헤드가 상당하다는 점이다.

많은 현대 운영체제가 마이크로커널 방식을 사용한다. MacOS의 커널인 Darwin도 카네기 멜론 대학교가 만든 최소의 마이크로커널인 Mach에 기반하고 있다.

적재가능 커널 모듈

이 접근법은 커널은 핵심적인 요소만 가지고 있고, 부팅 때나 실행 중에 부가적인 서비스를 모듈을 통하여 동적으로 링크한다. 이렇게 하면 새로운 기능을 넣을 때 컴파일을 다시할 필요가 없다. 예를 들어 CPU, 메모리 관리는 커널에 직접 구현하고, 원하는 파일시스템은 모듈을 통해 가져올 수 있다.

커널은 인터페이스를 통해 모듈을 호출하기 때문에 계층 구조와 비슷하다. 또한 다른 모듈의 적재 방법과 통신 방법을 안다는 점에서는 마이크로커널과 유사하다. 그러나 통신하기 위해서 메시지 전달 기법을 사용할 필요가 없다.

하이브리드 시스템

실제 운영체제는 섞어서 쓰는 경우가 많다. 실제 사례를 같이 보자

Mac OS X

Mac OS X

Android

Android

운영체제 관리

운영체제 디버깅

디버깅은 하드웨어와 소프트웨어의 시스템 오류를 발견하고 수정하는 행위이다. 시스템의 병목 현상을 제거하여 성능을 향상시키는 성능 조정도 디버깅에 포함된다.

  • 장애 분석 : 프로세스가 실패한다면 운영체제는 오류 정보를 로그 파일에 기록하고 메모리를 캡쳐한 코어 덤프를 저장한다.
  • 성능 조정 : 시스템의 동작을 추적하여 어느 부분에서 시간이 오래 걸리는지 확인한다. top 명령으로 시스템의 상태를 확인하는 것이 그 예이다.
  • DTrace : 시스템, 사용자 프로세스와 커널을 모두 종합적으로 추적할 수 있는 Solaris 10의 설비이다.

운영체제 생성

일반적인 운영체제는 여러 구성을 가진 하드웨어 위에서 동작한다. 이 경우 특정 컴퓨터를 위해 구성되어야 하는데 이를 SYSGEN(시스템 생성)이라 한다. 운영체제는 하드웨어의 정보를 읽거나 요구하고, 해당 정보를 통해 완전히 컴파일된다. 혹은 이러한 정보들을 가지고 필요한 모듈을 선택할 수도 있다.

시스템 부트

우리는 전원버튼을 누르면, ROM에 있는 부트스트랩 로더가 커널을 찾아 메인메모리에 적재하고 실행한다. 좀 더 복잡한 프로그램은 단순 부트스트랩 로더가 부트 프로그램을 호출하고 이 부트 프로그램이 다시 커널을 적재하는 구조를 사용하기도 한다.

부트 프로그램은 커널을 적재하기 이전에 다양한 작업을 실행한다. 기계를 진단하고, CPU 레지스터, 장치 제어기, 메인 메모리 등을 초기화한다. 그런 다음 운영체제를 시작한다.

작은 하드웨어의 경우 운영체제가 ROM에 저장돼 있는 경우가 있다. 이 경우 빈번히 업데이트가 필요하기 때문에 쓰기가 힘든 ROM 대신 EPROM을 사용한다. ROM, EPROM 등 하드웨어와 소프트웨어의 중간 상태에 있는 장치를 펌웨어라 부른다. 펌웨어는 RAM에 비해 속도가 느리기 때문에 몇몇 시스템은 펌웨어에 저장된 운영체제를 RAM에 복사한 후 쓰기도 한다.

일반적인 경우, 운영체제는 디스크에 있기 때문에 펌웨어에는 부트스트랩 로더만 있다. 로더는 디스크의 특정 위치의 프로그램만 가져오는 역할을 하고, 프로그램이 더 복잡한 명령을 추가로 수행한다. 이때 가져오는 위치를 부트 블록이라 하고 부트 블록이 있는 파티션을 부트파티션, 디스크를 부트디스크라 한다. Linux의 경우에 GRUB 부트스트랩 프로그램을 사용한다. 부트 프로그램은 운영체제 커널을 찾아내고 실행을 시작한다. 이때부터 진짜 실행 중이다.