Back

운영체제 개요

가상화, 병행성, 지속성

운영체제의 기능

운영체제는 프로그램이 잘 실행되도록 돕는 프로그램이다. 따라서 다음과 같은 기능들을 갖는다.

  • 프로그램을 실행하기 쉽도록 해 준다.
  • 프로그램들이 메모리를 공유할 수 있도록 해 준다.
  • 프로그램들이 외부 장치들과 정보를 주고받을 수 있도록 해 준다.

즉, 시스템이 올바르고 효율적으로 동작할 수 있도록 만드는 것이 OS의 역할이다.

여기서 잠깐 프로그램이 무엇인가에 대해 간단하게 짚고 넘어가자.

프로그램이란?

프로그램은 CPU가 수행하는 인스트럭션(Instruction, 명령어)들의 목록이라고 생각할 수 있다.

즉, 프로그램을 실행한다는 것은 인스트럭션을 수행하는 것이다.

따라서 프로그램이 실행될 때는 다음과 같은 과정을 거치게 된다.

  1. Fetch : 메모리에서 인스트럭션을 하나 가져온다.

  2. Decode : 가지고 온 인스트럭션이 어떤 종류인지를 알아낸다.

  3. Execute : 인스트럭션을 실제로 수행한다.

    여기까지 완료하면 하나의 인스트럭션을 수행한 것이다.

  4. 다음 인스트럭션을 찾는다.

프로그램이 실행되고 종료되기까지 이 4개의 과정이 계속 반복된다.

이러한 과정을 거쳐 프로그램이 실행되는 동안, 운영체제는 프로그램이 원활하게 실행될 수 있도록 환경을 제공하는 역할을 한다.

운영체제의 기능과 역할은 크게 3가지이다.

  • Virtualization (가상화)
  • Concurrency (병행성)
  • Persistency (지속성)

지금부터 각각의 개념에 대해 간단하게 살펴보도록 하자.

Virtualization (가상화)

운영체제의 가장 중요한 역할 중 하나는 시스템을 가상화하여 프로그램에게 제공하는 것이다.

구체적으로는 시스템의 물리적 자원(Physical Resource)인 하드웨어(Hardward)를 프로그램이 사용하기 편리하도록 가상화하여 제공하는 것을 의미한다.

가상화된 형태는 프로그램이 사용하기 편리하다. 실제 하드웨어의 구조 및 복잡한 작동 방식, 하드웨어 공유 시 발생하는 문제 등을 신경쓰지 않아도 되기 때문이다.

이러한 특징 때문에 운영체제를 Virtual Machine이라고 부르기도 한다.

System call

시스템 자원은 가상화되어 있으므로, 프로그램들은 시스템 자원에 직접 접근할 수 없다. 따라서 프로그램들은 시스템 자원을 사용하려면 운영체제에게 요청해야 한다.

운영체제에게 시스템 자원을 요청하기 위해 특수한 함수인 System call(시스템 콜)을 사용한다.

운영체제는 프로그램들이 사용할 수 있는 다양한 시스템 콜을 제공한다.

Resource manager

운영체제는 CPU, Memory, Disk 등의 시스템 자원을 (가상화할 뿐만 아니라) 관리하는 역할을 한다.

여러 프로그램들이 동시에 실행될 수 있도록 CPU를 공유하기도 하고, 각 프로그램이 사용할 수 있도록 메모리 및 디스크를 공유해 주기도 한다.

즉, OS가 여러 프로그램들 사이에서 시스템 자원을 분배하는 중재자 역할을 한다.

CPU 가상화

실행하고자 하는 프로그램의 개수에 비해 CPU의 개수는 너무나 적다.

하지만 CPU가 단 하나만 존재하는 경우에도, CPU 가상화를 통해 여러 프로그램이 적은 수의 CPU를 동시에 사용할 수 있다.

OS는 각 프로그램들에게 자신만의 전용 CPU를 가지고 있다는 환상을 제공해 준다. 즉, 하나의 프로세스로 여러 프로그램을 실행하더라도 각 프로그램들은 자신만의 CPU를 가지고 있는 것처럼 작동한다.

메모리 가상화

CPU와 마찬가지로 실제 물리적인 메모리를 여러 프로그램이 사용할 수 있게 해 주는 것이 메모리 가상화이다.

실제 메모리는 정보를 저장 가능한 바이트(Byte)들로 이루어진 저장 공간이다. 즉, **바이트의 배열(An array of Bytes)**로 생각할 수 있다. 각 바이트에는 고유한 번호가 존재하고, 이를 **주소(Address)**라고 한다. 이 주소를 가지고 메모리를 읽고 쓸 수 있다.

OS는 각 프로그램들에게 자신만의 전용 메모리를 가지고 있다는 환상을 제공해 준다.

하지만 각 프로그램에는 실제 주소(Physical Address)가 아닌 가상 주소(Virtual Address)가 제공된다.

따라서 현재 실행 중인 프로그램들이 모두 동일한 주소에 접근하는 것 처럼 보이는 경우에도, 실제로는 다른 주소에 접근하게 된다.

OS가 중간에서 가상 주소를 실제 주소로 연결해 주기 때문에, 이러한 경우에도 프로그램들은 문제 없이 작동할 수 있다.

Concurrency (병행성)

병행성은 여러 프로그램들이 동시에 실행되어야 하기 때문에 발생하는 특징이다.

다음 코드를 통해 병행성의 특징을 알 수 있다.

// thread.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int counter = 0;
int loops;

void *worker(void *arg) {
    int i;
    for (i = 0; i < loops; i++)
        counter++;
    return NULL;
}

int main(int argc, char *argv[]) {
    . . . // Exception Handler
    loops = atoi(argv[1]);
    printf("Initial Value : %d\n", counter);
    
    pthread_t p1, p2;
    Pthread_create(&p1, NULL, worker, NULL);
    Pthread_create(&p2, NULL, worker, NULL);
    Pthread_join(p1, NULL);
    Pthread_join(p2, NULL);
    
    printf("Final Value : %d\n", counter);
    return 0;
}

worker 함수는 전역 변수인 counter에 저장된 값을 1씩 증가시키는 함수이다.

worker 함수를 main 함수 내에서 멀티스레드(Multi-thread)를 활용해 동시에 1000번씩 작동시킨다고 생각해 보자.

$ gcc -o thread thread.c -Wall -pthread
$ ./thread 1000
Initial Value : 0
Final Value : 2000

당연한 결과이다. 하지만 loops의 값이 커지게 되면 이상한 현상이 발생한다.

$ ./thread 100000
Initial Value : 0
Final Value : 106296
$ ./thread 100000
Initial Value : 0
Final Value : 107258

예상대로라면 200000이 출력되어야 하지만, 그에 훨씬 못 미치는 값이 출력되는 것을 볼 수 있다. 더욱 심각한 것은, 조건 분기 등이 없음에도 매번 값이 다르게 출력된다는 사실이다. 이는 대표적인 병행성 문제이다.

왜 이런 현상이 발생하는지는 내부 인스트럭션의 구조를 보면 알 수 있다. worker 함수는 최소 3개의 인스트럭션으로 이루어져 있다.

  • Load : 메모리에서 counter 변수의 값을 읽는다.
  • Increment : 해당 변수의 값을 1 증가시킨다.
  • Store : 증가시킨 값을 다시 원래 위치에 저장한다.

문제는 두 스레드를 통해 실행되는 이 인스트럭션들이 서로 중첩되는 경우이다. 각 스레드의 인스트럭션들은 개별 스레드 내부에서는 순서대로 실행되지만, 멀티스레드의 경우 서로 끼어들 수 있다.

스레드 인스트럭션 수행하는 기능 counter
1 Load counter 변수를 불러온다 0
2 Load counter 변수를 불러온다 0
1 Increment 값을 1 증가시킨다 (01) 0
1 Store 증가시킨 값 1을 counter 변수에 저장한다 1
2 Increment 값을 1 증가시킨다 (01) 1
2 Store 증가시킨 값 1을 counter 변수에 저장한다 1

실제로 개별 스레드에서 한 번씩, 반복문이 총 2회 실행되었지만 내부 인스트럭션들이 중첩되는 바람에 의도와 다르게 실행되었음을 알 수 있다.

이를 방지하기 위해서는 공유된 자원을 읽어와서 값을 증가시키고 저장하기까지의, 위에 나열한 최소 3개의 인스트럭션들이 한 묶음으로(Atomic) 실행되어야 한다.

Persistency (지속성)

일반적으로 메인 메모리로 사용하는 DRAM은 휘발성(volatile) 저장 장치이다. 휘발성은 전원을 차단하면 저장된 정보가 삭제되는 특징을 말한다.

따라서 데이터를 보존하기 위해 하드웨어나 소프트웨어의 도움을 받는다.

  • 하드웨어(Hardware) : 입출력 장치인 하드 디스크 드라이브(HDD), 솔리드-스테이트 드라이브(SDD)
  • 소프트웨어(Software) : 운영체제의 파일 시스템(File System)

운영체제는 시스템에 연결된 HDD나 SSD와 같은 저장 공간을 추상화하여 제공하며, 저장된 파일을 관리하는 **파일 시스템(File System)**을 갖고 있다.

프로그램이 파일을 열거나 수정해 저장하는 등의 파일 관련 작업을 수행하려면 시스템 콜을 요청해야 한다.

지속성(Persistency)은 디렉토리와 파일로 추상화된 공간을 통해 데이터를 보존하는 것을 말한다.

운영체제가 갖는 지속성(Persistency)의 특징은 다음과 같다.

  • 프로그램의 요청을 받아 어느 위치에 있는 파일을 수정하거나 새로운 파일을 작성할 지를 결정한다.
  • 파일시스템은 파일 작성 중 오류가 발생한 경우 Journaling이나 Copy-on-Write등의 방식을 사용해 오류를 복구한다.

운영체제 설계 목표

  • Abstraction : 추상화 및 가상화를 통해 사용 편의성을 더한다.
  • High Performance : 성능이 뛰어나야 한다.좋아야 한다. 하드웨어에 OS가 설치된 후 그 위에 다른 모든 프로그램들이 설치되기 때문에, OS의 성능이 비효율적인 경우 다른 모든 프로그램들의 성능이 저하된다.
  • Protection (Isolation) : 프로그램 간의 간섭을 방지해야 한다. 특정 프로그램이 다른 프로그램의 데이터를 훼손하거나 덮어쓰는 경우를 막을 수 있어야 한다.
  • Reliability : OS는 항상 작동해야 한다. OS가 작동을 멈추면 전체 프로그램들도 작동할 수 없게 되고, 컴퓨터를 사용할 수 없게 된다.
  • Others : Energy-efficiency, Security, Mobility, . . .

앞으로 운영체제의 3요소인 Virtualization, Concurrency, Persistency에 관한 내용을 주제별로 다룰 예정이다.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus