프로세스 제어블록(Process Control Block)의 약어로 프로세스를 관리하는데 사용하는 OS의 자료구조이다.

운영체제는 프로세스를 PCB 단위로 관리하며 프로세스 스케줄링을 위한 정보를 PCB 를 통해 관리한다.


프로세스가 생성될 때마다 고유의 PCB 가 생성되며, 프로세스가 완료되면 PCB 는 제거된다.

프로세스 간 Switching 이 발생할 때, 운영체제는 PCB 를 이용해서 상태를 전이시킨다. (State Transition)


프로세스는 CPU가 처리하던 작업 내용들을 PCB에 저장하고, 

다음에 다시 CPU 를 점유하여 작업할 때 PCB로부터 해당 정보들을 CPU 에 넘겨와서 하던 작업을 진행한다.


PCB는 다음과 같은 데이터 구성을 갖고 있다.


- Process Identification Data

- Process State Data

- Process Control Data


PCB 는 다음과 같은 정보들을 저장하고 있다.


(1) Process ID : 프로세스를 구분하는 ID


(2) Process State : 각 State 들의 상태를 저장한다.


(3) Program Counter : 다음 Instruction 의 주소를 저장하는 카운터. CPU는 이 값을 통해 Process 의 Instruction 을 수행한다.


(4) Register : Accumulator, CPU Register, General Register 등을 포함한다.


(5) CPU Scheduling Information : 우선 순위, 최종 실행시간, CPU 점유시간 등이 포함된다.


(6) Memory Information : 해당 프로세스 주소공간(lower bound ~ upper bound) 정보를 저장.


(7) Process Information(페이지 테이블, 스케줄링 큐 포인터, 소유자, 부모 등)


(8) Device I/O Status(프로세스에 할당된 입출력 장치 목록, 열린 팔린 목록 등)


(9) Pointer : 부모/자식 프로세스에 대한 포인터, 자원에 대한 포인터 등


(10) Open File List : 프로세스를 위해 열려있는 파일의 리스트






 가상메모리(Virtual Memory)는 컴퓨터가 사용하는 메모리 관리 테크닉이다.

실제메모리를 추상화하여 가상 메모리에 올림으로써 프로세스들이 연속된 물리적 메모리 공간처럼 여기게 만들 수 있을 뿐만 아니라 실제 메모리보다 많은 크기의 메모리도 운용이 가능하다.


가상메모리 기술이 동작하기 위해서는 2가지 이슈가 있다.

하나는 가상 메모리를 물리 메모리에 매핑시키는 Address Translation이고, 다른 하나는 이 가상 공간에 대한 관리 이슈이다.


이러한 Virtual Memory 와 Physical Memory 의 매핑을 위한 기술로 Paging 기법이 사용된다.


페이징 기법은 컴퓨터가 메인 메모리에서 사용하기 위해 데이터를 저장하고 검색하는 메모리 관리 기법이다.

페이징 기법을 통해 컴퓨터의 물리적 메모리는 연속적으로 할당되어 존재할 필요가 없으며, 반대로 연속적으로 존재하지 않는 물리적 메모리라도 페이징 기법을 통해 연속적으로 존재하는 것처럼 이용될 수 있다.


페이징 방식에서는 가상 메모리 상의 주소 공간을 일정한 크기로 분할한다. 

주소공간은 페이지 단위로 나뉘어져있으며 실제 기억공간은 페이지 크기와 같은 프레임으로 나누어 사용한다.


 - Frame : 물리 메모리를 일정된 한 크기로 나눈 블록

 - Page : 가상 메모리를 일정된 한 크기로 나눈 블록


이 때 Frame 과 Page 의 크기는 동일하게 관리된다.

페이지의 크기는 크기는 시스템에 따라 다르며 크기가 작으면 메모리를 단편화하기 쉬워지지만 페이지의 갯수가 많아지기 때문에 페이지 단위의 입출력이 자주 발생하게 된다.


페이지가 하나의 프레임을 할당받으면, 물리 메모리에 위치하게 된다.

프레임을 할당받지 못한 페이지들은 외부저장장치에 저장되며, 마찬가지로 프레임과 같은 크기 단위로 관리된다.


페이징 기법에서 페이지는 페이지테이블(Page Table) 이라는 자료구조 형태로 관리된다.

Page Table 은 프로세스의 페이지 정보를 저장하고 있으며, 하나의 프로세스는 하나의 페이지 테이블을 가진다.

Page table 은 Index 를 키로 해당 페이지에 할당된 메모리(Frame)의 시작 주소를 Value 로 저장하고 있다.


페이지 테이블 엔트리(Page Table Entry)는 페이지 테이블의 레코드를 말한다.

PTE 의 각 필드에는 다음 내용들이 기록된다.


 - 페이지 기본 주소(Page base address)


 - 플래그 비트(Flag bit)

  - Accessed bit : 페이지에 대한 접근이 있었는지를 나타낸다.

  - Dirty bit : 페이지의 내용에 변경이 있었는지를 나타낸다.

  - Present bit : 현재 페이지에 할당된 프레임이 있는지를 나타낸다.

  - Read/Write bit : 읽기/쓰기에 대한 권한을 표시한다.


페이지의 크기는 하드웨어에 의해 정의되며 대게 2의 제곱으로 증가한다.



페이징 기법에서 동적 주소 변환 과정은 다음과 같다.


(1) 수행 중인 프로세스가 가상주소를 참조한다.


(2) 페이징 기법을 통해 페이지 p가 페이지 프레임 f에 있음을 알아낸다.


(3) 실주소 r = f(페이지 프레임) + d(오프셋) 를 구해낸다.


위와 같은 방식으로 페이지 테이블은 Virtual Memory 에서 Physical Memory 를 참조가능하도록 지원한다.



페이지 테이블은 사용을 위해서 메모리에 존재해야 하지만, 그렇게 되면 메모리 접근에 있어서 중복 호출이 일어나므로(페이지 테이블에 한번, 페이지테이블을 통한 실제 메모리에 한번), 대게 MMU 의 지원을 받아 매핑시키게 된다. 



(참고 : https://www.geeksforgeeks.org/operating-system-paging/

https://gabrieletolomei.wordpress.com/miscellanea/operating-systems/virtual-memory-paging-and-swapping/)


잘 모르는 사람에게 설명할 일이 생긴 김에... 인터넷의 동작에 대한 기본 설명을 간략하게 정리해보았다.


인터넷은 Interconnected Network of computers 에서 파생된 단어로, 각 컴퓨터 간의 네트워크 연결이 인터넷의 출발점이 된다.


원격에 있는 컴퓨터에 접속하기 위하여 각 단말은 IP Address 라는 고유 주소를 가지며, IP Address 를 찾아가는 것이 곧 다른 컴퓨터로의 접속으로 이어진다.


참고로 인터넷은 단순히 IP Address 를 직접 사용하지 않고, 사용자가 읽을 수 있는 형태의 Domain Name 이라는 것을 사용한다.

DNS 주소를 통해 머신은 IP 주소를 직접찾는 것이 아니라 도메인 네임 서버에서 받아와서 해당 서버가 중계해주는 IP Address 를 전달받는 방식이다.


IP Address 를 찾아가는 방식은 단순하게 각 네트워크 주소를 방문하면서 해당 네트워크에 해당 주소가 있는지를 탐색하는 방식으로 시작된다.


여기서 네트워크라는 건, 작게는 LAN(Local Area Network), 크게는 WAN(Wide Area Network) 혹은 그 이상의 크기로 전산망을 연결한 것을 말한다.


모든 컴퓨터들은 다른 모든 컴퓨터들과 직접적으로 연결되어 있지 않으며, 계층적으로 망 내에 속하고, 이렇게 구성된 작은 망들이 서로 연결되어 인터넷망을 구성한다.


이때 출발지에서 목적지까지 이어지는 네트워크의 각 단계를 홉이라하며 홉들을 거치면서 거대한 인터넷 망에서도 올바르게 주소를 찾을 수 있게 된다.


각 라우터에서는 브로드캐스트가 일어나며, 내부망을 구성하는 머신들에게 출발지점에서부터 전달된 메시지가 뿌려진다. 이 메시지는 각 머신들에 연결되어 OSI 7계층을 통하는데, 목적지의 주소가 아니라면 중간에 필터링되게 된다. 


목적지에서는 마침내 통신이 이루어지고, 응답메시지가 다시 홉을 타고 출발지로 다시 전송되게 된다.




분산 파일 시스템(Distributed File System)은 이더넷 네트워크에 연결된 Shared File System 으로, 주로 네트워크 상에 존재하는 여러 Shard File System 들을 클러스터링하여 Virtual File System 형태로 제공하는 것을 말한다.

그렇기 때문에 Block level Access가 아닌 network protocol 로 각 파일시스템에 내부적으로 접근하게 된다.

 

주로 여러 네트워크로 연결된 파일시스템들이 공유폴더를 구축하고, 이를 DFS 가 Managing 하여 클러스터링하는 형태이다.
이 때, 연결된 각 File System 서버들의 파일들은 Globally Unique 하게 관리되며, DFS 서버에 장애가 발생할 경우 DFS 를 사용할 수 없게 되어있다.

 

그렇기 떄문에 DFS 파일 시스템은 서버 내에서 각 클러스터된 파일시스템들에 대한 Fault-Tolerance 를 관리해주게 되고, 때때로 고성능을 내기 위해 Parallel 한 클러스터를 구축하기도 한다.

 

Distributed File System 과 Distributed Data Store 의 차이는 DFS는 로컬에서 파일을 접근하는 것과 마찬가지의 Interface 를 제공한다는 점이다.
즉, 사용자는 실제 파일 시스템을 다루듯 디렉토리 리스팅, 파일 실행, 파일 삭제 등을 할 수 있다.

 

DFS 가 제공하는 이점은 다음과 같다.

 

- Data migration 의 단순화


- 데이터 사용의 용이함


- 보안상 관리의 이점


- 서버의 고가용성 확보

 

 

이러한 Distributed File System 은 일반적으로 사용할 때에는 중앙 DFS 서버에서 관리하나, 클라우드에서 프로비저닝 될 때에는 하나의 서비스 형태로 제공된다.


대표적으로 Amazon S3, Google Cloud Storage, Openstack SWIFT, Microsoft Azure 가 있으며 모두 HTTP 프로토콜을 이용한 REST API 형태로 인터페이스를 제공한다.

 


* NFS 역시 분산 파일시스템의 일종이다.


NFS(Network File System) 는 네트워크에 파일을 저장하는 메커니즘이다. 

원격 컴퓨터에 있는 파일 시스템을 마치 로컬의 파일시스템처럼 사용할 수 있도록 허용하는 분산 파일 시스템이다.


NFS File System 은 서버와 클라이언트로 이루어져있고, 일반적인 로컬 파일시스템과 똑같은 기능을 수행할 수 있는 인터페이스를 제공한다.




I/O Scheduling 은 운영체제가 여러 프로세스로부터 I/O 요청을 받았을 때 이에 대한 우선순위를 정해서 실행시켜주는 것을 말한다.

 

일반적으로 OS 내에 있는 스케줄러는 throughput 과 latency 를 고려하여 설계되며 하드디스크 검색으로 낭비되는 시간을 최소화하고, 우선적으로 필요한 프로세스들에게 I/O 요청을 할당하는 것이 주 목표이다.

 

I/O Scheduler 의 종류로는 다음과 같은 것들이 있다.

 

- Noop Scheduler : 커널이 스케줄링을 위해 별다른 작업을 하지 않는다. SSD 또는 RAID 디스크를 사용하는 경우 스케줄러의 동작이 오히려 디스크의 성능에 부하를 줄 수 있다는 점에 착안하였다.

 

- CFQ(Completely Fair Queueing) : 기본 스케줄러로, 각 프로세스마다 Round Robin 알고리즘에 의해 스케줄링이 수행된다. 각 작업을 마친 후 일정 time slice 만큼 기다린 후 다른 프로세스로 제어를 넘긴다. Synchronous 작업이 Asynchronous 작업보다 우선순위가 높다.

 

- Deadline : I/O 대기 시간의 한계를 정해놓고 데드라인이 가까운 작업부터 제어를 할당한다. 지연에 최적화되어있으며 I/O 가 몇몇 프로세스에 집중되어 있을 때 유용하기 때문에 Database 시스템에 주로 사용된다.

 

- Anticipatory : 연속된 read 요청의 경우 미리 처리량을 예측해서 요청을 스케줄링하는 방식으로 응답시간이 중요한 경우에 유용하다. 

 


컴파일 과정은 사람이 이해할 수 있는 High Level Programming Language 로 구성된 소스코드를 기계가 이해할 수 있는 Lower Level Language 로 바꾸는 과정이다.


컴파일러는 다음과 같은 과정을 통해 컴파일을 수행한다.


[출처 : https://www.programcreek.com/2011/02/how-compiler-works/]


위의 그림은 컴파일의 단계를 간략하게 설명한다. 다음은 그림에 대한 설명이다.


(1) Lexical Analysis 

소스코드를 Token 으로 분할한다. 모든 키워드와 Parenthesis, 변수들 및 괄호들을 분리해낸다.


(2) Syntax Analysis

앞선 단계에서의 스캔으로 만들어진 토큰들(Token Stream)의 문법을 분석하기 위한 자료구조료 변형한다.

이렇게 만들어지는 자료구조를 Parse Tree 라고 한다.

이 단계에서는 Token 이 Valid 한지 검출하지 못하며, Token 이 사용되기 이전에 정의 또는 초기화되어있는 지 등 정적분석은 불가능하다.


또한 이단계에서 파싱이 일어난다. Parsing 작업은 Top-Down, Bottom-Up 두가지 방식으로 나뉜다.

Top-Down Parsing 은 Parse Tree 의 윗쪽부터 파싱을 수행하며, Bottom-Up Parsing 은 트리의 아래쪽부터 파싱이 수행된다.


유명한 파서의 종류로 Top-Down 방식의 Non-Backtracking Predictive Parser 인 LL Parser와 Bottom-Up 방식의 LR Parser 가 존재한다.


(3) Semantic Analysis

각 기호들 및 구문들을 의미있는 값들로 변경한다. 가령 기호 < 는 bool 을 Return 하는 Operand 함수로,

While 과 같은 키워드는 반복 구조를 이루는 void 함수로, 각 변수는 메모리로 치환한다.

이 단계에서 Type 의 Mismatch 나, 변수의 미정의, 파리미터 미정의 등 문법적 요소들이 검증된다.


(4) IR Generation

구문 분석으로 이루어진 자료를 중간 언어로 변경하는 작업을 수행한다.

IR 은 Intermediate Representation 의 약어로 소스코드에 근접한 기계어인 High Level IR, 타겟 머신에 종속적으로 디자인된 Low Level IR 이 존재한다.

컴파일러는 소스코드를 High Level IR -> Low Level IR 로 변경한 뒤 Target Machine Code 로 해석한다.


(5) IR Optimization

중간 언어를 최적화한다. 불필요한 루프를 없애거나, 사용하지 않는 변수의 정리 등이 수행된다.


(6) Code Generation

Syntax Analyzer 및 Semantic Analyzing 된 출력값을 Low level Code 로 해석한다.

이 단계를 거쳐서 Assembly Code 등의 Object Code 로 번역된다.


이 단계에서 수행되는 작업들은 다음과 같다.

 - Instruction Selection : 어떤 명령어를 사용할 것인지

 - Instruction Scheduling : 어떤 명령어를 먼저 실행할지 (최적화)

 - Register Allocation : 변수들을 프로세서 레지스터에 할당

 - Debug data generation : 디버그 모드인 경우 디버그를 위한 코드를 생성


(7) Optimization

위의 과정에서 생성된 코드를 한단계 더 최적화한다.

이 단계의 최적화는 두단계로, Machine 에 종속되지 않은 일반적인 형태의 최적화와 Machine 에 종속된 최적화가 이루어진다.

이 과정을 거치면서 중복 제거, 메모리 확보, 코드 정리, 루프 최적화, Control Flow 개선이 일어난다.


위의 단계들을 거치면 High Level Source Code 는 Machine Code 로 변환된다.

Generating 되는 코드는 대게 머신별로 다르게 되며, 세부로직 및 최적화의 과정 역시 컴파일러에 따라 차이가 존재하게 된다.




본 포스팅은 컴파일러에 대한 간략한 소개를 다루고 있으며 더 자세한 내용은 다음 링크들을 참조한다.

(이해해야할 분량이 많다.)


참조

https://www.tutorialspoint.com/compiler_design/index.htm

https://www.programcreek.com/2011/02/how-compiler-works/




Linux 계열의 운영체제는 EXT 라는 파일시스템 구조를 사용한다.


* EXT(Extended file system) : 리눅스 용으로 개발된 파일시스템으로 Minix 파일시스템의 한계를 극복하고자 만들어졌다.


* EXT2 

 : Linux 의 초창기 파일시스템 표준이다. TIMESTAMP 의 제약이 있으며 장애 발생시 파일 시스템 복구를 위한 fsck 도구를 갖고 있다.


- EXT2 inode : inode 는 파일 시스템의 가장 기본이 되는 단위이다. 

각각을 구분할 수 있는 고유번호를 가지고 파일의 데이터가 어느 블록의 어느 위치에 저장되어 있는지, 파일에 대한 접근권한, 파일의 최종 수정시간, 파일의 종류 등의 정보를 inode 테이블에 저장한다.

저장되는 정보는 모드, 소유자 정보, 크기, Timestamp, 데이터블록이다. 


- EXT2 super block : 슈퍼블록은 해당 파일 시스템의 기본적인 크기나 형태에 대한 정보를 저장한다. 슈퍼블록에 저장되는 항목은 다음과 같다.


 (1) Magic number : 마운트하는 소프트웨어에게 EXT2 파일 시스템의 슈퍼블록임을 확인하게하는 값이다.


 (2) Revision level : major level과 minor level 로 구성되어 있으며, mount 시스템이 file system 에서 지원되는지에 대한 버전 정보 확인을 위해 사용된다. 

 또한 이 정보를 이용해 호환성을 판단할 수 있다.


 (3) Mount count : 마운트 횟수와 최대마운트 횟수를 저장한다. 마운트가 실행될 때마다 카운트가 증가하며 이 값을 이용해 시스템 전체를 검사할 필요가 있는지 확인한다.


 (4) Block group number : 블록 그룹번호는 슈퍼 블록 복제본을 갖고 있는 블록 그룹의 번호를 나타낸다. 


 (5) Block size : 블록 크기는 File System 의 블록크기를 바이트 단위로 표시한다.


 (6) Blocks per group : 그룹에 속한 블록 수를 저장한다.


 (7) Free block : 파일 시스템 내부적으로 존재하는 프리블록 수를 나타낸다.


 (8) Free inode : 파일 시스템 내부적으로 존재하는 free inode 수를 나타낸다.


 (9) First inode : 리눅스 시스템의 첫번째 inode는 "/" 디렉토리 엔트리를 나타낸다.


- EXT2 group descriptor : 블록그룹에서 블록의 할당 상태를 나타내주는 비트맵이다. 블록을 할당하거나 해제할 경우 참조되는 정보로, block bitmap, inode bitmap, inode table 을 구성요소로 갖는다.

inode 비트맵은 블록 그룹에서 inode 할당상태를 나타내주는 비트맵으로 그 수는 블록 비트맵과 같은 block 의 수와 동일하다.


- EXT2 directory : EXT2 파일 시스템에서 디렉토리는 파일에 대한 접근 경로를 생성하고 저장한다.

directory 는 entry 의 list로 나타내어 지며, 엔트리 리스트에는 inode, 이름길이, 이름 정보가 저장된다.



* EXT3

 : 저널링을 지원하는 호환성 높은 파일시스템 구조이다. 온라인 상태에서 파일시스템을 확장 가능하며, directory 에 대한 tree 기반 indexing 을 제공한다.

쓰기 실행중 fsck 가 불가능한 단점이 있다.


 - 저널링(Journaling) 기술

기존의 EXT2는 시스템에 문제가 생기는 경우 파일시스템이 손상될 수 있는 문제점을 갖고 있었고, 이를 해결하기 위해 fsck(File System Check) 라는 도구를 사용하였다.

하지만 복구에는 시간이 오래걸리고, 복구하는 동안 시스템의 사용으 불가능한 단점이 있었다. 

EXT3는 이 단점을 해결하기 위한 저널링(Journaling) 기능을 사용하며, 이를 통해 파일 시스템의 무결성과 복구 기능을 갖추게 되었다.

저널링 기술은 파일 수정 이전에 로그에 내용을 저장하고, 로그를 바탕으로 수정 내용을 처리하는 Replay 과정을 거치며, 이런 파일 시스템의 특성을

Logging File System 이라고 한다.


 - 저널링(Journaling) 파일 시스템의 구조

저널링을 수행하기 위한 로그영역과 일반 파일 시스템 영역으로 나뉜다. Log는 Rolling File 형태의 구조로 관리되며, 이는 전용 스레드가 처리한다. 

로그에 대한 트랜잭션이 별도로 관리되며, 트랜잭션 내의 레코드 I/O 를 바탕으로 파일시스템이 관리된다.



* EXT4

 : Linux 파일시스템 표준으로, Timestamp 가 나노초단위로 개선되어 있다. 

 ext2, ext3 를 ext4 로 마운팅 가능하며 호환성이 뛰어나다. 다음과 같은 특징들을 지닌다.


 (1) 대형 파일 시스템 : 최대 1 Exabyte 까지의 볼륨을 지원한다.


 (2) Extent : 전통적인 Block mapping 을 대체하기 위한 extent 방식을 사용한다. inode 에 최대 4개의 extent를 할당해서 물리적으로 인접한 블록들을 묶어서 관리한다.


 (3) Allocate on flush : 데이터가 디스크에 실제로 쓰여지기까지 블록 할당을 지연시키는 기술이다. 하나의 파일에 대해 블록의 분산을 막을 수 있고 디스크 이동을 최소화시킬 수 있다. 다만 장애 시 충돌 가능성이 존재한다.


 (4) 디렉터리 제한 변경 : 하위 Directory 에 대한 제한이 32000개에서 64000개까지 늘어났다. 




참조 : http://egloos.zum.com/nix102guri/v/392605




운영체제는 시스템의 Disk partition 상에 파일들을 연속적이고 일정한 규칙을 갖고 저장하는데, 이러한 파일을 관리하는 규칙을 File System이라 한다.


FileSystem 의 구조


Unix/Linux 계열의 운영체제는 Tree 형태의 계층적인 파일구조를 구성한다. 다음은 File System 을 구성하는 요소들이다.


- Super block : 슈퍼블록은 FileSystem에 의존하는 정보를 가지며 FileSystem 의 크기 등과 같은 File System 의 전체적인 정보를 포함한다.


- inode(Index node) : 아이노드(inode)는 파일의 이름을 제외한 해당 파일의 모든 정보를 갖고 있다.

갖고있는 정보로는 파일의 소유권, 권한(Permission), 실제 데이터가 있는 물리적 주소, 파일의 형태, 크기, 링크 수, 수정/사용 시간 등이 있다.

파일 이름은 inode 의 번호와 함께 directory 안에 저장된다.

각 파일들은 1개씩의 inode를 갖고 있으며, 파일 시스템 내에서 파일의 이름을 key 로 식별되는 inode 숫자를 통해 식별 가능하다. (즉, inode는 파일 시스템 내에서 유일하다.)


- data block : 데이터블록은 inode 에 포함된다. inode 는 몇개의 data block 을 포함하고 있으며, 각 데이터 블록은 파일들의 데이터를 실제로 저장하고 있다.


- Directory block : 파일 이름과 inode 번호를 저장한다.


- Indirection block : 간접 블록은 추가적인 데이터 블록을 사용하기 위한 포인터들이 할당되는 공간이다. 

실제로 inode 는 적은 수의 데이터블록을 갖고 있으며 더 많은 데이터블록이 필요할 경우 이를 동적으로 가리킬 포인터가 저장된다.


- Hole : 홀은 inode 나 간접 블록 안의 데이터 블록의 주소로 특별한 값을 저장한다. 

hole 을 파일 안에 구조화되지만 hole 을 위한 실질적인 disk 공간은 할당되지 않는다. 단지 0바이트가 파일 안에서 특정 공간을 차지하고 있다고 가정하는 것이다.


위의 개념들 중에서 중요하고도 특징적인 개념은 Inode 의 개념이다.

파일시스템은 inode 를 참조하여 파일의 정보를 다룰 수 있다.


( * 참고로 현재 파일시스템 내의 Total Block / Free Block, Total Inode / Free Inode 정보는 [stat -f "파일이름"] 명령어를 통해 조회가능하다.)


또한 위의 내용들을 자세히 이용하기 위해서는 Nix 계열의 파일시스템인 EXT에 대해서도 알아야 한다. EXT에 대해선 다음 포스팅에 추가한다.





좀비 프로세스(Zombie Process)와 고아 프로세스(Orphan Process) 는 흔히 볼 수 있는 프로그래밍 퀴즈 테마의 하나이다.


Unix / Linux 계열의 운영체제에서 프로세스 들을 관리하는 특징적인 방법으로 인해 나타나는 특이한 형태의 프로세스를 말한다.


부모 프로세스가 자식 프로세스보다 먼저 종료되면 자식 프로세스는 고아 프로세스가 되며, 자식 프로세스가 먼저 종료되었지만 부모 프로세스가 자식 프로세스의 종료 상태를 회수하지 못했을 경우에 자식 프로세스를 좀비 프로세스라고 한다.


리눅스의 코딩 시 fork()를 통해 자식 프로세스를 만들면 fork의 리턴값이 되는 pid로 부모와 자식을 구분할 수 있다. (pid>0 이면 부모, pid == 0 이면 자식) 

 

자식 프로세스가 작업을 종료하면 고아 프로세스의 경우 리눅스 시스템 상의 init 프로세스가 wait을 통해 자원을 회수하여 PID 가 1로 변한다. (대부분의 Linux 에서 init 프로세스의 PID 는 1이기 때문이다.)

반면 좀비 프로세스의 경우 부모 프로세스에서 wait 시스템콜을 사용해줘야 리소스 유출을 방지할 수 있다.



#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
// Create a child process
int pid = fork();

if (pid > 0)
printf("in parent process");
// Note that pid is 0 in child process
// and negative if fork() fails
else if (pid == 0)
{
sleep(30);
printf("in child process");
}
return 0;
}




참조 : https://www.geeksforgeeks.org/zombie-and-orphan-processes-in-c/

(너무 좋은 예제라 참조하였다.)





단일 프로세스 & 쓰레드를 갖는 프로그램을 개발할 경우에는 신경쓸 일이 거의 없지만 여러 작업루틴이 동시에 수행되며, 공유 리소스에 접근하게 되는 동시성 프로그래밍을 할 경우에는 반드시 신경써주어야 할 부분이 바로


Mutual Exclusion (상호배제) 문제이다. 이는 공유 불가능한 자원을 동시에 사용하게 될 경우 발생할 수 있는 충돌을 방지하기 위해서 Critical Section 을 만들고, 해당 영역에서 데이터를 사용하게끔 하는 방법을 사용한다.


(1) 세마포어(Semaphore)


이렇게 공유자원에 대한 동시성 문제가 발생하였을 때, 즉 여러 개의 프로세스 또는 쓰레드가 동시접근하는 문제를 방지하기 위해 고안된 것이 바로 세마포어이다. 


세마포어란 리소스의 상태를 나타내는 카운터를 지정하여 다중 프로세스에서 행동을 조정 및 동기화 시킬 수 있는 기술이다. 

여러 프로세스가 접근 시 여러 개의 Lock 을 할당하여 동시에 허용 가능한 Counter 의 제한을 둔다. 카운터가 1개로 0 / 1의 값을 가질 때 Binary Semaphore 라하고 이는 Mutex와 동작이 같다.


(2) 뮤텍스(Mutex)


뮤텍스는 Lock 을 가지고 있을 때에만 공유자원에 접근이 가능하게끔 하는 로직이다. 

즉, 세마포어가 여러 개의 락을 두어 제한된 리소스 접근을 허용하는데 반해 뮤텍스는 오로지 한 개의 쓰레드/프로세스만 할당한다.



* 세마포어(Semaphore) 와 뮤텍스(Mutex) 의 차이점


- 세마포어는 주로 시스템적 범위에 적용이 되며 뮤텍스는 프로세스 내에서 적용이 된다. 

(물론 세마포어와 뮤텍스는 매커니즘의 개념이기 때문에 국한된다고 할 수는 없다.) 주로 뮤텍스는 프로세스 내 쓰레드간 자원 접근에 대하여 적용이 되며 Lock 한 쓰레드가 Unlock 도 해주어야 한다. 

반면 세마포어는 Lock 을 건 소유주가 아니더라도 Unlock 이 가능하다.


- 세마포어는 동기화 대상이 하나 이상일 때, 뮤텍스는 하나일 때 사용된다.


동기화 처리 로직은 Lock 을 수반하며 이 Lock 이 여러 개의 작업 큐 내에서 걸릴 때 DeadLock 이 생길 우려가 있다.


데드락은 임계 영역 내의 전역 리소스에 대해 복수 개의 Lock에 의해 처리가 지연되는 현상이다. 

대표적으로 Critical Section 에 접근하여 공유 자원을 처리하는 여러개의 로직이 서로에 대한 의존도(Dependency)를 가질 때 발생한다.


다음과 같은 상황을 가정해보자.




위의 그림에서 프로세스1 은 Resource 1을 선점하고 있으며, Resource 2에 대한 작업처리를 요구한다.

Resource 2에 대한 처리를 완료하면 Resource 1을 사용할 수 있도록 Critical Section 바깥으로 반환할 것이다.

반대로 프로세스2 는 Resouce 2를 선점하고 있으며 Resource 1에 대한 작업 처리를 마친 후 Resource 2를 반환할 것이다.


위의 상황에서 두 프로세스는 서로 선점하는 Resource가 다르므로 동시에 Critical Section 진입이 가능하지만, 서로의 자원을 요구하는 탓에 탈출은 불가능하다. 이 상황을 Deadlock이라고 한다.


Deadlock 이 발생하면 자원의 누수 및 동작의 교착상태가 계속되기 때문에 어플리케이션 또는 시스템에 치명적이며 따라서 문제 해결을 위해 다음과 같이 관리 한다.


- 교착 상태의 예방

(1) Mutual Exclusion 조건 제거

(2) 사용할 때에만 해당 자원을 점유하고 사용하지 않을 때에는 해당 자원을 다른 프로세스가 사용할 수 있도록 양도

(3) 선점 가능한 프로토콜 제작

(4) 자원 접근에 대한 순차적 처리 


- 교착 상태의 회피

 : 자원 요청에 대해 Circular Wait를 방지하기 위한 할당 상태를 검사한다.


- 교착 상태의 무시

 : 확률이 낮은 경우 별도의 처리를 하지 않는다.



+ Recent posts