본문 바로가기

CS/운영체제

[운영체제] 운영체제

링커 & 로더

  • 링커 : 여러 오브젝트 파일들을 하나의 실행 가능한 바이너리 파일로 합쳐주는 프로그램으로, 단순 코드뿐만 아니라 해당 코드에 사용된 라이브러리 등도 바이너리 파일에 포함될 수 있다.
  • 로더 : 스토리지에 저장되어 있는 프로그램을 메모리 영역으로 가져오는 프로그램. 로더에 의해 로딩된 프로그램만이 운영체제에 의해 실행될 수 있다.

 최근에는 라이브러리들을 링커를 통해 직접 바이너리 파일에 넣기보다는 DLL(Dynamically Linked Libraries)의 형태로 저장한 후, 필요할 때 메모리 상으로 로드 · 공유하는 방식을 이용한다고 한다.

운영체제의 설계(Design) 및 구현(Implementation)

 운영체제가 달성하고자 하는 목표는 해당 운영체제가 요구되는 환경, 해당 운영체제를 사용하게 될 사용자의 특성 등 다양한 요소에 의해 차이가 존재할 것이다. 운영체제를 설계하는 단계에서는 해당 운영체제의 목표(goal) 및 상세한 사항(specification)을 먼저 정의해야 한다. 이렇게 정해진 구체적 목표에 따라 시스템의 타입이나 하드웨어의 선택 등 다양한 사항이 영향을 받을 수 있다.

 운영체제마다 서로 다른 목적이 있겠지만, 우선 각 운영체제가 가지게 되는 목표를 뭉뚱그려 나타내면 "사용자의 편의성" 과 "시스템 및 하드웨어의 성능 및 동작"으로 구분할 수 있다.  

  • User Goal : 신뢰성, 사용의 편의, 학습의 용이, 안전, 빠른 반응속도 등이 강조된다. 대중적으로 사용하는 휴대폰, 컴퓨터의 OS 등이 이런 목표에 의미를 둔다.
  • System Goal : 디자인, 구현, 유지보수 등이 편하고 유연하며, 에러 없이 효과적으로 동작해야 한다.

두가지 목표는 서로 어느 정도 상충되는 위치에 있다. 보통 사용자가 사용하기 편할수록 여러 가지 기능을 운영체제 자체적으로 처리해야 하므로, 가용 자원이 감소하여 성능이 떨어진다고 느끼게 된다. 반대로 성능이 좋아질수록 기능이 최소한으로 제한되는 경향성이 있다. 따라서 이 두 가지 달성 목표를 운영체제의 목적에 맞게 적절히 절충하게 된다.

정책(Policy)과 메커니즘(Mechanism)

 운영체제의 설계 단계에서는 해당 운영체제가 "어떤 작업을 수행해야 하는지"를 정의해야 한다. 우선 운영체제가 수행할 작업이 정해지면, 다음에는 해당 작업을 "어떻게 구현해야 하는지"를 고려하게 된다.

  • 정책(Policy) : 운영체제가 수행해야 하는 작업을 정의한 것.
    • ex ) 매 단위 시간마다 인터럽트를 검사한다.
  • 메커니즘(Mechanism) : 정책을 달성하는 데 사용되는 구체적인 방법 
    • ex ) CPU 보호를 위한 "타이머" 메커니즘.

 정책은 꽤 자주 변경된다. 만약 정책이 변경에 메커니즘이 종속된다면 전체 시스템 설계를 수없이 반복해야 하므로 전체 프로세스의 효율성이 크게 떨어질 것이다. 반면 정책과 매커니즘이 서로 독립된 경우, 메커니즘의 변화가 없어 정책이 자주 변경되더라도 유연하게 대응된다는 장점이 있다.

 전반적으로 정책은 메커니즘보다 먼저 고려되어야 한다. 정책은 운영체제가 달성하고자 하는 "목표"다. 운영체제의 존재 의의가 사용자를 도와 프로그램 실행을 지원해주는 것에 있다는 점을 생각해보면, 아무리 컴퓨터의 동작 방식이 좋다고 하더라도 정작 사용자를 지원한다는 목표를 실행할 수 없는 운영체제가 얼마나 쓸모없는지 생각할 수 있을 것이다. 중요한 것은 방향성이다. 방향성에 해당하는 정책이 제대로 갖춰져 있다면 메커니즘에 문제가 있더라도 결국 방향성을 따르는 쪽으로 프로그램이나 하드웨어 구조를 수정할 수 있다. 

운영체제의 구현

 보통 C, C++ 등의 고수준 언어를 이용한다. 속도는 어셈블리어보다 느릴 수 있지만, 개발 속도 등에서 확연한 차이가 있으며, 최근의 하드웨어들은 이러한 언어로 만든 프로그램들을 충분히 동작시킬 수 있다.

운영체제 구조

 초기의 운영체제는 플로피 디스크 한 장에 모두 들어갈 정도로 작았다. 그러나 현대의 운영체제로 올수록 운영체제가 수행할 수 있는 동작 및 목적이 다양해짐에 따라 10GB 도 넘는 공간을 차지하고 있다. 이때 운영체제의 계보를 간단하게 살펴보면 다음과 같다.

가장 간단한 구조 : 초기 MS-DOS

 MS-DOS는 마이크로소프트에서 개발한 운영체제로, 최소 공간에 최대한의 기능을 박아놓은 형태를 띠고 있다. 모듈화가 크게 되어있지 않으며, 기본적인 파일 몇 개만으로도 동작한다는 특징이 있다.

조금 더 복잡한, 단일(monolithic) 구조 : original UNIX

UNIX의 경우 DOS보다 조금 더 복잡한 구조를 가진다. UNIX에서는 OS을 시스템 프로그램 및 커널부로 구분한다.

  • 시스템 프로그램 : shell, command, compiler, interpreter, system libraries
  • 커널 : 시스템 콜 인터페이스, 하드웨어 인터페이스 / signal , I/O system, file system, CPU scheduling, page replacement, virtual memory, terminal driver...

 이때 커널 자체는 하나의 커다란 정적 바이너리 파일 같은 개념으로, 하나의 커널 내에 모든 기능을 넣어두고, 이를 특정 메모리 주소에 적재하여 커널이 제공해야 하는 서비스를 시스템 콜을 통해 유저 측에 전달하게 된다. 이전 방식에 비해서는 훨씬 효율적이지만, 커널 자체가 하나의 단일체를 이루고 있으므로 커널 내에 새로운 기능을 구현하거나, 확장하는 것이 어렵다는 단점이 존재한다.

계층적 접근(Layered Approach)

 운영체제를 기능 등의 조건을 통해 하위 레이어(하드웨어, 0 계층)부터 상위 레이어(유저 인터페이스, N 계층)으로 나누는 접근 방식이다. 기능의 계층화에 의해 각 계층에서는 전체 시스템에 대한 통신을 고려하는 대신, 인접한 계층 사이에서 발생하는 통신만을 고려할 수 있는 형태로 독립된다. 각 계층은 서로의 구현 내용을 전혀 알 필요가 없으며, 모듈화에 의해 특정 계층의 내부 구조가 수정되거나 변경되더라도 다른 계층에 영향을 주지 않고 유지보수가 가능해진다.

 반면 모든 계층 구조를 가진 시스템이 그러하듯이, 하위 계층의 기능을 사용하기 위해서는 다양한 계층을 통과해야만 한다. 예를 들어 유저가 하드웨어 정보를 얻기 위해서는 Layer N부터 Layer 0까지 정보가 왕복해야 한다. 또한 하위 계층 수준에서 특정 기능 자체를 지원하지 않으면, 상위 레벨에서 특정 기능에 기반한 동작을 수행할 수 없다. 만약 하위에서 파일 읽기와 관련된 기능 자체를 지원하지 않는다면, 콜 기반으로 동작하는 계층 구조 특성상 상위에서는 호출할 수 있는 콜 자체가 없으므로 파일 읽기 기능을 구현할 수 없다. 

 운영체제의 기능을 구분하는데도 문제가 있을 수 있다. 레이어가 세세하게 나뉠수록 운영체제에 존재하는 특정 기능을 도대체 어떤 계층에 부여해야 하는가에 대한 구분이 어려워진다. 다양한 계층에서 특정 기능을 가진다면, 모든 계층에 해당 기능을 넣는 편이 성능은 좋을 텐데, 계층 구조와 상충되지는 않을까? 많은 기능들이 하나의 계층이 독점하기에는 애매한 부분이 있다는 단점이 존재하여, 현재 모델만으로는 운영체제를 관리하기 어려울 수 있다.

 그럼에도 대부분의 운영체제는 일부분 계층형 구조를 가진다. 몇몇 단점은 다른 구조와 접목하여 보완된다.

마이크로 커널 구조

위키피디아 'Microkernel' 발췌. username : Wooptoo

 기존 구조에서는 커널의 역할이 방대했던 것과는 달리, 마이크로 커널 구조에서는 커널이 수행하던 많은 작업을 유저 공간으로 이동시킨다. 예를 들어, 텍스트 에디터(vim 등)는 사실 커널이 관리할 필요가 없다. 차라리 이런 기능을 유저 수준으로 올리는 대신, 상주하는 커널을 최소화하는 것이 전체 성능에 도움이 될 것이다. 

 유저 모드 수준으로 올라간 기존 기능들은 모듈화 되어 각각 특정 업무를 담당하는 모듈이 된다. 각 모듈은 커널 수준으로 메시지를 보내 서로 통신할 수 있다(message passing 방식).

  • 장점 
    • 추가 기능을 모듈 수준에서 추가할 수 있어 커널을 바꾸지 않아도 되므로, 기능 추가가 편리하다.
    • 다른 아키텍처로 OS를 퍼팅할 때 커널 부분만 교체하면 가능하므로, 기종간 이식이 편리해진다.
    • 커널 관련 코드가 최소화되어 코드가 직관적이고, 신뢰성이 높다.
    • 특정 서비스가 실패하더라도 모듈 수준에서의 실패이므로, 핵심 기능과 관련이 없어 안전성이 높다.
  • 단점
    • 메시지 기반 통신은 반드시 커널 공간을 경유하므로, 자체만으로도 오버헤드가 발생한다.

모듈

 많은 현대 운영체제에서는 LKMs(Loadable Kernel Modules) 구조를 가진다. 중요한 기능을 가지는 코어 커널을 중심으로 이외의 로딩 가능한 커널 모듈들을 컴파일 타임 혹은 런타임에 추가하여 새로운 하드웨어/ 파일 시스템에 대한 지원을 추가하거나, 시스템 호출을 추가하는 데 사용된다.

 계층 구조와는 달리 각 모듈은 직접 소통할 수 있으며, 마이크로 커널 구조와는 달리 모듈 사이에서 메시지를 이용하여 통신하지 않으므로 기존 방식들에 비해 성능 면에서도 장점이 있다. 각 모듈은 서로 독립적으로 동작하며, "커널" 모듈이므로 서로 통신이 가능하다. 필요할 때 커널을 로딩할 수 있다는 점 역시 장점이 된다.

 컴퓨터에 USB 기기를 처음 꽂을 때 (마우스나 키보드 등), 우리는 미리 해당 기기들에 대한 드라이버를 설치하지 않는다. 해당 기기들을 꽂는 시점에 운영체제가 필수적인 드라이버를 가지고 있지 않다면 동적으로 로딩되기 때문이다.

Hybrid System

현대 운영체제는 특정 체계를 고수하는 대신, 각 구조의 장점만을 모아 이용한다. 리눅스의 경우 monolithic + dynamic loading 등을 이용하며, 윈도우 운영체제는 monolithic + microkernel + message 기반으로 동작한다고 한다. 

 성능적인 면을 요구한다면, 처음부터 커널 수준에 탑재하여 바로 접근할 수 있게 만들고, 성능이 크게 중요하지 않은 영역에서는 모듈화/다이나믹 로딩을 통해 필요한 시점에 정보를 가져오도록 구성할 수 있다.


 시스템 부팅

 컴퓨터를 ROM(혹은 BIOS) 상의 커널을 bootstrap loader을 통해 메모리 상에 적재한다. 해당 코드가 실행되면 운영체제가 동작한다. 이때, 리눅스 기반에서는 어떤 커널을 실행할지에 대해 선택할 수 있는 GRUB 와 같은 것이 잇다.

운영체제와 디버깅

 디버깅은 발생한 에러나 버그를 찾고 수정하는 과정으로, 성능을 향상시키는 목적으로 수행되기도 한다. 운영체제는 에러가 발생하면 해당 에러와 관련된 정보를 제공하는 로그 파일을 생성한다.

  • core dump : 특정 프로세스에서 에러가 발생했을 때 해당 시점에서의 메모리 정보를 담고 있는 덤프 파일.
  • crash dump : 운영체제가 실패한 시점에 생성되는 전체 커널에 대한 덤프 파일.

 덤프 파일을 보고 에러를 수정할 수도 있고, bottleneck 등이 발생한 위치를 메시지를 보고 따라가며(trace listing) 분석하여 문제를 해결할 수도 있으며, profiling을 수행할 수도 있다.

 성능을 튜닝하는 경우, Window task manager 등을 이용하면 운영체제가 제공하는 OS 자원관련 정보를 얻을 수 있다.

 

'CS > 운영체제' 카테고리의 다른 글

[운영체제] CPU 스케줄링  (0) 2022.04.11
[운영체제] 스레드 & Concurrency  (0) 2022.04.06
[운영체제] 프로세스  (0) 2022.04.03
[운영체제] OS 서비스/프로그램  (0) 2022.03.15
[운영체제] 개요  (0) 2022.03.11