프로세스의 정의


 

프로세스는 운영체제(os)가 사용자에게 제공하는 가장 근본적인 추상화 (fundamental abstraction)다.

프로세스의 정의는 단순하다. 실행중인 프로그램(running program)이 프로세스의 정의다.

 

프로그램은 그 자체로는 생명이 없다. 그저 디스크에 상주하는 명령어 묶음이며, 이는 실행이 되기를 기다리고 있다.

운영체제(OS)가 이들을 실행시킬 때 비로소 프로그램은 제 역할을 한다고 볼 수 있다.

 

 

 

CPU 가상화 (virtualizing CPU)


우리가 컴퓨터를 사용할 때, 마치 여러 개의 프로세스들이 동시에 실행되고 있다고 느낀다.

또한, 우린 CPU가 어떠한 프로세스를 실행시키고 있는지 고려할 필요가 없다.

 

하나의 CPU로 여러 개의 프로세스를 동시에 실행시키고 싶지만, 이는 불가능하다.

대신에, CPU time sharing을 통해서 여러 개의 프로세스를 번갈아가며 실행하여 마치 하나의 CPU가 여러 개의 프로세스를 동시에 실행하는 것처럼 보이게 할 수 있다.

 

이와 같이,  운영체제는 단일 physical CPU로 여러개의 virtualizing CPUs를 사용하는 것처럼 환상(illusion)을 만들어내며, 이를 CPU 가상화라고 한다.

 

CPU time sharing은 유저가 여러 개의 프로세스를 동시에 실행할 수 있도록 제공하지만, CPU가 공유되므로 각각의 프로세스는 더 느리게 실행될 것이며, 잠재적인 성능에 대한 trade-off가 있다. 

 

time sharing 메카니즘에서 context-switching이 쓰이며, context-switching은 주어진 CPU에서 하나의 프로그램을 종료시키고 다른 또 하나의 프로그램을 실행시킬 수 있는 운영체제의 ability이다.

 

 

 

프로세스와 Machine State


위에서 말했듯이, 프로세스는 OS가 실행중인 프로그램이다. 그리고 프로그램은 OS가 실행하기 전까진 디스크에 상주하는 instructions(명령어) 묶음이다.

 

그렇다면, 프로세스를 구성하는 것은 무엇일까? 이를 이해하기 위해서, 프로세스의 machine state에 대해 알 필요가 있다.

 

Memory

프로세스를 구성하는 핵심 machine state 구성 요소 중 하나는 메모리(memory)다.

실행하는 프로그램의 명령어들과 읽고 쓰는 데이터들 모두 메모리에 적재되어 있다.

 

 

 

Register

프로세스의 machine state 구성 요소 중 또 다른 하나는 레지스터다.

많은 명령어들이 명시적으로 레지스터를 읽고, 업데이트한다. 이는 프로세스의 실행에 있어 필수적이다.

 

프로세스의 machine state를 구성하는 특수한 레지스터들을 몇 개 소개한다.

 

Program Counter, PC (=instruction pointer, IP)

- 다음에 실행할 프로그램 명령어를 지시한다.

 

Stack Pointer 와 Frame Pointer

- 함수 파라미터(function parameters), local variables(지역 변수)가 담긴 스택을 관리하고 주소(address)를 반환(return)하기 위해 사용된다.

 

 

Persistent Storage Device

Persistent Storage Device는 hard disk drive와 같이 영구적으로 데이터가 저장되는 저장소를 뜻한다.

프로세스는 때때로 persistent storage device에 접근(access)한다. (ex. file open 및 I/O)

 

 

 

 

대표적인 Process API


 

Create : 운영체제가 새로운 프로세스를 생성하는 메소드다. 쉘(shell)에 명령어(command)를 입력하거나, 어플리케이션 아이콘을 더블 클릭할 때, 운영체제는 사용자가 지시한 프로그램을 실행하기 위한 새로운 프로세스를 생성한다.

 

Destroy : 운영체제가 프로세스를 강제적으로 종료하도록 하는 메소드다. 물론 많은 프로세스들이 실행이 완료된 후 스스로 종료되겠지만, 그렇지 않을 경우, 사용자가 프로세스를 kill(강제 종료)하고 싶을 때 사용한다.

 

Wait : 프로세스의 실행이 종료될 때까지 기다린다.

 

Miscellaneous Control : 프로세스를 kill 또는 wait하는 것과는 또 다른 control(제어)가 가능하다. Miscellaneous Control은 프로세스를 연기(suspend)한다. 즉, 잠깐동안 실행을 멈추었다가 다시 실행을 재개한다.

 

 

 

 

Process Creation Detail, 프로세스 생성에 대한 고찰


프로그램들이 프로세스로 변화(transform)하는 과정을 좀 더 살펴보자. 운영체제는 어떻게 프로그램을 가져와 실행하고, 프로세스를 생성할까?

 

첫 번째로, 운영체제는 프로그램 코드와 정적 데이터(static data)를 메모리로 로드한다. 프로그램은 처음엔 실행가능한 형식으로 디스크(disk, 현대에 와서는 flash기반 ssd)에 상주하므로 이러한 과정이 필요하다.

 

초기 운영체제는 프로세스 로딩을 eager하게 수행(프로그램이 실행되기 전에 한 번에 모든 로딩 처리)했지만,

현대의 운영체제는 lazy-loading을 채택하고 있다. 즉, 프로그램을 실행하는 동안, 필요한 코드, 데이터만을 부분적으로 가져온다. 

lazy-loading을 깊게 이해하기 위해서, paging(페이징)과 swapping에 대한 이해가 필요하다. 이 부분은 메모리 가상화에서 다룬다.

지금은, 운영체제가 프로그램을 실행하기 위해 필요한 프로그램 비트들을 디스크로부터 메모리로 가져온다고만 알아두자.

 

두 번째로, 운영체제는 프로그램이 실행될 때 메모리의 일부를 런타임 스택으로 할당하고 프로세스에게 넘겨준다. 예를 들어, C 프로그램에서는 스택을 지역 변수(local variable), 함수 파라미터(function parameters), 주소 반환(return address)을 위해 사용한다. OS는 일반적으로 arguments(ex. main() 함수의 argc, argv)를 담은 스택을 초기화할 것이다.

 

세 번째로, 운영체제는 프로그램이 실행될 때 메모리의 일부를 프로그램의 힙(heap)으로 할당하고 프로세스에게 넘겨준다. C 프로그램에서, 힙은 명시적으로 요청받은 동적 할당 데이터들을 위해 사용된다. 예를 들어 malloc()으로 할당하고, free()로 할당 해제한다. 힙은 연결 리스트(linked list), 해쉬 테이블(hash table), 트리(trees) 등의 자료 구조 사용에 필요하다. 힙은 처음엔 작을지 모르지만, 프로그램이 실행되고, malloc()을 통해 많은 요청이 발생하면 OS는 이러한 요청을 받아들여 더 많은 힙 메모리를 할당할 것이다.

 

이 뿐만 아니라, 운영체제는 다른 초기화 작업들도 수행한다. 특히, I/O(Input/Output, 입출력) 관련 초기화 작업도 포함하는데, 예를 들어, UNIX 시스템에서는 각각의 프로세스는 디폴트하게 3 개의 open 파일 디스크립터(file descriptor)를 가진다. 3개는 각각 표준 입력, 표준 출력, 그리고 오류를 의미한다. 이 디스크립터들은 프로그램이 터미널로부터 쉽게 입력을 읽고(read input), 스크린에 출력을 프린트할 수 있도록 한다(print output).

 

이처럼, 코드와 정적 데이터를 메모리에 로드하고, 스택과 힙을 생성, 초기화 및 할당하고, I/O관련 셋업과 같은 다른 작업들을 수행한 후 OS는 비로소 프로그램 실행을 위한 준비를 거의 마친다. 마지막으로, main() 시작 포인트(entry point)에서 프로그램을 실행시키기 위한 작업을 수행한다. 운영체제는 CPU에 대한 제어권을 새롭게 생성된 프로세스에 넘겨준다. 그리고 프로그램이 실행된다.

 

 

 

Process State (프로세스 상태)


이제 우린 프로세스가 무엇인지, 어떻게 생성되는 지 어느 정도 파악했다. 이제, 프로세스가 주어진 시간 동안 어떠한 상태로 존재할 수 있는지 process states에 대해 알아보자. 단순화했을 때, 프로세스는 아래 세 가지 상태 중 하나로 존재한다.

 

Running : 프로세스가 프로세스에서 실행되고 있는 상태, 즉 명령어가 실행되고 있는 상태를 의미한다.

 

Ready : ready 상태는 프로세스가 실행될 준비가 되었지만, 어떠한 이유에서인지 운영체제가 지금은 해당 프로세스를 실행하지 않는 상태를 의미한다.

 

Blocked : blocked 상태는 프로세스가, 특정 이벤트(event)가 발생하기 전까지는 실행할 수 없는 연산을 수행하고 있는 상태이다. 흔한 예시로, 프로세스가 disk I/O request를 시작했을 때 blocked 상태가 되며, 그 동안 다른 프로세스가 프로세서를 사용할 수 있다. 

 

 

 

 

Operating System의 Data Structure


운영체제도 프로그램이며, 다른 프로그램들과 마찬가지로 다양한 관련 정보를 추적하는 주요 데이터 구조를 가지고 있다.

 

예를 들어, 각각 프로세스의 상태를 추적하기 위해 운영체제는 프로세스 리스트(process list)를 가지고 있다. 프로세스 리스트는 ready상태의 모든 프로세스를 포함하며, 어떠한 프로세스가 실행되고 있는 지를 추적하기 위한 정보들을 포함한다.

운영체제는 또한 blocked 상태의 프로세스 또한 추적해야 한다. 예를 들어, I/O event가 완료되었을 때, 운영체제는 올바른 프로세스를 깨워(wake) 다시 실행하기 위해 ready상태로 전환해야 한다.

 

이처럼, 운영체제는 프로세스를 추적하기 위해 stopped process(중지된 프로세스)의 레지스터 값들을 register context에 저장한다. 프로세스가 중지되면, 중지된 프로세스의 레지스터 값들은 레지스터 컨텍스트에 저장되며, 이 레지스터들을 복구함으로써 운영체제는 다시 프로세스를 재개(resume)할 수 있다. 이와 같은 테크닉은 context switch에서 자세하게 배운다.

 

실제로, process state는 위의 running, ready, blocked가 아닌 다른 상태로도 존재할 수 있다.

 

예를 들어, 프로세스가 생성중일 때는 initial state, 프로세스가 종료(exit)되었지만 회수(cleaned up)되지 않았을 때 final state(unix 시스템에서는 이를 zombie state라고 칭한다)가 있다.

 

final state는, 프로세스를 생성한 부모 프로세스(parent)가 종료된 자식 프로세스의 반환 코드(return code)를 검사하고, 종료된 자식 프로세스가 성공적으로 실행되었는 지 확인할 수 있도록 한다.(일반적으로 작업이 성공적으로 수행되었으면 0을, 그렇지 않으면 0이 아닌 값을 반환한다).

 

프로세스가 종료될 때, 자식 프로세스가 완료되기까지 기다리기 위한 final call(최종 호출, ex. wait())을 하고, 운영체제에게 이제 사라지는 프로세스와 관련된 자료 구조들을 정리(cleaned up)해도 된다고 명시한다.

 

 

 


 

 

출처 : pages.cs.wisc.edu/~remzi/OSTEP/

 

Operating Systems: Three Easy Pieces

Blog: Why Textbooks Should Be Free Quick: Free Book Chapters - Hardcover - Softcover (Lulu) - Softcover (Amazon) - Buy PDF - EU (Lulu) - Buy in India - Buy Stuff - Donate - For Teachers - Homework - Projects - News - Acknowledgements - Other Books Welcome

pages.cs.wisc.edu

 

Virtual Address와 Physical Address 사이의 Mapping


CPU가 가지고 있는 Virtual Address와 Main Memory의 실제 데이터를 가리키는 Physical Address가 있다.

운영체제(OS)는 이와 같은 메모리 가상화를 제공한다.

가상화를 통해서, 각각의 프로세스는 개별적으로 고유한 메모리를 할당받아 사용할 수 있으며, 이를 통해서 다수의 프로세스를 실행할 때 발생하는 잘못된 메모리 접근으로부터의 보안과 효율적인 메모리 관리가 가능해졌다.

 

 

 

Segment와 Page


Segment(세그먼트)는 메모리를 서로 크기가 다른 논리적 블록 단위이다.

이렇게 세그멘트를 나누어 메모리를 관리하는 기법을 세그멘테이션이라고 하며,

 

세그멘테이션과 가상 메모리를 고정된 크기의 블록으로 나누어 관리하는 것이 페이징 기법이다.

따라서 Page(페이지)는 메모리를 잘게 쪼갠 일정한 크기의 블록 단위이다.

 

 

 

 

Page Table


Page Table은 Virtual Address와 Physical Address 사이에서 Page 단위의 memory address를 Mapping하는 역할을 한다.

Main Memory에 상주하고 있으며 이로 인해 CPU는 불가피하게 Main Memory에 한 번 더 접근해야 한다.

 

1. Virtual Address에 해당하는 Physical Address를 Main Memory의 Page Table로부터 조회한다.

2. 조회한 Physical Address로부터 데이터를 가져온다. 이 때, 해당 데이터가 Cache에 있을 경우 Cache로부터, 없을 경우 Main Memory로부터 가져온다.

 

즉, 1번 단계에서 반드시 Main Memory에 접근해야 하므로 성능 측면에서 비효율적이다.

 

 

 

Translation Look-aside Buffer


데이터를 가져올 때, CPU와 Main Memory사이의 간극을 줄이기 위해 캐시 메모리를 도입한 것처럼 

데이터를 가져오기 위한 address를 조회할 때, CPU와 Main Memory사이의 간극울 줄이기 위해 도입한 것이 TLB(Translation Look-aside Buffer)이다. Page Table의 cache라고 볼 수 있다.

 

이를 통해서 가상 메모리 주소(virtual address)로부터 물리적 메모리 주소(physical address) 변환하는 속도를 높일 수 있다.

 

 

 

캐시 일관성


각각 CPU의 캐시 데이터와 메인 공유 메모리의 데이터가 모두 일치하도록 하는 것

 

 

캐시 일관성 문제


위의 캐시 일관성이 지켜지지 않는 상황이다. CPU 캐시 데이터와 메인 메모리의 데이터 사이의 괴리 또는 멀티 프로세서에서 각각 CPU 캐시 데이터 사이의 괴리 모두 이에 포함된다.

 

 

 

 

해결 방식


소프트웨어적 해결방식과 하드웨어적 해결방식으로 나뉜다.

소프트웨어적으로는 컴파일 시(Compile time) 문제를 검출하며

하드웨어적으로는 실행 시(Run time) 문제를 검출한다.

 

 

 

소프트웨어적 해결


운영체제(OS)와 컴파일러를 사용하여 해결하는 방식이다. 코드 분석을 통해 안전하게 공유 변수를 사용할 수 있도록 주기를 설정하거나, 아예 공유 데이터 변수를 캐시에 저장하지 않도록 설정하는 방법 등이 있다. Compile Time에 문제를 검출하므로, 캐시 이용률 측면에서 다소 비효율적이라는 단점이 있다.

 

 

 

하드웨어적 해결


1. 스누피 프로토콜(snoopy protocol)

각각 CPU의 캐시들은 공유되는 캐시 데이터를 파악하고 있으며, 공유되는 캐시 데이터가 갱신 되었을 경우 이를 기반으로 갱신되지 않은 나머지 데이터를 무효화한다. 공유되는 버스 구조가 Broadcasting 및 Snooping에 유리하므로 버스 기반 다중 프로세서 시스템에 적합한 방식이다.

 

2. 디렉토리 프로토콜(directory protocol)

주기억장치의 중앙 제어기가 캐시 일관성을 관리하는 형태이다. 주기억장치의 중앙 제어기는 각각 CPU의 캐시 데이터들을 가지고 있으며, 모든 지역 캐시 제어기의 동작을 제어하고 보고받으며 캐시 일관성을 관리한다.

+ Recent posts