[OSTEP] OSTEP 2장 요약

Updated:

프로그램이 수행되는 과정에서 프로세서는 메모리로 부터 명령어를 fetch해 오고 해당 명령어를 decode하고 수행합니다.

프로세서에 의해서 하나의 명령어의 수행이 끝나면 프로세서는 다음 명령어를 수행하게 됩니다. $($현대의 프로세서는 프로그램의 수행 속도를 높이기 위해서 여러개의 명령어를 동시에 처리하기도 하고 비순차실행을 수행하기도 합니다)

프로그램이 실행되는 동안에는 많은 작업들이 시스템을 사용하기 편리하도록 하기 위해 수행됩니다. 소프트웨어의 본체는 프로그램이 메모리를 서로 공유하고 다른 장치들과 상호작용 할 수 있도록 가능하게 하는 등의 프로그램이 편리하게 작동되도록 하는 작업을 담당합니다. 이러한 소프트웨어의 본체를 운영체제$($OS) 라고 하며 운영체제는 시스템이 사용하기 편리한 방법으로 올바르고 효율적으로 작동할 수 있도록 관리합니다.

위와 같은 관리를 위한 OS의 가장 기본적인 기술이 바로 가상화(Virtualization) 입니다. 가상화 란 OS가 물리적 자원 $($프로세서, 메모리, 디스크 등)을 더 사용하기 편리하고 강력한 논리적 형태로 변환하는 것을 말합니다. 이러한 특징 때문에 운영체제를 가상화 머신 이라고 부르기도 합니다.

사용자가 OS에게 명령을 내려서 가상 머신의 기능들을 이용하기 위해서는 OS가 제공하는 인터페이스$($API)를 불러내야 합니다. OS는 어플리케이션을 위한 수백 개가 넘는 system call을 제공합니다. 이러한 system call은 프로그램을 실행하고 메모리와 외부 장치들에 접근하는 것과 같은 작업에 사용되기 때문에 OS가 어플리케이션 에게 standard library를 제공해 준다고도 합니다.

가상화는 여러 프로그램들이 제한된 CPU를 공유하는 동시에 자신의 명령어와 데이터, 그리고 장치에 접근할 수 있게 하며 이로 인해 프로그램들은 메모리, 디스크 장치들을 서로 공유하게 됩니다. 이러한 이유 때문에 OS는 자원관리자라고 불리기도 합니다. 각각의 CPU, 메모리, 디스크들은 시스템의 자원입니다. 이러한 자원들을 효율적이고 공평하게 관리하는 것이 OS의 역할입니다.

왜 OS가 자원들을 추상화 시키는지는 이 책에서 중요한 질문이 아닙니다. OS가 자원들을 추상화 시키는 이유는 당연히 추상화를 통해 시스템을 더 쉽게 사용할 수 있게 되기 때문입니다. 우리가 주목해야 할 문제는 어떻게 OS가 자원들을 추상화 시키냐는 것입니다. 추상화를 위해서 어떤 mechanism과 police가 OS에 의해 적용되고 이러한 과정을 어떻게 효율적으로 처리하는지, 그리고 어떤 하드웨어의 도움이 필요 한지가 주목해야할 문제점입니다.

2.1 CPU의 추상화

cpu.c

위의 프로그램은 Spin()함수에 의해서 1초의 지연시간을 가진 후에 인자로 전달된 문자열을 출력하는 프로그램입니다. 이 프로그램을 하나의 CPU를 사용하여 수행하면 결과는 다음과 같습니다.

cpu.c

예상대로 프로그램은 초단위로 지연된 후에 인자로 주어진 문자열을 출력하는 과정을 반복하게 됩니다. 여기서 유의해야 할 점은 해당 프로그램이 무한 반복된다는 점입니다. 때문에 프로그램을 종료시키기 위해서는 foreground에서 수행되는 프로그램을 종료시키는 명령어인 “Control-c”를 입력하여 종료 시켜야 합니다.

cpu.c

2.2 프로그램에서는 같은 프로그램을 여러 인스턴스로 수행하였을 때의 결과입니다. 위의 결과를 보면 여러 프로그램이 동시에 수행된 것과 같은 결과를 보여줍니다. 이는 운영체제가 하드웨어의 도움을 받아 추상화를 제공한 것으로 마치 시스템이 여러개의 가상의 CPU가 존재하는 것과 같은 효과를 만들어 줍니다. 이는 제한적인 CPU의 개수를 무한한 CPU가 존재하는 것과 같은 추상화를 제공하며 이로 인해 여러 프로그램을 동시에 수행되는 것과 같은 추상화를 제공하게 됩니다. 만약 두 프로그램이 특정한 시간에 수행된다면 둘 중 어느 프로그램을 먼저 수행해야 하는지 와 같은 질문이 생기기도 합니다. 이러한 수행 순서를 위해 OS는 policy를 제공합니다..

프로그램을 수행하고 종료시키기 위해서는 사용자의 요구사항을 OS와 소통하게 할 수 있는 인터페이스(APIs)가 필요합니다.

2.2 메모리의 추상화

물리적 메모리는 바이트의 배열을 의미합니다.

메모리에서 데이터를 읽어 오기 위해서는 접근하고 싶은 데이터의 메모리 주소를 명시해 주어야 합니다.

메모리에 데이터를 쓰기 위해서는 쓰고 싶은 데이터와 데이터를 저장할 주소 공간을 명시해 주어야 합니다.

메모리는 프로그램이 수행되는 동안 항상 참조됩니다. 프로그램은 자신의 모든 자료구조를 메모리상에 저장해 놓고 다양한 명령어들을 통해 수행 과정 도중에 접근합니다. 각 프로그램의 명령어들 또한 메모리에 존재하기 때문에 명령어 fetch 과정에서 매번 메모리는 참조되게 됩니다.

mem.c

다음의 malloc()을 통해 메모리를 할당하는 프로그램을 보겠습니다. 해당 프로그램으로 여러개의 인스턴스를 수행했을 시에 다음과 같은 결과를 얻게 됩니다.

cpu.c

두 프로그램 둘다 0x200000이라는 같은 주소에 메모리를 할당해 주었지만 위의 결과를 보면 마치 두개의 프로그램이 각기 다른 0x200000주소의 값을 갱신하는 것처럼 보입니다. 이는 OS가 메모리를 추상화 시켜주기 때문에 가능한 일입니다. 이를 통해 수행되고 있는 프로그램들이 같은 물리적 메모리 공간을 공유하지 않고 고유의 가상 주소공간을 갖게 됩니다. 따라서 어떠한 프로그램에 의해서 메모리가 참조되더라도 다른 프로세스의 주소공간에는 아무런 영향을 미치지 못합니다. 즉, 물리적 메모리는 결국 공유 자원이며 운영체제에 의해 관리되는 자원입니다.

2.3 병행성

쓰레드는 프로세스와 달리 메모리의 데이터를 공유합니다. Pthread_create()에 의해서 두개의 쓰레드가 생성되었다고 생각해보겠습니다. 이 두개의 쓰레드가 인자를 전달 받아 인자만큼 반복문을 돌며 counter값을 1씩 증가시키는 프로그램을 수행한다고 할 때, 만약 인자의 값이 1000 이면 두개의 쓰레드가 각자 1000번씩 counter를 증가 시키기 때문에 2000이라는 결과값을 예측해 볼 수 있습니다. 즉, 인자값을 N이라 했을 때, 두개의 쓰레드가 존재하므로 결과값은 결국 2N이 출력될 것이라는 것을 알 수 있습니다.

그렇다면 만약 N의 값을 더욱 증가시켜 인자로 100,000을 전달하면 어떻게 될까요? 두개의 쓰레드에 의해 200,000이라는 결과가 나올 것이라는 예상과 달리 143,012라는 이상한 값이 출력되는 것을 알 수 있습니다. 다시 한번 100,000을 인자로 전달하여 프로그램을 수행하더라도 이번엔 137,298이라는 전혀 상관 없는 듯한 값이 나오게 됩니다.

이는 병행성 문제 때문인데, 공유자원을 여러 쓰레드가 동시에 접근할 때 발생하게 됩니다. 프로그램이 수행되는 과정에서 명령어들이 원자성을 만족하지 못하기 때문에 한 쓰레드가 수행하는 도중에 다른 쓰레드가 끼어들 수 있게 되는 것입니다.

2.4 지속성

시스템 메모리 상에서 DRAM의 경우 휘발성의 성질 때문에 전원이 공급되지 않거나 시스템에 문제가 생기면 메모리 상의 데이터가 손실됩니다. 그렇기 때문에 데이터를 영구적으로 보관하기 위해 하드웨어 장치가 필요합니다. 그중에 하드 드라이브는 가장 대표적인 비휘발성 장치이며 SSD또한 최근에 주목받고 있습니다.

이러한 디스크를 관리해주는 시스템 운영체제를 파일 시스템이라고 합니다. 파일 시스템은 유저가 생성한 파일을 시스템상의 디스크에 효율적으로 저장하고 관리하는 역할을 담당합니다. CPU나 메모리에서와 달리 OS는 독립적인 가상 디스크를 생성하지는 않습니다. 대신 write, read, close와 같은 system call이 운영체제인 파일 시스템에 발송되면 요청사항을 전달받아 알맞는 응답을 유저에게 반환하는 작업을 수행 합니다. 파일 시스템이 장치에 데이터를 write 할 시에는 잠시 수행이 지연되는데 이는 한번에 그룹으로 묶어서 처리해 성능을 향상 시키기 위함입니다. write시에 프로그램이 충동하지 않게 하기 위해서 대부분의 파일 시스템은 복잡한 write 프로토콜을 포함합니다. 대표적인 write 프로토콜에는 journaling, copy-on-write 이 있으며 write 수행 시에 문제가 생기더라도 시스템이 정상적인 상태로 회복할 수 있게 해줍니다.

2.5 Design Goals

이제 어느정도 OS가 어떠한 일을 하는지 알게 되었습니다. OS는 CPU, 메모리, 디스크 등의 물리적 자원들을 가상화 시켜서 병행성과 같은 복잡한 문제를 해결하고 파일들을 지속적으로 보관합니다. 운영체제의 가장 중요한 목적 중 하나는 추상화를 제공하여 시스템을 쉽고 편리하게 사용할 수 있게 하는 것 입니다. 추상화는 큰 하나의 프로그램을 여러 이해 가능한 조각들로 나눠서 우리가 어셈블리어 같은걸 신경 쓰지 않고 고급언어로 프로그램을 작성하는 것을 가능하게 합니다.

운영체제의 또 다른 목적은 높은 성능을 제공하기 위함입니다. 이는 곧 OS의 overheads를 최대한 줄이는 것을 뜻합니다. overheads는 시간, 공간 형태로 발생합니다. 또 다른 목표는 OS와 어플리케이션, 혹은 어플리케이션과 어플리케이션 간의 protection을 제공하는 것 입니다. 여러 프로그램을 동시에 수행할 때 한 프로그램의 문제가 다른 프로그램들에게도 피해를 주지 않게 하기 위해, 특히 어플리케이션의 문제로 인해 OS에 문제가 생기지 않게 하는 것이 중요합니다. 만약 OS에 문제가 생긴다면 시스템에서 수행중인 모든 프로그램이 영향을 받기 때문입니다. protection은 프로세스들을 서로 독립시켜서 이러한 문제들을 해결합니다.

운영체제의 실행은 중간에 멈춰서는 안됩니다. 만약 실행이 중단된다면 시스템 상에서 수행되고 있던 모든 어플리케이션들은 오류가 발생하게 됩니다. 이러한 의존성 때문에 운영체제는 높은 신뢰성을 주기 위해 고군분투합니다.

에너지 효율성, 보안, 모빌리티 등은 OS가 더욱더 작은 장치에서 수행되게 되면서 더욱 중요해 지고 있습니다.

초기의 OS는 자주 사용되는 함수를 모아놓은 하나의 라이브러리에 불과했습니다. 또한 여러 작업들을 한꺼번에 한번에 수행하는 batch processing 기법을 사용하였습니다.

OS의 수행 코드는 디바이스들의 제어권을 갖고 있었기 때문에 다른 어플리케이션과 다르게 취급받게 되었습니다. 파일 시스템이 탑재되었고 유저와 커널모드를 분리하였으며 유저모드에서 커널모드에 접근하기 위한 system call이 등장 하였습니다.

system call은 제어를 OS로 전송하는 동시에 하드웨어 권한 수준을 높입니다. 유저 어플리케이션은 어플리케이션의 행동을 제한하는 유저모드에서 동작합니다. 예를 들어 유저모드에서 동작하는 어플리케이션은 디스크로 I/O 요청을 보낼 수 없고 물리 메모리 페이지에 접근할 수 없으며 네트워크로 페킷을 전달할 수도 없습니다. system call이 호출되면 $($보통 특별한 하드웨어 명령어인 트랩에 의해 호출됩니다) 하드웨어는 제어를 OS에 의해 미리 지정된 트랩 헨들러로 보냅니다. 그리고 이와 동시에 권한수준을 커널모드로 올립니다. 커널모드 내에서 OS는 시스템의 하드웨어에 대한 모든 접근 권한을 갖게 되기 때문에 프로그램을 위한 메모리를 더 많이 할당하거나 I/O 요청을 하는 등의 작업이 가능합니다. OS가 요청을 전부 처리하고 나면 return – from – trap 명령어를 통해 다시 사용자에게 제어가 넘어가며 유저모드로 되돌아 갑니다.

컴퓨터가 보급화 되면서 멀티프로그래밍 개념이 생겨났습니다. 멀티프로그래밍을 통해 OS는 여러개의 작업들을 메모리로 올릴 수 있게 되었고 작업들을 빠르게 스위치 해가며 수행함으로서 CPU의 활용성을 높일 수 있게 되었습니다. I/O 장치가 CPU의 작업을 기다리느라 시간이 지체되는 문제가 있었는데 기다리는 동안 다른 작업으로 스위치 되어 다른 작업을 수행할 수 있게 되었습니다.

하지만 멀티프로그래밍에 의해 하나의 프로그램이 다른 프로그램의 메모리를 침범하지 않게하기위한 memory protection이 중요해졌고 병행성 문제 또한 발생하게 되었습니다.

운영체제의 가장 큰 발전은 유닉스 운영체제가 등장하고 나서부터 입니다. 유닉스는 여러 운영체제의 장점들을 집약하여 만든 운영체제이며 사용하기 간편하고 심플하다 는 장점이 있습니다. 유닉스 체제는 여러 사람들에게 배포되었으며 다양한 사람들을 통해 시스템에 더 많은 것들이 추가되었습니다.

PC $($Personal Computer)가 보급되어 더많은 사람이 컴퓨터를 사용할 수 있게 되었지만 불행히도 OS는 퇴보 되는 과정을 겪게 되었습니다. DOS와 같은 초기의 운영체제는 memory protection 문제에 관해 전혀 신경 쓰지 않았기 때문에 어플리케이션이 메모리의 어떤 곳이든 접근이 가능 했습니다. Mac OS는 작업 스케줄링에 협력적인 접근 방식을 취했습니다. 때문에 쓰레드가 무한 루프에 갇히게 되면 시스템 전체를 차지하게 되었으며 이는 리부트를 야기하였습니다.

다행히도 현대의 운영체제는 많은 발전을 이루었습니다. 심지어 핸드폰에서 조차 운영체제가 돌아가게 되었습니다. 운영체제가 계속 발전을 하고있고 유저와 어플리케이션을 위한 새로운 기능들이 추가되고 있다는 것은 분명 좋은 일이라고 할 수 있겠습니다.

Categories:

Updated:

Leave a comment