포스트

Operating System: Chapter 02 - 운영체제 구조

Operating System: Chapter 02 - 운영체제 구조

2.1. 운영체제 서비스

운영체제는 프로그램 실행 환경을 제공하며, 프로그램과 사용자에게 특정 서비스를 제공한다.
img1

사용자 인터페이스

거의 모든 운영체제는 사용자 인터페이스(UI)를 제공하며, 일반적으로는 그래픽 사용자 인터페이스(GUI)가 사용한다.

인터페이스는 포인팅 장치인 마우스와 텍스트를 입력할 키보드를 가진다.
모바일 시스템은 터치스크린 인터페이스를 제공한다.
명령어 라인 인터페이스(CLI)는 명령을 사용하며 이를 입력할 방법이 사용된다.

프로그램 실행

시스템은 프로그램을 메모리에 적재해 실행할 수 있어야 한다.
프로그램은 정상적이든 비정상적이든 실행을 끝낼 수 있어야 한다.

입출력 연산

수행 중인 프로그램은 입출력(파일, 입출력 장치 등)을 요구할 수 있다.
특정 장치에 대해서는 특수한 기능(네트워크 인터페이스에서 읽거나 파일 시스템에 쓰기 등)이 요구될 수 있다.
사용자들은 통상 입출력 장치를 직접 제어할 수 없으며 운영체제가 제공한다.

파일 시스템 조작

프로그램은 파일을 읽고 쓸 수 있으며 생성 및 삭제할 수 있다.
또한, 지정된 파일을 찾을 수 있어야 하며 파일의 정보를 열거할 수 있어야 한다.
마지막으로 파일 소유권에 기반을 둔 권한 관리를 이용하여 파일이나 디렉터리의 접근을 허가하거나 거부할 수 있게 한다.

통신

한 프로세스가 다른 프로세스와 정보를 교환해야 할 필요가 있는 여러 상황이 있다.

  • 동일한 컴퓨터에서 수행되고 있는 프로세스들 사이의 통신
  • 네트워크에 의해 함께 묶여 있는 서로 다른 컴퓨터 시스템상에서 수행되는 프로세스들 사이의 통신

통신은 공유 메모리 혹은 메시지 전달 기법을 사용하여 구현될 수 있다.

오류 탐지

운영체제는 모든 가능한 오류를 항상 의식하고 있어야 한다.
오류는 CPU, 메모리 하드웨어, 입출력 장치 또는 사용자 프로그램에서 일어날 수 있다.

자원 할당

다수의 프로세스나 다수의 작업이 동시에 실행될 때, 그들 각각에 자원을 할당해 주어야 한다.
어떤 자원(CPU 사이클, 메인 메모리, 파일 저장장치 등)은 특수한 할당 코드를 가질 수 있지만, 다른 것들(입출력 장치 등)은 훨씬 일반적인 요청과 방출 코드를 가질 수 있다.

기록 작성 (logging)

시스템은 어떤 프로그램이 어떤 종류의 컴퓨터 자원을 얼마나 많이 사용하는지를 추적하기 위해 기록을 관리한다.

보호와 보안

다중 사용자 컴퓨터 시스템 또는 네트워크로 연결된 컴퓨터 시스템에 저장된 정보의 소유자는 그 정보의 사용을 통제하기를 원한다.
서로 다른 여러 프로세스가 병행하게 수행될 때, 한 프로세스가 다른 프로세스나 운영체제 자체를 방해해서는 안 된다.
이러한 보안은 각 사용자가 자원에 대한 접근을 원할 때 통상 패스워드를 사용해서 시스템에게 자기 자신을 인증하는 것으로부터 시작된다.

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

2.2.1. 명령 인터프리터

대부분의 운영체제는 명령 인터프리터를 프로세스가 시작되거나 사용자가 처음 로그온할 때 수행되는 특수한 프로그램으로 취급한다.
선택할 수 있는 여러 명령 인터프리터를 제공하는 시스템에서 이 해석기는 셸(shell)이라고 한다.

명령 인터프리터는 사용자가 지정한 명령을 가져와서 그것을 수행한다.
이 수준에서 제공되는 많은 명령은 파일을 조작(생성, 삭제, 리스트, 프린트, 복사, 수행 등)한다.

명령어는 두 가지 일반적인 방식으로 구현된다.

  • 명령 인터프리터 자체가 명령을 실행할 코드를 가지는 경우
  • 시스템 프로그램에 의해 대부분의 명령을 구현하는 경우(rm file.txt)

2.2.2. 그래픽 기반 사용자 인터페이스

GUI에서는 데스크톱이라고 특정지어지는 마우스를 기반으로 하는 윈도 메뉴 시스템을 사용한다.
사용자는 마우스를 움직여 마우스 포인터를 프로그램, 파일, 시스템 기능 등을 나타내는 화면상의 이미지(아이콘)에 위치시킨다.
마우스 포인터의 위치에 따라, 마우스 버튼을 누름으로써 프로그램을 호출하거나 파일 혹은 디렉터리를 선택할 수도 있고, 또는 명령을 포함한 메뉴를 잡아당길 수도 있다.

2.2.3. 터치스크린 인터페이스

스마트폰 및 휴대용 태블릿 컴퓨터는 일반적으로 터치스크린 인터페이스를 사용한다.
사용자는 터치스크린에서 손가락을 누르거나 스와이프 하는 등의 제스처를 취하여 상호 작용한다.

2.2.4. 인터페이스의 선택

컴퓨터를 관리하는 시스템 관리자와 파워 유저들은 명령어 라인 인터페이스를 사용한다.
몇몇 시스템에서는 GUI를 통해서는 시스템 기능의 일부만을 이용할 수 있고 자주 쓰이지 않는 기능은 명령어 라인을 사용할 수 있는 사용자만이 이용할 수 있다.

명령어 라인 인터페이스는 프로그래밍 가능하므로, 반복적으로 해야 할 작업을 쉽게 할 수 있다.
이러한 프로그램은 실행 가능한 기계어 코드로 컴파일되지는 않지만 명령어 라인 인터페이스에 의해 번역되면서 실행될 수 있다. 이를 셸 스크립트라고 한다.

2.3. 시스템 콜

시스템 콜은 운영체제에 의해 사용 가능하게 된 서비스에 대한 인터페이스를 제공한다.
특정 저수준 작업은 어셈블리 명령을 사용하여 작성되어야 하더라도 이러한 호출은 일반적으로 C/C++로 작성된 함수 형태로 제공된다.

2.3.1. 예제

한 파일로부터 데이터를 읽어서 다른 파일로 복사하는 프로그램을 작성한다고 가정해보자.
프로그램이 필요로 하는 첫 번째 입력은 두 개의 파일, 즉 입력 파일과 출력 파일의 이름일 것이다.
이름을 지정하는 방법은 다양할 수 있는데, 한 가지 방법은 명령의 일부로 두 파일의 이름을 전달하는 것이다. cp in.txt out.txt
두 번째 방법은 대화형 시스템에서 프로그램이 사용자에게 이름을 요청하는 것이다.

두 개의 파일 이름이 얻어지면, 프로그램은 반드시 입력 파일을 오픈하고 출력 파일을 생성한 후 오픈한다.
각각의 연산은 또 다른 시스템 콜을 필요로 하며, 각 시스템 콜에서 오류가 발생하면 처리되어야 한다.
존재하지 않는 파일이거나, 출력 파일 이름이 이미 존재하는 경우 프로그램을 중단하거나, 기존 파일을 삭제하는 등의 처리가 필요하다.

두 개의 파일이 준비되면, 입력 파일로부터 읽어서(하나의 시스템 콜), 출력 파일에 기록(또 다른 시스템 콜)하는 루프에 들어가게 된다.
각 읽기와 쓰기는 가능한 여러 가지 오류 상황의 정보(파일의 끝, 읽기 하드웨어 오류, 디스크 공간 부족 등)를 반환해야 한다.

전체 파일이 복사된 후, 프로그램은 두 개의 파일을 닫고(2개의 시스템 콜), 콘솔 또는 윈도에 메시지를 기록하고(추가의 시스템 콜들), 결국 정상적으로 종료하게 된다.

2.3.2. 응용 프로그래밍 인터페이스

대부분의 응용 프로그램 개발자들은 응용 프로그램 인터페이스(API; Application Programming Interface)에 따라 프로그램을 설계한다.
API는 각 함수에 전달되어야 할 매개변수들과 프로그래머들이 기대할 수 있는 반환 값을 포함하여 응용 프로그래머가 사용 가능한 함수의 집합을 명시한다.
응용 프로그래머가 사용 가능한 가장 흔한 세 가지 API는 Windows API, POSIX API, Java API이다.
UNIX와 Linux 시스템에서 C로 작성된 프로그램을 위해서 제공되는 라이브러리는 libc로 불린다.

API를 구성하는 함수들은 통상 응용 프로그래머들을 대신하여 실제 시스템 콜을 호출한다.
예를 들어, Windows 함수 CreateProcess는 실제로 Windows 커널의 NtCreateProcess 시스템 콜을 호출한다.

응용 프로그래머들은 실제 시스템 콜을 호출하는 것보다 API에 따라 프로그래밍하는 것을 선호한다.

  • API에 따라 프로그램을 설계하는 응용 프로그래머는 자신의 프로그램이 같은 API를 지원하는 어느 시스템에서건 컴파일되고 실행된다는 것을 기대할 수 있다.
  • 시스템 콜은 종종 좀 더 자세한 명세가 필요하고 프로그램상에서 작업하기가 더 어렵다.

실행시간 환경(RTE; Runtime Environment)은 컴파일러 또는 인터프리터를 포함하여 특정 프로그래밍 언어로 작성된 응용 프로그램을 실행하는 데 필요한 전체 소프트웨어 제품군과 라이브러리 또는 로더와 같은 다른 소프트웨어를 함께 가리킨다.
RTE는 운영체제가 제공하는 시스템 콜에 대한 연결고리 역할을 하는 시스템 콜 인터페이스를 제공한다.
시스템 콜 인터페이스는 API 함수의 호출을 가로채어 필요한 운영체제 시스템 콜을 호출한다.
통상 각 시스템 콜에는 번호가 할당되고 시스템 콜 인터페이스는 이 번호에 따라 색인되는 테이블을 유지한다.
시스템 콜 인터페이스는 의도하는 시스템 콜을 호출하고 시스템 콜의 상태와 반환 값을 돌려준다.

호출자는 시스템 콜이 어떻게 구현되고 실행 중 무슨 작업을 하는지 아무것도 알 필요가 없다.
호출자는 단지 API를 준수하고 시스템 콜의 결과로서 운영체제가 무엇을 할 것인지만 이해하면 된다.

시스템 콜은 사용되는 컴퓨터에 따라 다른 방법으로 발생한다.
종종, 단순히 원하는 시스템 콜이 무엇인지보다 더 많은 정보가 요구될 수도 있다.
필요한 정보의 유형과 양은 특정 운영체제와 호출에 따라 다양하다.

운영체제에 매개변수를 전달하기 위해서 세 가지 일반적인 방법을 사용한다.
가장 간단한 방법은 레지스터 내에 전달하는 것이다.
레지스터보다 더 많은 매개변수를 요구하는 경우 메모리 내의 블록이나 테이블에 저장되고, 블록의 주소가 레지스터 내에 매개변수로 저장된다.
매개변수는 스택에 push하여 전달될 수도 있다.

2.3.3. 시스템 콜의 유형

시스템 콜은 다섯 가지 중요한 범주, 즉 프로세스 제어, 파일 조작, 장치 조작, 정보 유지 보수, 통신과 보호 등으로 묶을 수 있다.

  • 프로세스 제어
    • CreateProcess, fork
    • ExitProcess, exit
    • WaitForSingleObject, wait
  • 파일 관리
    • CreateFile, open
    • ReadFile, read
    • WriteFile, write
    • CloseHandle, close
  • 장치 관리
    • SetConsoleMode, ioctl
    • ReadConsole, read
    • WriteConsole, write
  • 정보 유지
    • GetCurrentProcessID, getpid
    • SetTimer, alarm
    • Sleep, sleep
  • 통신
    • CreatePipe, pipe
    • CreateFileMapping, shm_open
    • MapViewOfFile, mmap
  • 보호
    • SetFileSecurity, chmod
    • InitializeSecurityDescriptor, umask
    • SetSecurityDescriptorGroup, chown

2.3.3.1. 프로세스 제어

실행 중인 프로그램은 수행을 정상적으로(end) 또는 비정상적으로(abort) 멈출 수 있어야 한다.
현재 실행 중인 프로그램을 중지하기 위해 시스템 콜이 호출되거나 프로그램에 문제가 발생해 오류 트랩을 유발할 경우, 때때로 메모리 덤프가 행해지고 오류 메시지가 생성된다.
이 덤프는 특별한 로그 파일이나 디스크에 기록되고 문제의 원인을 결정하기 위해 디버거에 의해 검사될 수 있다.

정상적이거나 비정상적인 상황에서 운영체제는 명령 인터프리터로 제어를 전달해야 한다.
대화식 시스템에서 명령 인터프리터는 단순히 다음 명령을 계속 수행하며, 사용자가 오류에 응답하는 적절한 명령을 내릴 것을 가정한다.
GUI 시스템에서는 팝업 윈도가 사용자에게 오류를 알리고 지시를 기라딘다.
일부 시스템에서는 오류가 발생할 경우 특별한 복구 행위를 지시하는 제어 카드를 허용한다.

프로그램이 입력에서 오류를 발견하고 비정상적으로 종료하기를 원한다면, 프로그램은 오류 수준을 정의하기를 원할 수도 있다.
더 높은 등급의 오류 매개변수를 전달함으로써 더 치명적인 오류를 나타낼 수 있다.
이렇게 함으로써 정상 종료를 등급 0의 오류로 정의하여 정상 종료와 비정상 종료를 결합할 수도 있다.

한 프로그램을 실행하고 있는 프로세스가 다른 프로그램을 적재하고 실행하기를 원할 수 있다.
이 기능은 명령 인터프리터가 사용자 명령 또는 마우스의 클릭을 통하여 지시된 프로그램을 실행하는 것을 허용한다.

그렇다면 적재된 프로그램이 종료되었을 때 어디로 제어를 되돌려주는가?
새로운 프로그램이 종료되었을 때 제어가 기존 프로그램으로 돌아간다면, 우리는 반드시 기존 프로그램의 메모리 이미지를 보관해야 한다.
따라서 우리는 실질적으로 한 프로그램이 다른 프로그램을 호출하는 기법을 만든 셈이 된다.
만약 두 프로그램이 병행하게 수행된다면, 우리는 다중 프로그래밍 될 새로운 프로세스를 생성한 것이다. (create_process())

우리가 새로운 잡이나 프로세스를 생성한다면 그 실행을 제어할 수 있어야 한다.
이러한 제어는 잡의 우선순위, 최대 허용 실행 시간 등을 포함하여 잡 혹은 프로세스의 속성들을 결정하고 재설정할 수 있는 능력이 필요하다.(get_process_attributes(), set_process_attributes())
또한, 새로 생성한 잡이나 프로세스가 잘못되었거나 더 이상 필요 없다면 종료하기를 원할 수 있다.(terminate_process())

새로운 프로세스를 생성한 후에는, 이들의 실행이 끝나기를 기다려야 할 필요가 있을 수 있다.
우리는 일정 시간만큼 기다리기를 원할 수 있다.(wait_time)
그리고 더욱 가능성이 큰 경우는 우리가 특정 이벤트가 일어날 때까지 기다리는 것이다.(wait_event) 그 경우 프로세스들은 그 이벤트가 일어나면 신호를 보내야 한다.(signal_event)

빈번하게 둘 이상의 프로세스들은 데이터를 공유한다.
공유되는 데이터의 일관성을 보장하기 위해 운영체제는 종종 프로세스가 공유 데이터를 잠글 수 있는 시스템 콜을 제공한다. 그러면 잠금이 해제될 때까지 어느 프로세스도 데이터에 접근할 수 없게 된다.
통상 그런 시스템은 acquire_lock()release_lock() 시스템 콜을 제공한다.

Arduino는 마이크로 컨트롤러와 다양한 이벤트에 반응하는 입력 센서로 구성된 간단한 하드웨어 플랫폼이다.
Aurduino 응용 프로그램은 PC에서 작성한 후 USB 연결을 통해 컴파일된 프로그램(스케치)을 PC에서 Arduino에 플래시 메모리로 업로드한다.
표준 Arduino 플랫폼은 운영체제가 제공되지 않으며, 부트로더로 불리는 작은 소프트웨어가 스케치를 Arduino 메모리의 특정 영역으로 적재한다.
또한 Arduino는 하드웨어 입력 센서 이외의 사용자 인터페이스를 제공하지 않는다.

FreeBSD는 다중 태스킹 시스템의 예시이다.
사용자가 시스템에 로그인할 때 사용자가 선택한 셸이 실행되어 명령을 기다렸다가 사용자가 요청한 프로그램을 수행한다.
FreeBSD는 다중 태스킹 시스템이므로 명령 인터프리터는 다른 프로그램이 실행되는 동안 수행을 계속할 수 있다.
새로운 프로세스를 시작하기 위해 셸은 fork() 시스템 콜을 실행하며, 선택된 프로그램이 exec() 시스템 콜을 통해 메모리에 적재되고, 프로그램이 실행된다.
명령이 내려진 방법에 따라 셸은 프로세스가 종료하기를 기다리거나 백그라운드에서 프로세스를 수행한다.
프로세스가 백그라운드에서 수행될 때, 그 프로세스는 키보드로부터 직접 입력을 받을 수 없다. 이는 셸이 그 자원을 사용하고 있기 때문이다. 따라서 입출력은 파일, 또는 GUI 인터페이스를 통해 행해진다.
반면 사용자는 셸에게 다른 프로그램을 수행하도록 요청하거나, 수행 중인 프로세스의 진행 상황을 감시하게 하거나, 그 프로그램의 우선순위를 변경하는 등의 요청을 자유롭게 할 수 있다.
프로세스가 끝나면, 종료하기 위해 exit() 시스템 콜을 호출하며, 호출한 프로세스에게 상태 코드를 돌려준다.

2.3.3.2. 파일 관리

우리는 파일을 생성하고 삭제할 수 있어야 한다. 이들 시스템 콜은 파일 이름이나 속성의 일부를 요구한다.
파일이 생성되면 그것을 열고 사용한다. 또한 읽고, 쓰고, 위치 변경(되감기 혹은 파일의 끝으로 건너뛰기 등)할 수 있다.
마지막으로, 파일을 더 이상 사용하지 않음을 나타내는 파일 닫기가 필요하다.

파일 시스템이 파일을 조직하기 위해 디렉터리 구조를 가진다면, 디렉터리에 대해서도 이와 같은 연산 집합이 필요하다.
추가로, 파일이나 디렉터리에 대해 여러 속성의 값을 결정할 수 있어야 하고, 필요하다면 그것을 재설정할 수 있어야 한다. 파일 속성은 파일 이름, 유형, 보호 코드, 회계 정보 등을 포함한다.
이러한 기능을 위해서는 최소한 파일 속성 획득과 파일 속성 설정의 두 시스템 콜이 필요하다.

2.3.3.3. 장치 관리

프로세스는 작업을 계속 수행하기 위해 추가 자원(주기억장치, 디스크 드라이브, 파일 등)이 필요할 수 있다.
자원들을 사용할 수 있다면 자원이 주어지고, 제어가 사용자 프로그램으로 복귀될 수 있다.

운영체제에 의해 제어되는 다양한 자원들은 장치로 간주될 수 있다.
이 장치들의 일부는 물리 장치이고 다른 장치들은 추상적, 혹은 가상적 장치로 생각할 수 있다.
다수의 사용자가 동시에 사용하는 시스템은 독점적인 장치 사용을 보장받기 위해 우선 그 장치를 요청하는 것을 요구한다.
그 장치의 사용이 끝나면, 우리는 그것을 반드시 방출해야 한다. 이는 파일의 열기, 닫기 시스템 콜과 비슷하다.
장치를 요청하고, 파일과 마찬가지로 그 장치를 읽고, 쓰고, 위치변경할 수 있다.
입출력 장치와 파일 간에는 유사성이 매우 많으므로 UNIX를 포함한 많은 운영체제가 이들 둘을 통합된 파일-장치 구조로 결합하였다.

2.3.3.4. 정보 유지 관리

많은 시스템 콜은 단순히 사용자 프로그램과 운영체제 간의 정보 전달을 위해 존재한다.
예를 들어, 시스템 대부분은 현재 시간과 날짜를 되돌려 주는 시스템 콜을 가지고 있다.
또 다른 시스템 콜은 운영체제 버전 번호, 자유 메모리 또는 자유 디스크 공간 등과 같은 시스템에 관한 정보를 알려준다.

프로그램 디버깅에 도움이 되는 시스템 콜도 있다. 많은 시스템이 메모리를 덤프하기 위한 시스템 콜을 제공한다.
Linux 시스템에서 사용 가능한 프로그램 strace는 실행될 때마다 각 시스템 콜을 나열한다.
마이크로프로세서조차도 한 명령어 실행(Single Step)이라고 하는 CPU 모드를 제공하며, 이 모드에서 모든 명령어 실행 후에 트랩이 CPU에 의해 실행된다.
트랩은 일반적으로 디버거에 의해 포착된다.

많은 운영체제는 프로그램의 시간 프로파일을 제공한다. 시간 프로파일은 그 프로그램이 특정 위치, 혹은 위치의 집합에서 수행한 시간의 양을 나타낸다.
시간 프로파일은 추적 설비나 정규 타이머 인터럽트가 필요하다. 타이머 인터럽트가 발생할 때마다 프로그램 카운터의 값이 기록된다.

운영체제는 현재 운영되고 있는 모든 프로세스에 관한 정보를 가지고 있으며, 이러한 정보에 접근하기 위한 시스템 콜이 있다.
일반적으로, 그 프로세스 정보를 획득하고 설정하기 위한 시스템 콜도 있다.

2.3.3.5. 통신

통신 모델에는 메시지 전달과 공유 메모리의 두 가지 일반적인 모델이 있다. 시스템 대부분은 둘 다 구현한다.

메시지 전달 모델에서는 통신하는 두 프로세스가 정보를 교환하기 위하여 서로 메시지를 주고받는다.
메시지는 두 프로세스 사이에 직접 교환되거나 우편함을 통하여 간접적으로 교환될 수 있다.
통신이 이루어지려면 연결이 반드시 열려야 하며, 상대 통신자가 동일한 CPU에 있는 프로세스든지 통신 네트워크에 의해 연결된 다른 컴퓨터에 있는 프로세스이든지 간에 그 이름을 반드시 알고 있어야 한다.
네트워크의 각 컴퓨터는 호스트 이름을 가지며, 각 컴퓨터는 이들 이름으로 일반적으로 알려져 있다.
마찬가지로 각 프로세스는 프로세스 이름을 가지고 있으며, 운영체제에 의해 동등한 식별자로 변환되고, 이 식별자는 운영체제가 그 프로세스를 가리키는 데 사용할 수 있다.
수신 프로세스는 통상 통신이 일어날 수 있도록 accept_connection() 호출에 자신의 허가를 제공한다.
연결을 받아들일 프로세스들의 대부분은 특수 목적의 디먼(daemon)으로서, 그러한 목적을 위해 제공된 시스템 프로그램들이다.
디먼은 연결을 위해 대기 호출을 수행하고 연결이 이루어질 때 깨어난다.
클라이언트로 알려진 통신의 출발지와 서버로 알려진 수신 디먼은 여러 시스템 콜을 통해 메시지들을 교환한다.

공유 메모리 모델에서, 프로세스는 다른 프로세스가 소유한 메모리 영역에 대한 접근을 위해 shared_memory_create(), shared_memory_attach() 시스템 콜을 사용한다.
정상적으로 운영체제는 한 프로세스가 다른 프로세스의 메모리에 접근하는 것을 막으려고 한다.
공유 메모리는 두 개 이상의 프로세스가 이러한 제한을 제거하는 데 동의할 것을 필요로 한다. 이후, 이들 프로세스는 이러한 공유 영역에서 데이터를 읽고 씀으로써 정보를 교환할 수 있다.
데이터의 형식은 운영체제의 제어 하에 있는 것이 아니라 이들 프로세스에 의해 결정된다.
프로세스는 또한 동일한 위치에 동시에 쓰지 않도록 보장할 책임을 진다.

메시지 전달은 피해야 할 충돌이 없으므로 소량의 데이터를 교환할 때 유용하며, 컴퓨터 간의 통신을 위해 메모리 공유보다 구현하기가 쉽다.
공유 메모리는 한 컴퓨터 안에서는 메모리 전송 속도로 수행할 수 있으므로 최대 속도와 편리한 통신을 제공한다. 하지만, 보호와 동기화 부분에서 여러 문제점을 가지고 있다.

2.3.3.6. 보호

보호는 컴퓨터 시스템이 제공하는 자원에 대한 접근을 제어하기 위한 기법을 지원한다.
운영체제는 파일과 디스크 같은 자원의 허가 권한을 설정하는 시스템 콜과, 특정 사용자가 지정된 자원에 대해 접근이 허가 혹은 불허되었는지 명시하는 시스템 콜을 제공한다.

2.4. 시스템 서비스

시스템 서비스는 시스템 유틸리티로도 알려진, 프로그램 개발과 실행을 위해 더 편리한 환경을 제공한다.

  • 파일 관리: 파일과 디렉터리를 생성, 삭제, 복사, 개명, 인쇄, 열거하고 조작한다.
  • 상태 정보: 날짜, 시간, 사용 가능한 메모리와 디스크 공간의 양, 사용자 수 등의 정보를 단말기나 다른 출력 장치 혹은 파일로 포맷하여 인쇄하거나 GUI의 윈도에 표시한다.
  • 파일 변경: 디스크나 다른 저장 장치에 저장된 파일의 내용을 생성하고 변경하기 위해 문장 편집기를 사용할 수 있다.
  • 프로그래밍 언어 지원: 일반적인 프로그래밍 언어들에 대한 컴파일러, 어셈블러, 디버거 및 해석기가 종종 운영체제와 함께 사용자에게 제공되거나 별도로 다운로드받을 수 있다.
  • 프로그램 적재와 수행: 프로그램이 실행되려면 컴파일 후 메모리에 적재되어야 한다. 시스템은 절대 로더, 재배치 가능 로더, 링키지 에디터와 중첩 로더 등을 제공할 수 있다.
  • 통신: 프로세스, 사용자, 그리고 다른 컴퓨터 시스템들 사이에 가상 접속을 이루기 위한 기법을 제공한다.
  • 백그라운드 서비스: 시스템이 정지될 때까지 계속해서 실행되는 프로세스가 존재한다.

운영체제 대부분은 시스템 프로그램과 함께 유용한 프로그램(응용 프로그램)들을 제공한다.

2.5. 링커와 로더

프로그램은 디스크에 이진 실행 파일로 존재하며, CPU에서 실행하려면 프로그램을 메모리로 가져와 프로세스 형태로 배치되어야 한다.
img2

  • 소스 파일은 임의의 물리 메모리 위치에 적재되도록 설계된 오브젝트 파일로 컴파일된다. (재배치 가능 오브젝트 파일)
  • 링커는 이러한 재배치 가능 오브젝트 파일을 하나의 이진 실행 파일로 결합한다.
    링킹 단계에서 표준 C 또는 수학 라이브러리와 같은 다른 오브젝트 파일 또는 라이브러리도 포함될 수 있다.
  • 로더는 이진 실행 파일을 메모리에 적재하며, CPU 코어에서 실행할 수 있는 상태가 된다.
    링크 및 로드와 관련된 활동은 재배치로, 프로그램 부분에 최종 주소를 할당하고 프로그램 코드와 데이터를 해당 주소와 일치하도록 조정하여 프로그램이 실행될 때 코드가 라이브러리 함수를 호출하고 변수에 접근할 수 있게 한다.

시스템 대부분은 프로그램이 적재될 때 라이브러리를 동적으로 링크할 수 있게 한다.
예를 들어, Windows는 동적 링킹 라이브러리(DLL; Dynamic Linking Library)를 지원한다.
실행 파일에서 사용되지 않을 수 있는 라이브러리를 링크하고 로드하지 않아도 된다. 대신, 라이브러리는 조건부로 링크되며 프로그램 실행 시간에 필요한 경우 적재된다.

오브젝트 파일 및 실행 파일은 일반적으로 표준화된 형식을 가진다. 이 표준 형식은 컴파일된 기계 코드 및 프로그램에서 참조되는 함수 및 변수에 대한 메타데이터를 포함하는 기호 테이블을 포함한다.
UNIX 및 Linux 시스템의 경우 이 표준 형식을 ELF라고 한다.
Windows 시스템은 PE 형식을 사용하고, macOS는 Mach-O 형식을 사용한다.

2.6. 응용 프로그램이 운영체제마다 다른 이유

기본적으로 한 운영체제에서 컴파일된 응용 프로그램은 다른 운영체제에서 실행될 수 없다.
각 운영체제는 고유한 시스템 콜 집합을 제공한다. 어느 정도 같더라도 다른 장벽으로 인해 응용 프로그램을 다른 운영체제에서 실행하기 어렵다.

다음 세 가지 방법 중 한 가지를 사용하여 응용 프로그램이 여러 운영체제에서 실행될 수 있게 만들 수 있다.

  • 운영체제마다 인터프리터가 제공되는 인터프리터 언어로 작성될 수 있다. (Python, Ruby)
  • 실행 중인 응용 프로그램을 포함하고 있는 가상 머신을 가진 언어로 작성될 수 있다. (Java)
  • 컴파일러가 기기 및 운영체제 고유의 이진 파일을 생성하는 표준 언어 또는 API를 사용할 수 있다.

일반적으로 응용 프로그램의 이동성이 부족한 데에는 여러 가지 원인이 있다.

  • 각 운영체제에는 헤더, 명령어 및 변수의 배치를 강제하는 응용 프로그램 이진 형식이 있다.
  • CPU는 다양한 명령어 집합을 가지며 해당 명령어가 포함된 응용 프로그램만 올바르게 실행할 수 있다.
  • 운영체제는 다양한 활동을 요청할 수 있는 시스템 콜을 제공하는데, 피연산자, 피연산자 순서, 호출 방법, 시스템 콜 번호, 반환 결과 등이 운영체제별로 여러 측면에서 다르다.

아키텍처 수준에서 이진 코드의 여러 구성요소가 주어진 아키텍처에서 특정 운영체제와 상호 작용할 수 있는 방법을 정의하는 데 ABI(Application Binary Interface)가 사용된다.
ABI는 주소 길이, 시스템 콜에 매개변수를 전달하는 방법, 런타임 스택 구성, 시스템 라이브러리의 이진 형식 및 데이터 유형의 크기 등의 하위 수준의 세부 정보를 명시한다.
일반적으로 ABI는 특정 아키텍처에 대해 명시되므로, 아키텍처 수준의 API로 볼 수 있다.
이진 실행 파일이 특정 ABI에 따라 컴파일되고 링크된 경우 해당 ABI를 지원하는 다른 시스템에서 실행될 수 있어야 한다.
그러나 특정 아키텍처에서 실행되는 특정 운영체제에 대해 ABI가 정의되어 있기 때문에 ABI는 플랫폼 간 호환성을 거의 제공하지 않는다.

2.7. 운영체제 설계 및 구현

2.7.1. 설계 목표

[생략]

2.7.2. 기법과 정책

한 가지 중요한 원칙은 기법으로부터 정책을 분리하는 것이다.

  • 기법은 어떤 일을 어떻게 할 것인가를 결정하는 것
  • 정책은 무엇을 할 것인가를 결정하는 것

정책은 장소가 바뀌거나 시간이 흐름에 따라 변경될 수 있다. 최악의 경우, 정책의 각 변경이 저변에 깔린 기법의 변경을 요구하게 된다.
여러 정책에서 사용되기에 충분한 융통성 있는 일반적인 기법이 더 바람직하다. 그렇게 되면 정책의 변경은 시스템의 일부 매개변수만을 재정의하도록 요구한다.

마이크로 커널 기반 운영체제는 프리미티브 빌딩 블록의 기본 집합을 구현함으로써 기법과 정책의 분리를 극단적으로 추구한다.
이 블록들은 정책으로부터 거의 자유로우며, 더 고급의 기법과 정책드링 사용자 생성 커널 모듈이나 사용자 프로그램 자체를 통해 첨가될 수 있도록 한다.
반면 Windows는 모든 장치에서 전체적인 모양과 느낌을 통일하기 위해 기법과 정책이 밀접해지도록 인코딩하였다. 인터페이스 자체가 커널 및 시스템 라이브러리에 내장되어 있으므로 모든 응용 프로그램의 인터페이스는 비슷하다.

2.7.3. 구현

초기 운영체제는 어셈블리 언어로 작성되었지만, 이제 대부분은 C/C++과 같은 고급 언어로 작성되며, 극히 일부의 시스템이 어셈블리 언어로 작성된다.
둘 이상의 고급 언어가 사용될 수도 있다. 커널의 최하위 레벨은 어셈블리와 C로 작성될 수 있으며, 상위 레벨 루틴은 C/C++로, 시스템 라이브러리는 C++/상위 레벨 언어로 작성될 수 있다.
Android의 경우 커널은 대부분 C로 작성되어 있고, 일부 어셈블리 언어가 함께 사용된다.

운영체제를 구현하기 위해 고급 언어나 최소한 시스템 구현 언어를 사용함으로써, 코드를 빨리 작성할 수 있고, 간결하고, 이해하기 쉽고, 디버그하기 쉽다.
도한, 컴파일러 기술이 향상됨으로써 단순한 재 컴파일에 의해 전체 운영체제를 위해 생성된 코드를 향상시킨다.
마지막으로, 운영체제가 고급 언어로 작성된 경우 다른 하드웨어로 이식하는 것이 훨씬 쉽다.

운영체제를 고급 수준 언어로 구현하는 것에 대한 단점은 속도가 느리고 저장 장치가 많이 소요되는 것이다. 그러나 이것은 현재의 시스템에서는 주된 문제가 아니다.
전문적인 어셈블리어 프로그래머는 효율적인 작은 루틴을 생산할 수 있지만, 적용하여 우수한 코드를 생산할 수 있다.«br>

운영체제의 주요 성능 향상은 우수한 어셈블리어 코드보다는 좋은 자료구조와 알고리즘의 결과일 가능성이 크다.
운영체제가 크긴 하지만, 단지 소량의 코드(인터럽트 핸들러, 입출력 관리자, 메모리 관리자와 CPU 스케줄러 등)만이 고성능이 중요하다.

2.8. 운영체제 구조

현대 운영체제와 같이 크고 복잡한 시스템은 적절하게 동작하고 쉽게 변경될 수 있으려면 쉽게 제작되어야 한다.
일반적인 접근 방법은 한 개의 일관된 시스템보다는 태스크를 작은 구성요소로 분할하는 것이다.
각 모듈은 신중히 정의된 인터페이스와 기능들을 가진, 시스템의 잘 정의된 부분이어야 한다.

2.8.1. 모놀리식 구조

운영체제를 구성하는 가장 간단한 구조는, 구조가 아예 없는 것이다. 커널의 모든 기능을 단일 주소 공간에서 실행되는 단일 정적 이진 파일에 넣는 것이다.

최초의 UNIX 운영체제는 커널과 시스템 프로그램 두 부분으로 구성되며, 커널은 여러 가지 인터페이스와 장치 드라이버로 나뉜다.
img3

시스템 콜 인터페이스 아래와 물리적 하드웨어 위의 모든 것이 커널이며, 커널은 시스템 콜을 통해 파일 시스템, CPU 스케줄링, 메모리 관리 그리고 다른 운영체제 기능을 제공한다.
이는 하나의 주소 공간으로 결합하기에는 엄청나게 많은 기능이다.

Linux 운영체제는 UNIX에 기반을 두고 있으며 다음과 유사하게 구성된다.
img4

응용 프로그램은 일반적으로 커널에 대한 시스템 콜 인터페이스와 통신할 때 glibc 표준 C 라이브러리를 사용한다.
Linux 커널은 단일 주소 공간에서 커널 모드로 전부 실행된다는 점에서 모놀리식이지만, 런타임 중에 커널을 수정할 수 있는 모듈식 설계를 갖추고 있다.

모놀리식은 명백히 단순하지만, 이 구조는 구현 및 확장하기 어렵다.
하지만, 시스템 콜 인터페이스에는 오버헤드가 거의 없고 커널 안에서의 통신 속도가 빠르다는 이점이 있다.

2.8.2. 계층적 접근

시스템의 한 부분을 변경하면 다른 부분에 광범위한 영향을 줄 수 있으므로 모놀리식 접근법은 종종 밀접하게 결합된 시스템으로 불린다.
대안으로 느슨하게 결합된 시스템을 설계할 수 있다. 이러한 시스템은 기능이 특정 기능 및 한정된 기능을 가진 개별적이며 작은 구성요소로 나뉜다. 이 모든 구성요소가 합쳐져 커널을 구성한다.
이 방식의 장점은, 한 구성요소의 변경이 해당 구성요소에만 영향을 미치고 다른 구성요소에는 영향을 미치지 않으므로 시스템 구현자가 시스템의 내부 작동을 더 자유롭게 생성하고 변경할 수 있다.

시스템은 다양한 방식으로 모듈화될 수 있는데, 그 중 한 가지 방식이 계층적 접근 방식이다.
운영체제는 여러 개의 층으로 나누어지며, 최하위 층(층 0)은 하드웨어, 최상위 층(층 N)은 사용자 인터페이스이다.

운영체제 층은 데이터와 이를 조작하는 연산으로 구성된 추상된 객체의 구현이다.
전형적인 운영체제 층은 자료구조와 상위층에서 호출할 수 있는 루틴의 집합으로 구성된다.
운영체제 층은 다시 하위 층에 대한 연산을 호출할 수 있다.

계층적 접근 방식의 주된 장점은 구현과 디버깅의 간단함에 있다. 층들은 단지 자신의 하위층들의 서비스와 기능들만을 사용하도록 선택된다.
첫 번째 층은 정의에 의해 하드웨어만을 사용하여 이 층의 기능을 구현하므로, 나머지 시스템에 신경쓰지 않고 디버깅할 수 있다.
첫 번째 층의 디버깅이 끝나면, 두 번째 층을 디버깅하는 동안 그것이 정확하게 동작한다고 가정할 수 있으며, 이 과정이 반복된다.
어느 층의 디버깅 중 오류가 발견되면, 그 하위 층은 이미 디버깅되었기 때문에 오류는 반드시 그 층에 있다.

계층화된 시스템은 컴퓨터 네트워크(TCP/IP) 및 웹 응용 프로그램에서 성공적으로 사용됐다.
그럼에도 불구하고 순수한 계층 접근 방식을 사용하는 운영체제는 비교적 적다.

  • 각 계층의 기능을 적절히 정의해야 하는 문제와 관련이 있다.
  • 이러한 시스템의 전반적인 성능은 운영체제 서비스를 얻기 위해 사용자 프로그램이 여러 계층을 통과해야 하는 오버헤드로 인해 열악하다.

2.8.3. 마이크로커널

마이크로커널은 모든 중요치 않은 구성요소를 커널로부터 제거하고, 그들의 별도 주소 공간에 존재하는 사용자 수준 프로그램으로 구현하여 운영체제를 구성하는 방법이다. 결과적으로 커널이 더 작아진다.
마이크로커널은 통신 설비 외에 추가로 최소한의 프로세스와 메모리 관리를 제공한다.
img5

마이크로커널의 주 기능은 클라이언트 프로그램과 사용자 공간에서 수행되는 다양한 서비스 간에 통신을 제공하는 것이다.
통신은 메시지 전달에 의해 제공되며, 메시지를 교환함으로써 간접적으로 상호작용한다.

마이크로커널의 한 가지 장점은 운영체제의 확장이 쉽다는 것이다.
모든 새로운 서비스는 사용자 공간에 추가되며, 커널을 변경할 필요가 없다.
커널을 변경되어야 할 때는, 마이크로커널이 작은 커널이므로 변경할 대상이 비교적 적은 경향이 있다.
결과적으로 만들어지는 운영체제는 한 하드웨어로부터 다른 하드웨어로 이식이 쉽다.
마이크로커널은 서비스 대부분이 커널이 아니라 사용자 프로세스로 수행되기 때문에 더욱 높은 보안성과 신뢰성을 제공한다.

하지만 마이크로커널은 가중된 시스템 기능 오버헤드 때문에 성능이 나빠진다.
두 개의 사용자 수준 서비스가 통신해야 하는 경우 별도의 주소 공간에 서비스가 존재하므로 메시지가 복사되어야 한다.
운영체제는 메시지를 교환하기 위해 한 프로세스에서 다음 프로세스로 전환해야 할 수도 있다.
메시지 복사 및 프로세스 전환과 관련된 오버헤드는 마이크로커널 기반 운영체제의 성장에 가장 큰 장애였다.
Windows NT 역시 첫 릴리스에는 마이크로커널 구조였으나 성능이 떨어졌고, Windows NT 4.0은 계층들을 사용자 공간으로부터 커널 공간으로 옮기고 그들을 더욱 긴밀히 통합함으로써 성능 문제를 부분적으로 개선하였다.
Windows XP가 설계될 때까지 Windows 구조는 마이크로커널보다는 모놀리식에 가까운 구조였다.

2.8.4. 모듈

운영체제를 설계하는 데 이용되는 최근 기술 중 최선책은 적재가능 커널 모듈(LKM; Loadable Kernel Module)이다.
이 접근법에서 커널은 핵심적인 구성요소의 집합을 가지며, 부팅 중 혹은 실행 중에 부가적인 서비스들을 모듈을 통해 링크할 수 있다.

설계의 주안점은 커널은 핵심 서비스를 제공하고 다른 서비스들은 커널이 실행되는 동안 동적으로 구현하는 것이다.
서비스를 동적으로 링크하는 것은 새로운 기능을 직접 커널에 추가하는 것보다 바람직하다. 특히 후자의 경우 수정 사항이 생길 때마다 커널을 다시 컴파일해야 한다.

전체적인 결과는 커널의 각 부분이 정의되고 보호된 인터페이스를 가진다는 점에서 계층 구조를 닮았지만, 모듈에서 임의의 다른 모듈을 호출할 수 있다는 점에서 계층 구조보다 유연하다.
중심 모듈은 단지 핵심 기능만을 가지고 있고 다른 모듈의 적재 방법과 모듈들과 어떻게 통신하는지 안다는 점에서는 마이크로커널과 유사하다. 그러나 통신하기 위하여 메시지 전달을 호출할 필요가 없기 때문에 더 효율적이다.

Linux는 주로 장치 드라이버와 파일 시스템을 지원하기 위해 적재가능 커널 모듈을 사용한다.
LKM은 시스템이 시작되거나 USB 장치가 실행 중인 시스템에 접속되는 경우와 같이 런타임 중 커널에 삽입될 수 있다.

2.8.5. 하이브리드 시스템

운영체제는 다양한 구조를 결합하여 성능, 보안 및 편리성 문제를 해결하려는 혼용 구조로 작성된다.
Linux는 운영체제 전부가 하나의 주소 공간에 존재하여 효율적인 성능을 제공하므로 모놀리식 구조이다. 그러나 이 운영체제들은 모듈을 사용하므로 새로운 기능을 동적으로 커널에 추가할 수 있다.
Windows도 동일한 이유로 모놀리식 구조라 할 수 있지만, 사용자 모드 프로세스로서 실행되는 분리된 서브시스템을 지원하는 등 전형적인 마이크로커널의 형태를 유지하고 있다. 또한 Windows 역시 동적으로 적재가능한 커널 모듈을 지원한다.

2.8.5.1. macOS와 iOS

두 시스템의 일반적인 아키텍처는 다음과 같다.
img6

  • 사용자 경험 층: 사용자가 컴퓨팅 장치와 상호작용할 수 있는 소프트웨어 인터페이스를 정의한다.
  • 응용 프로그램 프레임워크 층: Objective-C 및 Swift 프로그래밍 언어에 대한 API를 제공한다.
  • 코어 프레임워크 층: OpenGL을 포함한 그래픽 및 미디어를 지원하는 프레임워크를 정의한다.
  • 커널 환경: Darwin이라고도 불리는 이 환경에는 Mach 마이크로커널과 BSD UNIX 커널이 포함된다.

응용 프로그램은 사용자 경험 기능을 이용하거나 이 기능을 우회하여 응용 프로그램 프레임워크 또는 핵심 프레임워크와 직접 상호작용하도록 설계될 수 있다.
혹은 응용 프로그램은 프레임워크를 완전히 버리고 커널 환경과 직접 통신할 수 있다.

macOS와 iOS의 중요한 차이점은 다음과 같다.

  • macOS는 데스크톱 및 랩톱 컴퓨터 시스템용이므로 Intel 아키텍처에서 실행되도록 컴파일된다. iOS는 모바일 장치용으로 설계되었으므로 ARM 기반 아키텍처용으로 컴파일된다. (이제는 아닌…?)
  • iOS 커널은 전원 관리 및 공격적인 메모리 관리와 같은 모바일 시스템의 특정 기능과 요구를 해결하기 위해 약간 수정되었으며, macOS보다 보안 설정이 더 엄격하다.
  • iOS 운영체제는 일반적으로 macOS보다 개발자에게 훨씬 더 제한적이며 폐쇄적이다.

Darwin은 주로 Mach 마이크로커널과 BSD UNIX 커널로 구성된 계층화된 시스템이다.
img7

운영체제 대부분은 UNIX 및 Linux 시스템에서 표준 C 라이브러리를 통하는 것처럼 커널에 대해 하나의 시스템 콜 인터페이스를 제공하는 반면, Darwin은 Mach 시스템 콜과 BSD 시스템 콜 두 개의 시스템 콜 인터페이스를 제공한다.

시스템 콜 인터페이스 아래에서 Mach는 메모리 관리, CPU 스케줄링 및 메시지 전달 및 RPC와 같은 프로세스 간 통신 기능을 포함한 기본 운영체제 서비스를 제공한다.
Mach에서 제공하는 대부분의 기능은 커널 추상화(Mach 프로세스인 태스크, 메모리 객체 및 포트)를 통해서 사용 가능하다.
응용 프로그램은 BSD POSIX fork() 시스템 콜을 사용하여 새 프로세스를 생성할 수 있다.
Mach 및 BSD 외에도 커널 환경은 장치 드라이버 및 동적 적재가능 모듈 개발을 위한 I/O 키트를 제공한다.

Darwin은 메시지 전달 오버헤드를 줄이고자 Mach, BSD, I/O 키트 및 모든 커널 확장을 단일 주소 공간으로 결합한다.
다양한 하위 시스템이 사용자 공간에서 실행된다는 관점에서 보면 Mach는 순수한 마이크로커널이 아니다.
메시지 전달은 여전히 발생하지만 서비스가 동일한 주소 공간에 액세스할 수 있으므로 복사할 필요가 없다.

2.8.5.2. Android

Android는 Apple의 iOS와 달리 다양한 모바일 플랫폼에서 실행되며 오픈 소스이다.

img8

Android는 그래픽, 오디오 및 하드웨어 기능을 지원하는 다양한 프레임워크를 제공하는 계층화된 소프트웨어라는 점에서 iOS와 유사하다.

Android 장치의 소프트웨어 설계자는 Java 언어로 응용 프로그램을 개발하지만, 일반적으로 표준 Java API를 사용하지 않는다. Google은 Java 개발을 위해 별도의 Android API를 설계하였다.
Java 응용 프로그램은 Android Runtime ART에서 실행할 수 있는 형식으로 컴파일된다.
ART는 Android용으로 설계되어 메모리와 CPU 처리 능력이 제한적인 모바일 장치에 최적화된 가상 머신이다.
Java 프로그램은 우선 Java 바이트코드 .class 파일로 컴파일된 후 실행 가능한 .dex 파일로 변환된다.
많은 Java 가상 머신이 프로그램 효율성을 향상시키기 위해 JIT 컴파일을 수행하는 반면, ART는 AOT(Ahead-of-Time) 컴파일을 수행한다.
.dex 파일은 장치에 설치될 때 해당 기계어 코드로 컴파일되어 ART에서 실행될 수 있게 한다.

Android 개발자는 Java 네이티브 인터페이스(JNI)를 사용하는 Java 프로그램을 작성하여, 가상 머신을 우회하고 특정 하드웨어 기능에 액세스할 수 있는 프로그램을 작성할 수 있다.
JNI로 작성된된 프로그램은 일반적으로 한 하드웨어 장치에서 다른 하드웨어 장치로 이식할 수 없다.

Android 응용 프로그램에서 사용 가능한 네이티브 라이브러리 집합에는 웹 브라우저, 데이터베이스 지원 및 네트워크 소켓을 개발하기 위한 프레임워크가 포함된다.

Android는 거의 무제한의 하드웨어 장치에서 실행될 수 있으므로 Google은 하드웨어 추상화 계층(HAL)을 통해 물리적 하드웨어를 추상화하기로 선택하였다.
HAL은 카메라, GPS 및 기타 센서와 같은 모든 하드웨어를 추상화하여 특정 하드웨어와 상관없이 일관된 뷰를 응용 프로그램에게 제공한다.

Linux 시스템에서 사용하는 표준 C 라이브러리는 GNU C 라이브러리(glibc)이다.
대신 Google은 Android를 위한 Bionic 표준 C 라이브러리를 개발하였다. Bionic은 glibc보다 메모리 사용량이 적을 뿐만 아니라 모바일 장치를 특징짓는 느린 CPU를 위해 설계되었다.

Android 소프트웨어 스택의 맨 아래에는 Linux 커널이 있다.
Google은 전원 관리와 같은 모바일 시스템의 특수한 요구를 지원하기 위해 다양한 영역에서 Android에서 사용하는 Linux 커널을 수정하였다.
또한 메모리 관리 및 할당을 변경했으며 Binder로 알려진 새로운 형태의 IPC를 추가하였다.

2.9. 운영체제 빌딩과 부팅

일반적으로 운영체제는 다양한 주변장치 구성을 가진 모든 종류의 컴퓨터에서 실행되도록 설계된다.

2.9.1. 운영체제 생성

운영체제를 처음부터 생성 혹은 빌딩하는 경우 다음 절차를 밟아야 한다.

  • 운영체제 소스 코드를 작성한다.
  • 운영체제가 실행될 시스템의 운영체제를 구성한다.
  • 운영체제를 컴파일한다.
  • 운영체제를 설치한다.
  • 컴퓨터와 새 운영체제를 부팅한다.

시스템을 구성하려면 어떤 기능이 포함되는지 명시해야 하며 이는 운영체제에 따라 다르다.
일반적으로 시스템 구성 방법을 설명하는 매개변수는 특정 유형의 구성 파일에 저장되며 이 파일을 만든 후에는 여러 가지 방법으로 사용할 수 있다.

한편으로는 시스템 관리자가 이를 사용하여 운영체제 소스 코드의 사본을 수정할 수 있다. 그런 다음 운영체제가 완전히 컴파일된다.
컴파일 시 주어진 데이터 선언, 초기화 및 상수는 구성 파일에 설명된 시스템에 맞는 운영체제의 출력 오브젝트 버전을 생성한다.

상세한 조정을 할 수 없는 수준에서는 시스템 설명을 통하여 기존 라이브러리에서 사전 컴파일된 오브젝트 모듈을 선택할 수 있다. 이 모듈들이 서로 링크되어 새 운영체제가 생성된다.
이 과정을 통해 지원되는 모든 I/O 장치의 장치 드라이버를 포함한 라이브러리에서 필요한 것만 선택하여 운영체제에 링크할 수 있다.
시스템이 다시 컴파일되지 않기 때문에 시스템 생성 속도는 빠르지만 생성 시스템은 지나치게 일반적일 수 있으며 다른 하드웨어 구성을 지원하지 않을 수 있다.

다른 극단적인 경우에는 완전히 모듈 방식으로 시스템을 구성할 수 있다.
여기서 선택은 컴파일 또는 링크 시간이 아닌 실행 시간에 일어난다. 시스템 생성은 단순히 시스템 구성을 설명하는 매개변수의 설정만 하면 된다.

이러한 접근 방식의 주요 차이점은 생성된 시스템의 크기 및 일반성과 하드웨어 구성이 변경될 때 변경이 얼마나 쉬운가에 있다.
임베디드 시스템의 경우 첫 번째 접근 방식을 채택하고 특정 정적 하드웨어 구성을 위한 운영체제를 생성하는 것은 드문 일이 아니다.
그러나 데스크톱 및 랩톱 컴퓨터와 모바일 장치를 지원하는 대부분의 최신 운영체제는 두 번째 접근 방식을 채택하였다.
즉, 운영체제는 여전히 특정 하드웨어 구성을 위해 생성되지만 적재가능 커널 모듈과 같은 기술을 사용하면 시스템의 동적 변경을 위한 모듈 방식을 지원할 수 있다.

2.9.2. 시스템 부트

커널을 적재하여 컴퓨터를 시작하는 과정을 시스템 부팅이라고 한다.
시스템 대부분에서 부팅 과정은 다음과 같다.

  • 부트스트랩 프로그램(부트로더)이라 불리는 작은 코드가 커널의 위치를 찾는다.
  • 커널이 메모리에 적재되고 시작된다.
  • 커널은 하드웨어를 초기화한다.
  • 루트 파일 시스템이 마운트된다.

일부 컴퓨터 시스템은 다단계 부팅 과정을 사용한다.
컴퓨터 전원을 처음 켜면 BIOS라고 하는 비휘발성 펌웨어에 있는 소형 부트로더가 실행되며, 이 부트로더는 일반적으로 부트 블록이라고 하는 디스크의 정해진 위치에 있는 두 번째 부트로더를 적재하는 작업만 한다.
부트 블록에 저장된 프로그램은 전체 운영체제를 메모리에 적재하고 실행을 시작하기에 충분히 정교할 수 있다.
더 일반적으로, 이 부트로더는 간단한 코드로서 디스크의 주소와 부트스트랩 프로그램의 나머지 길이만 알고 있다.

많은 최신 컴퓨터 시스템이 BIOS 기반 부팅 과정을 UEFI(Unified Extensible Firmware interface)로 대체하였다.
UEFI는 64비트 시스템과 용량이 큰 디스크를 더 잘 지원하는 것을 포함하여 BIOS에 비해 몇 가지 장점이 있다.
가장 큰 장점은 UEFI가 하나의 완전한 부팅 관리자이므로 다단계 BIOS 부팅 과정보다 빠르다는 것이다.

부트로더는 BIOS에서 부팅하든 UEFI에서 부팅하든 다양한 작업을 수행할 수 있다.
커널 프로그램이 포함된 파일을 메모리에 적재하는 것 외에도 진단을 실시하여 메모리와 CPU를 점검하고 장치 검색과 같은 시스템 상태를 확인한다. 진단을 통과하면 프로그램은 부팅 과정을 계속 진행할 수 있다.
부트스트랩은 CPU 레지스터에서 장치 컨트롤러 및 메인 메모리의 내용에 이르기까지 시스템의 모든 측면을 초기화할 수 있다.
이후 운영체제를 시작하고 루트 파일 시스템을 마운트한다.

GRUB은 Linux 및 UNIX 시스템을 위한 오픈 소스 부트스트랩 프로그램이다.

공간을 절약하고 부팅 시간을 줄이기 위해 Linux 커널 이미지는 압축 파일이며 메모리에 적재된 후 압축이 풀어진다.
부팅 과정에서 부트로더는 일반적으로 initramfs로 알려진 임시 RAM 파일 시스템을 생성한다.
이 파일 시스템에는 실제 루트 파일 시스템을 지원하기 위해 설치해야 하는 드라이버와 커널 모듈이 저장되어 있다.
커널이 시작되고 필요한 드라이버가 설치되면 커널은 루트 파일 시스템을 임시 RAM 위치에서 적절한 루트 파일 시스템 위치로 전환한다.
마지막으로 Linux는 시스템의 초기 프로세스인 systemd 프로세스를 생성한 다음 다른 서비스를 시작한다.

부트 메커니즘은 부트로더와 독립적이지 않다. 따라서 BIOS와 UEFI용 특정 GRUB 부트로더 버전이 있으며 펌웨어는 어떤 특정 부트로더가 사용되는지 알아야 한다.

모바일 시스템의 부팅 과정은 기존 PC의 부팅 과정과 약간 다르다.
커널은 Linux 기반이지만 Android는 GRUB을 사용하지 않고 부트로더의 제공을 벤더에 맡긴다. 가장 일반적인 Android 부트로더는 LK(작은 커널)이다.
Android 시스템은 초기 RAM 파일 시스템뿐만 아니라 Linux와 동일한 압축 커널 이미지를 사용한다.
그러나 Linux는 필요한 드라이버가 모두 적재되면 initramfs를 폐기하지만 Android는 initramfs를 장치의 루트 파일 시스템으로 유지한다.
커널이 적재되고 루트 파일 시스템이 마운트되면 Android는 init 프로세스를 시작하고 홈 화면을 표시하기 전에 여러 서비스를 생성한다.

Windows, Linux, macOS, iOS 및 Android를 비롯한 대부분의 운영체제의 부트로더는 하드웨어 문제 진단, 손상된 파일 시스템 복구 및 운영체제 재설치 등의 작업을 할 수 있는 복구 모드 또는 단일 사용자 모드로 부팅할 수 있는 기능을 제공한다.

2.10. 운영체제 디버깅

디버깅은 하드웨어와 소프트웨어에서의 시스템 오류를 발견하고 수정하는 행위이다.
성능 문제는 버그로 간주되므로 시스템에서 처리 중에 발생하는 병목 현상을 제거하여 성능을 향상시키려는 성능 조정도 디버깅에 포함된다.

2.10.1. 장애 분석

프로세스가 실패한다면, 운영체제 대부분은 시스템 관리자 또는 문제를 발생시킨 사용자에게 문제가 발생했다는 것을 경고하기 위해 오류 정보를 로그 파일에 기록한다.
운영체제는 프로세스가 사용하던 메모리를 캡처한 코어 덤프를 취하고 차후 분석을 위해 파일로 저장한다.
실행 중인 프로그램과 코어 덤프는 디버거에 의해 검사될 수 있으며, 장애 발생 시 프로그래머가 프로세스의 코드와 메모리를 분석할 수 있도록 한다.

사용자 수준 프로세스 코드를 디버깅하는 것은 도전적인 일이며, 커널의 크기와 복잡도, 하드웨어 제어 및 사용자 수준 디버깅 도구가 없기 때문에 운영체제 커널을 디버깅하는 것은 훨씬 복잡하다.
커널 장애는 크래시라고 불리며, 프로세스 장애와 마찬가지로 오류 정보가 로그 파일에 저장되고 메모리의 상태가 크래시 덤프에 저장된다.

운영체제 디버깅와 프로세스 디버깅은 종종 두 태스크 간의 근본적인 차이에 의해 서로 다른 도구와 기법을 사용한다.
파일 시스템 코드 때문에 발생한 커널 장애는 로그를 파일 시스템에 기록하기 어려우므로, 이 용도를 위해 예약된 파일 시스템을 가지지 않은 디스크의 특정 부분에 저장한다.
커널이 복구 불가능한 오류를 탐지하면 메모리의 전체 내용 또는 적어도 시스템 메모리의 커널이 소유한 부분만이라도 이 디스크 영역에 저장한다.

2.10.2. 성능 관찰 및 조정

성능 조정은 처리 병목 지점을 제거함으로써 성능을 향상시킨다.
병목 지점을 발견하기 위해서는 시스템의 성능을 감시해야 하고, 시스템의 동작을 측정하고 표시할 수 있는 방법을 가지고 있어야 한다.
도구는 프로세스별 또는 시스템 전체의 관찰을 제공하느냐로 특징이 묘사될 수 있다. 이러한 관찰을 위해 도구는 카운터 또는 추적의 두 가지 접근 방식 중 하나를 선택할 수 있다.

2.10.2.1. 카운터

운영체제는 일련의 카운터를 통해 호출된 시스템 콜 횟수 또는 네트워크 장치 또는 디스크에 수행된 작업 수와 같은 시스템 활동을 추적한다.

  • 프로세스별: ps, top
  • 시스템 전체: vmstat, netstat, iostat

Linux 시스템의 카운터 기반 도구 대부분은 /proc 파일 시스템에서 통계를 읽는다.
이는 커널 메모리에만 존재하는 의사 파일 시스템이며, 주로 다양한 프로세스별 통계와 커널 통계를 질의하는 데 사용된다.
/proc 파일 시스템은 디렉터리 계층 구조로 구성되며, 각 프로세스의 pid가 /proc 아래의 하위 디렉터리로 표시된다.

Windows 시스템은 Windows 작업 관리자를 제공하는데, 이 도구는 현재 실행 중인 응용 프로그램과 함께 프로세스, CPU, 메모리 사용 및 네트워크 통계를 보여준다.

2.10.3. 추적

추적 도구는 시스템 콜과 관련된 단계와 같은 특정 이벤트에 대한 데이터를 수집한다.

  • 프로세스별: strace, gdb
  • 시스템 전체: perf, tcpdump

2.10.4. BCC

BCC 도구 집합은 사용하지 않을 경우에는 성능에 전혀 영향을 주지 않고, 사용 중일 때에는 그에 비례하게 성능에 영향을 주면서, 동적이고 안전한 디버깅 환경을 제공한다.

BCC(BPF Compiler Collection)는 eBPF 도구에 대한 프론트엔드 인터페이스이다.
eBPF 프로그램은 C의 부분집합을 사용하여 작성되며, eBPF 명령어로 컴파일된 후 Linux 시스템에 동적으로 삽입될 수 있다.
eBPF 명령어는 특정 이벤트(호출된 시스템 콜 등)를 캡처하거나 시스템 성능(디스크 I/O 수행에 필요한 시간 등)을 관찰하는 데 사용될 수 있다.
eBPF 명령어가 올바르게 작동하는 것을 보장하기 위해, 실행 중인 Linux 커널에 삽입하기 전 검증기를 통과해야 한다.

eBPF는 Linux 커널 내에서 추적할 수 있는 다양한 기능을 제공하지만, C 인터페이스를 사용하므로 개발이 어렵다.
BCC는 eBPF를 사용하는 도구를 더 쉽게 작성할 수 있도록 개발되었으며 Python 언어로 작성된 프론트엔드 인터페이스를 제공한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

"Operating System" 카테고리의 게시물