diff --git a/operating-system/context-switch/han/README.md b/operating-system/context-switch/han/README.md new file mode 100644 index 0000000..5fac1af --- /dev/null +++ b/operating-system/context-switch/han/README.md @@ -0,0 +1,96 @@ +# Interrupt + +- 프로세스가 하던일을 멈추고, *이미 정해진 코드*에서 요청에 대한 처리를 **수행**하는 것. +- 각 자원들이 능동적으로 자신의 상태 변화를 CPU에게 알리는 방식. + +- `Polling` + - *CPU*가 일정한 시간 간격을 두고, 각 장원들의 상태를 주기적으로 확인하는 방법 + - Interrupt는 자원들이 CPU에게 자신의 상태를 알리는 방법이고.. +- 인터럽트는 하드웨어, 소프트웨어 인터럽트로 나눌수 있을듯. + - 하드웨어는.. 모니터 마우스 등등이고.. + - 소프트웨어는 CPU 자신이 인터럽트를 사용하는 경우 인듯. + + + +- Interrupt가 실행되는 과정 + + ![image](https://user-images.githubusercontent.com/22140570/136755289-4b270209-cbc6-4250-8070-1e76658b5c5d.png) + + - PC ? + - CPU가 실행하는 명령어 + - 레지스터에 있음 + + 2. 장치 (키보드) 의 인터럽트 발생 + + 3. 현재 실행 중인 프로세스 정보 저장 + 1. 어디에? 시스템 스택에. + 2. 어떤 정보? PSW(Program status Word, 현재 상태 정보), PC 레지스터의 값 (CPU가 어떤 명령어를 실행하고 있었는지 ) + + 4. Interrupt Vector에 가서, 요청들어온 인터럽트에 대한 ISR을 찾음. + + 5. 찾은 ISR에 대한 주소를 PC에 넣음 (인터럽트 처리를 위해서..) + + 6. 인터럽트 처리 + + 7. 저장된 프로세스 정보를 가져와서, 이 전 실행 되었던 프로세스 실행으로 돌아감 + +- 참고 키워드 + - Interrupt Service Routine, ISR (Interrupt Handler) + - Interrupt Vector + - 여러가지 인터럽트들을 관리하는 테이블 + + + +# Context switching + +- 위 이미지에서 (3,4,5)번 과정 + +- 하나의 프로세스가 cpu를 사용하는 상태인데, 다른 프로세스가 cpu를 사용하게 하기 위해서... 발생하는 것. + + - 즉 이전 프로세스의 상태를 **보관** 하고, 새롭게 실행된 프로세스의 상태를 **적재하도록 하는 과정**, 작업을 의미. + +- 과정 + + ![image](https://user-images.githubusercontent.com/22140570/136755834-7938b4ae-86bb-408e-8cb4-ee7fc909435a.png) + + - 인터럽트 과정과 같음 + + 1. P0 실행 중.. 그런데 P1 프로세스에서 인터럽트 or 시스템 콜 발생 + 2. P0 프로세스에 대한 정보를 PCB에 저장. 그리고 P1에 대한 정보를 PCB에서 찾아서, 메모리에 올림 + 3. P1 실행 + 4. 위 반복. + + + +- Context Switching이 왜 발생? + - CPU는 한번에 하나의 프로세스만 처리할 수 있기에. + - 여러 프로세스를 실행, 중단 하면서 작동하기에. + - 이 비용이 비싸기에, Mutil Thread 환경이 나오지 않았을까 + + + +- 참고 키워드 + - idle : CPU가 아무일도 하지 않는 상태 + - idle이 겹칠 경우를 오버 헤드라 말함. + - 왜? + - 필요한 정보들을 적재하느라, CPU가 아무일도 하지 않는 상태이기 때문에 (일을 안해서, CPU가 낭비되고 있음) + - 즉 프로세스가 많아지면, 적재과정 때문에 CPU가 안하는 일이 많아져서 오버헤드가 증가할듯. + - PCB (Process Control Block) + - 레지스터에 있고, Queue (LIFO) 구조임. + + - process state : 프로세스 상태 값 (Create, Ready, Runinng..) + + - procuess counter : CPU가 다음 실행할 명령어의 주소 값. + + + ![](https://nesoy.github.io/assets/posts/20181113/1.png) + + + +# 참고 + +- https://www.youtube.com/watch?v=-4HKhwlH3FQ +- http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788993712476 +- https://nesoy.github.io/articles/2018-11/Context-Switching +- https://jeong-pro.tistory.com/93 + diff --git a/operating-system/deadlock/han/README.md b/operating-system/deadlock/han/README.md new file mode 100644 index 0000000..a9a175c --- /dev/null +++ b/operating-system/deadlock/han/README.md @@ -0,0 +1,157 @@ +# Deadlock이란? + +- 교착 상태 + +- 둘 이상의 프로세스가 각자가 가지고 있는 자원을 보유한 채, 외부 조치 없는 한 영원히 그 상태에서 기다리고 있는 상황을 의미. + + - 즉 어떤 자원을 가지고 있고 + 무슨 이유에서 인지, 외부 조치 없이는 무조건 기다리게 되어있는 상황을 의미 + + + +## 실제 시스템에서 교착 상태 + +- Database (MySQL) + + - **상호 거래 패턴** + + ![image](https://user-images.githubusercontent.com/22140570/136698857-2e340bae-151d-47fa-85a1-c22c007c8f0e.png) + + - 트랜잭션 1 은 A를 점유하고 있음., 트랜잭션 2는 B를 점유하고 있음. + - 이 상태에서 트랜잭션1 는 B에게 접근하려 함 (이미 트랜잭션 2가 점유하고 있는 상태) + - 그런데 트랜잭션 2은 A에 접근하려 함 (이미 트랜잭션 1이 점유하고 있는 상태임) + - 점유가 풀려야, 해당 데이터에 접근할 수 있는데, 현재 상태로 보아서, 점유가 풀릴 수가 없는 상태임. (교착 상태) + + + +## 교착 상태를 만족하기 위한 4가지 필요조건 + +- 아래 4가지 조건은 만족하게 되면, 교착 상태에 빠지게 된다. + - 아래 4가지 중에 하나만이라도 생기지 않도록 할 수 있다면, 교착 상태는 절대 발생하지 않는다. + + + +### 상호 배제 조건 + +> *mutual exclusion condition* + +- 자원의 배타적인 사용이라 부르기도 함. + +- 한번에 프로세스 하나만 해당 자원을 사용할 수 있음. + - 다른 프로세스가 위 자원을 사용하려 하면, 기다려야 한다. + - 한정된 자원에 대한 프로세스들의 사용 경쟁을 의미. + + + +### 점유와 대기 조건 + +> *hold and wait condition* + +- 자원의 부분 할당이라 부르기도 함. + - 각각의 프로세스는 자신의실행 전체 과정에서 자원이 필요할 때 마다, 일부분을 확보, 실행해나가다가, 할당 불가능한 자원 때문에 교착 상태에 빠진다. + +- 즉 프로세스는 자원을 최소한 하나 보유하고, 다른 프로세스에 할당된 자원을 위해 대기하는 프로세스가 존재함을 의미. + + + +### 비 선점 조건 + +> *nopremption condition* + +- 자원의 선점 불가능성이라 부르기도 함. + +- 이미 할당된 자원을 강제로 뻈을수는 없음 + - 즉 자원의 선점 불가능성을 고수하는 경우, 해당 조건 때문에 교착 상태를 일으키는 조건을 만족하게 되기도 함. + + + +### 순환 대기 조건 + +> *circular wait conditon* + +- 대기 프로세스의 집합이 순한 형태로 자원을 대기 + + + + + +## 교착 상태 해결 방법 + +### 예방 + +- 위 4가지 발생 조건 중에, 하나라도 발생하지 않도록 하는 것. +- 예를 들면.. + - 상호 배제 조건 의 경우, **여러 프로세스** 들이 해당 자원을 사용할 수 있도록 해주는 것. + - 다만, 배타적으로 사용할 수 밖에 없는 자원도 있기에, 상호 배제 조건을 배제하는건 불가능할듯. + - 점유와 대기 조건의 경우, 자원이 부분할당 되지 않고, 필요한 자원을 모두 할당해 버리는 것. + - 자원의 낭비 발생. 심각하게.. + - 비 선점 조건의 경우, 모든 자원이 선점 가능하도록 해주는 것. + - 즉 어떤 자원을 A 프로세스가 잡고 실행 중에 있는데, B 프로세스 에서 해당 자원을 요청할 경우, A프로세스는 자신이 보유하고 있는 자원을 내놔야함. + - A 프로세스 입장에서는.. 잘 하고 있는 도중 자신의 일의 결과를 모두 뺏길 수 있음 (중단 혹은 다시 시작 가능성 있으므로.) + - 자원 낭비 + - 순환 대기 조건을 배제하는 경우, 자원의 요청 순서를 단 방향으로만 하도록 하는 것일듯. + - 그래도 자원 낭비, 무한 대기는 피해갈 수 없을듯. (모든 경우의 수를 따질 수 없으므로) + + + +### 회피 + +> *Safe sequence, Safe state* + +- 교착 상태를 피해가게 하는 방법 + +- 안전 상태? + - 프로세스들이 요청하는 모든 자원을, 교착 상태에 빠지지 않으면서 모두에게 자원을 할당해줄 수 있는 상태 +- 안전 순서? + - 특정 순서로 프로세스들에게 자원을 할당해줬더니, 교착 상태가 발생하지 않았음. 이러한 순서. +- 불안전 상태? + - 안전 상태가 아닌 상태 + - 교착 상태 발생 가능성이 있다. + +- 은행원 알고리즘 + + - 시스템을 안전 / 불안전 상태로 구분 + - 불안전 상태이면, 할당할 자원을 고정, 프로세스 수도 고정, 제한된 시간안에 자원 반납등의 조건이 전제됨. + +- 회피 전략을 즉 자원을 요청할 때마다, 시스템의 안전 상태를 파악해야함 (오버헤드 심함) + + - 이러한 점 때문에, 해당 전략을 사용하는 시스템은 거의 없다. + + + +### 탐지 및 복구 + +- 교착 상태가 자주 발생한다면 사용. +- 교착 상태는 **필요악** + - 왜? + - 교착 상태는 안 만들어지는게 좋지만, 교착 상태가 발생할 수 없는 환경을 만들어 버린다면, 할당된 자원을 효율적으로 사용하는 것은 불가능한 일이 됨. +- 탐지 + - RAG Resource Allocation Graph + - 자원 할당 그래프. + - 교착 상태 탐지를 위해, 현 시스템의 상황을 나타내는 그래프임. + - Allocation, Request, Available + - **순환 대기 조건**이 존재하는 지 탐지함 (시스템의 자원 상태를 확인함) + - 탐지 알고리즘을 사용..해서 오버헤드 존재 +- 복구 + - 순환 대기를 깨서, 교착 상태로 부터 회복하도록 함. + - 순환 대기에 포함된 **프로세스의 제어권을 뺏고 롤백**.. 혹은 순환 대기가 깨질 때까지 **프로세스 종료** + - 전자 최소 비용의 프로세스를 고를 수 있지만, 이를 계산하는 데 복잡 + - 후자 프로세스를 종료할 때 마다, 교착 상태가 해결 되었는지 확인해야함 (오버헤드) + - 어떤 프로세스를 깰까? + - 시스템마다 다른 기준으로 우선 순위 + - MySQL의 경우, 트랜잭션의 크기가 가장 작은.. + +### 무시 + +- 교착 상태가 드물게 발생한다면 이 방법을 사용 +- 드물게 발생하는데, 굳이 교착 상태 해결 비용을 미리 지불할 필요는 없을듯. +- 즉 교착 상태가 발생했다? 그러면 사용자가 원인이 되는 프로세스, 스레드를 죽이는 방법을 택함. + + + +# 참고 + +- https://www.youtube.com/watch?v=FXzBRD3CPlQ + +- https://chanhuiseok.github.io/posts/cs-2/ +- http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788993712476 + diff --git a/operating-system/memory-hierarchy/han/README.md b/operating-system/memory-hierarchy/han/README.md new file mode 100644 index 0000000..44ba23b --- /dev/null +++ b/operating-system/memory-hierarchy/han/README.md @@ -0,0 +1,42 @@ +# 메모리 계층 구조란? +![](https://www.elprocus.com/wp-content/uploads/Memory-Hierarchy.jpg) + +- CPU 접근 속도에 따라 메모리의 계층을 나눠놓은 것. +- 4가지 특징이 있음 + +## Capacity +- 크기 +- 상위 계층일수록 작음 (레지스터가 가지는 크기는 가장 작다는 의미) + +## Access Time +- 접근 시간 +- CPU가 메모리에 read/write 하는 데 걸리는 시간 +- 상위 계층 일수록 빨리 접근할 수 있을듯. + +## Performance +- 수행 능력 +- 얼마나 CPU가 효율적으로 움직이는지 에 대한 이야기 인듯. +- 과거 메모리 계층 구조 없이, 메모리가 구성되었을 때는 메인 메모리든 레지스터든 접근 시간 때문에 수행 능력이 비슷하게 안 좋았음. + - 시스템 수행 능력 강화를 위해, 해당 계층 구조가 등장했다고 보면 될듯. + +## Cost per bit +- 1bit 당 가격을 생각하면, 상위 계층일수록 비쌀 것. + + + +# Primary Memory +- CPU와 가장 접근성이 좋은 내부 메모리 +- 보통 메인, 캐시, 레지스터 를 의미 + +# Secondary Memory +- 외부 메모리 (CPU 외부에 존재하는 듯 ) +- I/O 작업이 필요하다 +- Disk..등이 이에 해당됨. + +# Summary +![](https://media.vlpt.us/images/ogs0518/post/992f6465-cf96-4bd7-8afb-85086bb245fb/Untitled.png) + +# 참고 +- https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EA%B3%84%EC%B8%B5_%EA%B5%AC%EC%A1%B0 +- https://www.geeksforgeeks.org/memory-hierarchy-design-and-its-characteristics/ +- https://www.elprocus.com/memory-hierarchy-in-computer-architecture/ \ No newline at end of file diff --git a/operating-system/memory-hierarchy/sigrid/README.md b/operating-system/memory-hierarchy/sigrid/README.md new file mode 100644 index 0000000..cbb46c5 --- /dev/null +++ b/operating-system/memory-hierarchy/sigrid/README.md @@ -0,0 +1,19 @@ +# 메모리 계층 구조 +![](https://i.imgur.com/Ga7efAW.png) +## 개요 +* 메모리 계층 구조는 메모리에 필요에 따라 여러 종류로 나누어 둠으로서, CPU가 메모리에 훨씬 빨리 접근하도록 유도하기 위함이다. + * Registers, Cache: CPU 내부에 존재하므로, 빠르게 접근할 수 있다. + * 캐시 + * ![](https://i.imgur.com/YIk9tLP.png) + * Memory: CPU 외부에 존재하므로, Registers와 Cache보다 느리게 접근할 수 있다. + * RAM: Random Access Memory. RAM은 어느 위치에 저장된 데이터든지 접근(읽기 및 쓰기)하는 데 동일한 시간이 걸리는 메모리이기에 ‘랜덤(Random, 무작위)’이라는 명칭이 주어졌다. + * Hard Disk: 직접 CPU에 접근할 수 없다. CPU가 하드 디스크에 접근하기 위해서는, 하드 디스크의 데이터를 메모리로 이동시킨 후에야 메모리에 접근해야 한다. 따라서 매우 느리다. + +## 특징 +### 비용적 측면 +* 메모리 구조에서 상층으로 갈수록 더 비싸진다. +### 참조의 지역성 (Locality of Reference) +* 자주 쓰이는 데이터는 자주 쓰이고, 그렇지 않은 데이터는 그렇지 않다. CPU는 자동으로 자주 쓰이는 데이터이고, 또는 자주 쓰일 것 같은 데이터를 캐시로 읽어온다. +* 자주 쓰이는 데이터는, 전체 데이터 양에 비해 작은 양이다. 따라서, 캐시는 메모리보다 더 작아도 된다. +### 속도적 측면 +* CPU와 가까이 있는 레지스터가 가장 빠르게 접근 가능하고, 밑으로 내려갈 수록 접근 속도가 느려진다. diff --git a/operating-system/mutex-semaphore/han/README.md b/operating-system/mutex-semaphore/han/README.md new file mode 100644 index 0000000..20fd13d --- /dev/null +++ b/operating-system/mutex-semaphore/han/README.md @@ -0,0 +1,62 @@ +# Mutex +- Mutual (mut) + Exclusion (ex) 의 합성어 +- 상호 배제를 의미 +- 여러 스레드를 실행하는 환경, 자원에 대한 접근을 강제하기 위한 동기화 방식 + +> Mutual Exclusion + +- 하나의 프로세스가 공유 자원을 사용할 때, 다른 프로세스가 해당 자원에 접근하지 못하도록 하는 것. + - 즉 공유 자원에 점유할 수 있는 프로세스, 스레드의 수는 한 개라는 의미..? +- 어떻게 접근하지 못하도록 할까? +- 어떻게 다른 프로세스가 공유 자원이 사용할 수 있는지, 없는지 알 수 있을까? + +> 작동 방식 + +1. Lock + - 공유 자원에는 `Boolean Lock` 변수가 있음 + - Lock = true을 한 스레드만 Lock을 풀 수 있음. + - 그리고 공유자원을 사용하려하고는 스레드들을 위한 **대기큐**가 있음. + - 현재 공유자원을 점유하고 있는 스레드가 있을 경우, 이 자원에 접근하는 스레드들을 `blocking` 시키고, 대기큐에 적재하여 `sleeping` 상태로 변경해둠. + - 공유 자원의 Lock 변수가 false라면, 비어져 있다면, 대기큐에서 하나 깨워서 점유하게 만듬. + +2. SpinLock +- Busy-wating 방식 +- 공유자원에 접근하는 스레드는, 계속 공유 자원에게 자리가 있는지 계속 물어보는 방식 (대기큐가 없다) +- 그럼 비효율적 + - 왜? + - 해당 공유자원을 점유하고 있는 스레드 뿐만 아니라, 물어보는 다른 스레드의 요청까지 처리해줘야 하므로.. +- 그럼 어떤 상황에 위 방식을 사용할까? + - 대기큐를 사용하는 방식보다, 공유자원에게 물어보는 시간이 짦다면.. 굳이 대기큐를 사용하지 않아도 될듯. (컨텍스트 스위칭 시간이 짦으면..) + - 다른 스레드의 질의를 처리할 여유가 있을 때 (멀티 프로세스 일 때) + + +# Semaphore +- 다수의 프로세스, 스레드가 여러 개의 공유자원에 대한 접근을 제한하는 방법 +- Mutex랑은 다르게, 여러 프로세스, 스레드가 공유자원에 접근할 수 있다는 차이점이 있음 (뮤텍스는 하나의 스레드가 공유자원에 접근할 때의 접근을 제어하는 방식을 이야기 하는듯) +- P(wait), V(signal) +- 변수는 정수형 (뮤텍스는 boolean... 변수) + +> 작동 방식 + +1. Sleep - wait +- 대기큐 사용 +- 공유 자원에 자리가 생기면, 대기큐에서 잠자고 있는 스레드를 꺠우는 방식 + +2. SpinLock +- Busy - wating 방식 + +# 정리 + +1. Mutex를 이용하는 환경은, 공유 자원에 하나의 소비자 (프로세스 혹은 스레드) 가 점유가 가능할 때 사용함. +2. 엄밀하게 말하면 Mutex는 locking mechanism임. 즉 공유 자원을 잠그고, 열는 과정에 대한 것인듯. + - 즉 자원에 동기적인 접근이 가능토록 하는 방법 + - 뮤텍스를 통해 공유자원을 잠근 소유자만, 뮤텍스를 통해 공유자원을 릴리즈 할 수 있다 (뮤텍스에 대한 소유권이 존재한다.) + +3. Semaphore를 이용하는 환경은, 공유 자원에 여러 소비자 (여러 프로세스, 혹은 여러 스레드) 가 점유 가능할 때 사용하는 방법 인듯. +4. Semaphore는 signaling mechanism 임 (소비자가 다른 소비자에게 알려주는 방식) + - 어떤 자원을 점유하고 있는 소비자 중 한명이, 자신이 다 끝났으면 다른 대기하는 소비자에게 알려주는 방식인듯 + +# 참고 +- https://www.youtube.com/watch?v=oazGbhBCOfU +- https://en.wikipedia.org/wiki/Semaphore_(programming) +- https://www.geeksforgeeks.org/mutex-vs-semaphore/ \ No newline at end of file diff --git a/operating-system/mutex-semaphore/sigrid/README.md b/operating-system/mutex-semaphore/sigrid/README.md new file mode 100644 index 0000000..2bb8213 --- /dev/null +++ b/operating-system/mutex-semaphore/sigrid/README.md @@ -0,0 +1,296 @@ +# 세마포어와 뮤텍스 +## Process Synchronization vs Thread Synchronization +* 현대 운영체제는 Thread Synchronization 위주로 동작한다. +* Process 1의 Thread A가 실행되고, Process 1의 Thread B가 실행되고 이후 Process 2의 Thread C가 실행되는 형태로 진행된다. 즉, Context Switching의 기준이 현대 OS에는 Thread인 것이다. 각 프로세스 내의 Thread 1, Thread 2… 동일 프로세스 내에서 서로 다른 쓰레드들이 번갈아가며 처리된다. +* Independent Process란 Process 1, Process 2 가 아무런 관계가 없는 독립적인 프로세서인 경우를 의미한다. 이와 달리, Cooperating Processor는 Process 1, Process 2가 서로 영향을 주고 받는 경우를 의미한다. 예를 들면, 프로세스 간 자원공유가 필요한 경우(DB 등)을 꼽아볼 수 있다. +* Cooperating Processor의 경우, 쓰레드 간의 공유자원을 사용하므로 동기화 작업이 필요하다. 공유 자원에 대한 동시 접근(concurrent access)는 데이터 일관성을 깨뜨리므로, cooperating process 간의 순서있는 실행(orderly execution)을 통해 데이터 일관성을 유지해야 한다. +## Example: Bank Account Problem +``` +// Test.java +class Test { + public static void main(String[] args) throws InterruptedException { + BankAccount b = new BankAccount(); + Parent p = new Parent(b); + Child c = new Child(b); + p.start(); // start(): 쓰레드를 실행하는 메서드 + c.start(); + p.join(); // join(): 쓰레드가 끝나기를 기다리는 메서드 + c.join(); + System.out.println("balance = " + b.getBalance()); + } +} + +// 계좌 +class BankAccount { + int balance; + void deposit(int amount) { + int temp = balance + amount; // 임계구역 코드 + System.out.print("+"); // 시간 지연 의도를 위한 코드 + balance = temp; + } + void withdraw(int amount) { + int temp = balance - amount; // 임계구역 코드 + System.out.print("-"); // 시간 지연 의도를 위한 코드 + balance = temp; + } + int getBalance() { + return balance; + } +} + +// 입금 프로세스 +class Parent extends Thread { + BankAccount b; + Parent(BankAccount b) { + this.b = b; + } + public void run() { // run(): 쓰레드가 실제로 동작하는 부분(치환) + for (int i = 0; i < 100; i++) + b.deposit(1000); + } +} + +// 출금 프로세스 +class Child extends Thread { + BankAccount b; + Child(BankAccount b) { + this.b = b; + } + public void run() { + for (int i = 0; i < 100; i++) + b.withdraw(1000); + } +} +``` +* 위 코드에 대한 출력값은 다음과 같다. 만약 시간 지연을 위한 코드(System.out.print 함수 호출)이 없었다면, balance 값은 0이 되었을 것이다. +``` +++++++++++++++++++++++++++++++++++---------------------------------------------- +--------------------------------------------------------------------------++++++ ++++----------------------------------------------+++++++++++++++++++++++++++++++ ++----+++++++-+++++----+++------------------------------------------------------- +-+++++++-++++-+++++++++-------++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++---------------+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++-++++++++++++-------------------++++++++++++++++++++-++++++++++++++++++++++++++ +++++++-+------------------------------------------------------------------------ +-+++++++++++-+++++++----------------------------------------+-------+----------- +-+------+----------------------------------------------------------------------- +-+------------------------------------------------------------------------------ +-+------------------------------------------------------------------------------ +-------------------+-------+---------------------------------------------------- +------------------------------+------------------------------------------------- +------------------------------------------------------+------------------------- +-+------------------------------------------------------------------------------ +-++---------------------------------------++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +balance = 1000000 +``` +* 간단한 실험을 통해, 우리는 시간 지연이 주어질 때 여러 쓰레드가 하나의 공유 자원을 사용하는 프로그램은 망가진다는 사실을 알 수 있다. 출력되는 결과는 운영체제에서 쓰레드를 스위칭하는 패턴이 매번 다르므로 수행할 때마다 다르다. 이는 공통변수, 즉 계좌의 잔액(common variable)에 대한 동시 업데이트(concurrent update) 가 이루어지기 때문이다. Java와 같이 High-level 언어에서는 한 줄로 족하지만, assembly code로 번역될 때 여러 줄로 바뀌므로 중간에 스위칭이 발생할 수 있다. +* 이를 해결하기 위해서는, 공통변수에 해당하는 임계구역(critical section)을 설정해 임계구역 코드에 접근하는 쓰레드가 단 하나만 존재하도록 해야 한다. +``` +// 해결 코드 +class Test { + static final int MAX = 100; // 입출금 회수 + public static void main(String[] args) throws InterruptedException { + // 은행계좌를 만들고 + BankAccount b = + new BankAccount(); + // 부모 쓰레드와 + Parent p = new Parent(b, MAX); + // 자식 쓰레드를 만든 후 + Child c = new Child(b, MAX); + // 각각 실행시킨다. + p.start(); + c.start(); + p.join();// 부모와 자식 쓰레드가 + c.join();// 각각 종료하기를 기다린다. + System.out.println("Final balance = " + + b.getBalance());// 최종 잔액 출력 + } +} + +class BankAccount { + int balance; + void deposit(int amount) { + balance = balance + amount; + } + void withdraw(int amount) { + balance = balance - amount; + } + int getBalance() { + return balance; + } +} + +class Parent extends Thread { + BankAccount b; + int count; + Parent(BankAccount b, int count) { + this.b = b; + this.count = count; +} + public void run() { + for (int i=0; i 최초 S값은 1임 + while S=0 do wait --> S가 0면 1이 될때까지 기다려야 함 + S := S-1 --> S를 0로 만들어 다른 프로세스가 들어 오지 못하도록 함 +end P + +--- 임계 구역 --- + +procedure V(S) --> 현재상태는 S가 0임 + S := S+1 --> S를 1로 원위치시켜 해제하는 과정 +end V +``` +* 위 코드에서 acquire 함수는 P에 해당하는 것으로서, 쓰레드가 임계구역에 진입하는 경우 value 값을 감소시키는 역할을 한다. 만약 value 값이 0보다 작으면 해당 임계구역에 어느 쓰레드가 존재한다는 뜻이므로, 새로운 쓰레드가 접근하지 못하도록 막아야 한다. +* If the semaphore value is negative, its magnitude is the number of processes waiting on that semaphore. 즉, value의 절댓값 크기만큼 현재 프로세스/쓰레드들이 임계구역 접근 번호표를 뽑은 셈이 된다. 이와 달리, value의 초기값은 1이므로 1일 때는 프로세스가 직접 접근 가능하다는 뜻이며 0이면 현재 누군가가 쓰고 있으니 잠깐만 대기하라는 뜻이다. +* 대기열은 list(Queue)로 구현하되, 원소 추가 이후 block을 걸어준다. +* 위 코드에서 release 함수는 V에 해당하는 것으로서, 쓰레드가 임계구역에 빠져나가는 경우 value 값을 증가시키는 역할을 한다. 만약 value 값이 0보다 크면 임계구역에 진입하려고 대기하는 프로세스가 list(Queue)에 남아있다는 의미이므로 순서대로 상위 프로세스를 임계구역에 접근하도록 해 주어야 한다. +### 예시: 최초 S 값은 1이고, 현재 해당 구역을 수행할 프로세스 A, B가 있다고 가정하자. +* 먼저 도착한 A가 P(S)를 실행하여 S를 0으로 만들고 임계구역에 들어감 +* 그 뒤에 도착한 B가 P(S)를 실행하지만 S가 0이므로 대기 상태 +* A가 임계구역 수행을 마치고 V(S)를 실행하면 S는 다시 1이 됨 +* B는 이제 P(S)에서 while문을 빠져나올 수 있고, 임계구역으로 들어가 수행함 +* Bank Account Problem을 Semaphore를 활용하여 해결하면 다음과 같다. 쓰레드 동기화를 통하여 임계구역 문제를 해결하였고, 이에 따라 +- 출력을 제외하고 balance 값이 0 출력된다. +``` +import java.util.concurrent.Semaphore; +class BankAccount { + int balance; + Semaphore sem; + BankAccount() { + sem = new Semaphore(1);// 초기값 = 1 + } + void deposit(int amount) { // 입금 + try { + sem.acquire(); // 진입 전: acquire() + } catch (InterruptedException e) {} + int temp = balance + amount; + System.out.print("+"); + balance = temp; + sem.release(); // 나온 후: release() + } + void withdraw(int amount) { // 출금 + try { + sem.acquire(); // 진입 전: acquire() + } catch (InterruptedException e) {} + int temp = balance - amount; + System.out.print("-"); + balance = temp; + sem.release(); // 나온 후: release() +} +int getBalance() { + return balance; +} +} +``` +### Ordering +* 세마포어를 사용하는 이유 중 하나는 ordering하기 위함인데, 이는 프로세스의 실행 순서를 원하는 대로 설정하는 것에 있다. +* 예시: 프로세스가 Process 1, Process 2 두 개가 있다고 가정하자. 원하는 순서는 Process 1, Process 2 순으로 실행하기를 원한다. 그러면 아래와 같이 설정해줄 수 있다. + +https://velog.io/@codemcd/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9COS-8.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%8F%99%EA%B8%B0%ED%99%94-1 + +* 위의 사진은, Process 1 > Process 2 순서로 프로세스 실행됨이 보장된다. +* P1이 먼저 실행된 경우는 다음과 같다. + * Section 1 이전에 아무런 동작이 없으므로 바로 수행한다. + * sem.release() 를 만나면 value값을 1 증가시키고, 세마포 큐에 있는 프로세스를 깨워주는데 현재에는 큐에 프로세스가 없으므로 아무 동작도 하지 않는다. + * P2가 실행된다. + * P2의 sem.acquire() 를 만나면 현재 value값은 1이고 이를 1감소시키면 0이 된다. value = 0이면 block을 하지 않으므로, 무사히 Section 2가 수행된다. +* P2가 먼저 실행된 경우는 다음과 같다. + * Section 2 이전에 sem.acquire() 가 있으므로 이를 수행하는데, 현재 value값은 0이고 이를 1 감소 시키면 -1 이 된다. value값이 음수면 해당 프로세스를 block 시킨다. 즉, 세마포 큐에 삽입한다. + * P1이 실행되면 Context Switch으로 Section 1이 바로 수행된다. + * sem.release() 를 만나면 value값을 1 증가시키고, 세마포 큐에 있는 P2 프로세스를 깨워준다.(현재 value = 0) + * P2의 Section 2가 수행된다. +* Bank Account Problem을 Ordering으로 해결해보면 다음과 같다. + * 입금(Parent) 또는 출금(Child)가 먼저 실행되는 경우: 상호배타를 위한 sem 세마포어 및 실행순서 조정을 위한 sem2 세마포어 할당. + * 프로그램 시작 시, 부모 쓰레드는 그대로 실행하도록 하고 자식 쓰레드가 실행되는 경우 sem2 세마포어에 의해 일단 block되고 이후 부모 쓰레드를 실행하여 자식 쓰레드가 이후에 블록된 상태에서 벗어나도록 설정. 참고: https://zzsza.github.io/development/2018/07/30/process-synchronization/ + * 구체적으로, 초기값이 0인 sem2 세마포어에 대해 acquire()를 호출하게 하도록 deposit(), withdraw() 메소드를 각각 수정 +``` +class BankAccount { + int balance; + + Semaphore sem, sem2; + BankAccount() { + sem = new Semaphore(1); + sem2 = new Semaphore(0); // Ordeing을 위한 세마포 + } + + void deposit(int amount) { + try { + sem.acquire(); + } catch (InterruptedException e) {} + int temp = balance + amount; + System.out.print("+"); + balance = temp; + sem.release(); + sem2.release(); // block된 출금 프로세스가 있다면 깨워준다. + } + void withdraw(int amount) { + try { + sem2.acquire(); // 출금을 먼저하려고 하면 block한다. + sem.acquire(); + } catch (InterruptedException e) {} + int temp = balance - amount; + System.out.print("-"); + balance = temp; + sem.release(); + } + int getBalance() { + return balance; + } +} +``` +* 입출금이 교대로 이루어지는 경우(Child-Parent-Child-Parent…): 블록된 부모 쓰레드는 자식 쓰레드가 깨워주고, 블록된 자식 쓰레드는 부모 쓰레드가 각각 깨워주도록 한다. + * 상호배타를 위한 sem 세마포어 외에 부모 쓰레드의 블록을 위해 dsem 세마포어를, 자식 쓰레드의 블록을 위해 wsem 세마포어를 각각 사용한다. + * 잔액이 항상 0 이상인 경우: 출금하려는 액수보다 잔액이 작으면 자식 쓰레드가 블록되도록 하며 이후 부모 쓰레드가 깨워주게 한다. + * 상호배타를 위한 sem 세마포어 외에 sem2 세마포어를 사용하여 잔액 부족시 자식 쓰레드가 블록되도록 한다. diff --git a/operating-system/user-kernel-thread/han/README.md b/operating-system/user-kernel-thread/han/README.md new file mode 100644 index 0000000..79a5309 --- /dev/null +++ b/operating-system/user-kernel-thread/han/README.md @@ -0,0 +1,65 @@ +# Kernel 이란? +![](https://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/Kernel_Layout.svg/400px-Kernel_Layout.svg.png) + +- OS에 핵심 부분인, 컴퓨터 프로그램을 의미. +- 시스템의 모든 것을 제어할 수 있도록 도와주는 부분. +- 항상 메모리에 상주하고 있으며, 하드웨어와 소프트웨어의 상호작용을 할 수 있도록 도와주는 핵심적인 인터페이스를 의미. + +# Kernel level thread +- 커널이 모든 프로세스, 스레드를 관리하는 방식을 의미 (스케쥴링등.. 모두 커널에서 관리) +- 1개의 Process table, Thread table을 통해서 관리. +- Context swiching이 자주 일어남. + +## Advantages +- 커널이 존재하는 모든 스레드의 정보를 알고 있기 때문에, 스케쥴러에서 적절하게 각 스레드별로 CPU 자원을 배분할 수 있음 +- non-blocking system call이 필요하지 않음. + - 즉 만약 하나의 프로세스에 여러개의 스레드가 있다고 가정 하고 어떤 스레드(프로세스를 잡고 있는)가 Blocking 상태에 들어간다고 하여도, 다른 스레드에 작동에 영향을 주지 않음. + - 커널이 그 프로세스에, 다른 스레드가 runnable한 상태임을 알고 있으므로, 스케쥴러에게 다른 스레드를 실행하라고 이야기 해줄듯. + +## Disadbantages +- 느리고 비율적임. + - 모든 스레드가 커널에 의해 관리되므로 (System call), 이러한 관리는 비용이 큼. +- 복잡도가 높음. + - 커널이 모든 스레드의 정보를 알고 있어야 하므로, + - 이러한 정보는 TCB(Thread Control Block) 에서 관리되는, 이러한 정보를 유지시키는 것이 상당한 오버헤드를 초래함. + +# User level thread +- 커널은 쓰레드의 존재를 알지 못함 (즉 관리하지 않는다.) +- 커널은 단지 프로세스를 관리하는 테이블 (Process table) 만 가지고 있을뿐, 각각의 프로세스들이 스레드를 관리하는 테이블을 가지고 있음 (Thread table) + - 즉 여러개의 스레드는 하나의 프로세스 (Many to One) 관리한다고 보면 됨. +- Context swiching 이 커널 레벨 스레드 보다 적게 일어남. +- 커널에 진입하지 않아도, 라이브러리 지원을 통해 스레드를 사용할 수 있게 함. +- Ex) Jvm with thread + +## Advantages +- 하나의 프로세스가 여러개의 스레드를 관리하는 만큼, 스레드에 수행에 필요한 정보가 해당 프로세스에 모두 저장되어있음. +- 그래서 빠르고 효율적임. (즉 context switching이 적어서) +- 각각의 프로세스가 다른 스케쥴링 알고리즘을 사용하도록 커스텀 할 수 있음. + +## Disadbantages +- 어떤 프로세스에 여러 개의 스레드가 있다고 가정할 때, + - 하나의 스레드가 커널을 호출 한다면 (System call) + - 해당 프로세스 내, 모든 스레드가 중단될 것 (Blocking System) + - 그래서 user level thread를 사용하기 위해서는 non-blocking system call이 필요함. (커널을 호출하더라도, blocking 되지 않도록..?) + +# 정리 +![](https://examradar.com/wp-content/uploads/2019/02/threads-types.png) +- (좌측) User level thread , (우측) Kernel level thread + +- 유저 레벨에서 스레드를 관리하는 것이라는 뜻은, + - 라이브러리등을 통해 하나의 프로세스에서 여러개의 스레드를 사용하게 만드는 것인듯. + - 커널은 어떤 스레드가 있는지 모르고, 프로세스의 존재만 알고 있음. + - 그래서 상대적으로 관리할 비용이 줄어들고 (프로세스만 관리하면 되니), 효율적으로 프로세스-스레드를 관리할 수 있도록 도와주는 측면이 있는듯. + - 다만, 커널이 스레드를 관리하지 않는다는 건, 스레드의 상태를 잘못할 경우, 해당 프로세스는 blocked 상태로 변경되고, 다른 스레드들에 영향이 가는 결과를 초래할 수 있을듯. + +- 커널 레벨에서 스레드를 관리하는 것은.. + - 커널이라는 프로그램이 모든 스레드의 정보를 알고 있어야 함. + - 이 점이 굉장히 비용이 큰 사안일듯. + - 다만, 커널이 관리해주므로, 프로세스가 죽는 block되는 사태는 벌어지지 않을듯. + +# 참고 +- https://en.wikipedia.org/wiki/Kernel_(operating_system) +- https://happy-chipmunk.tistory.com/entry/11-Multithreading2-Thread-%EC%9D%98-%EC%A0%81%EC%9A%A9-Userlevel-Threading-%EA%B3%BC-Kernellevel-Threading +- https://www.geeksforgeeks.org/difference-between-user-level-thread-and-kernel-level-thread/ +- https://examradar.com/os-threads-different-types-thread-questions-answers/ +- https://colinch4.github.io/2020-02-02/%EC%BB%A4%EB%84%90%EB%A0%88%EB%B2%A8%EC%8A%A4%EB%A0%88%EB%93%9C-vs-%EC%9C%A0%EC%A0%80%EB%A0%88%EB%B2%A8%EC%8A%A4%EB%A0%88%EB%93%9C/ \ No newline at end of file diff --git a/operating-system/user-kernel-thread/sigrid/README.md b/operating-system/user-kernel-thread/sigrid/README.md new file mode 100644 index 0000000..423ddf2 --- /dev/null +++ b/operating-system/user-kernel-thread/sigrid/README.md @@ -0,0 +1,85 @@ +# 사용자 수준 쓰레드 vs 커널 수준 쓰레드 +![](https://i.imgur.com/4rFNHLc.png) +## Kernel Thread +* The kernel knows about and manages all threads. +* One process control block (PCP) per process. +* One thread control block (TCB) per thread in the system. +* Provide system calls to create and manage threads from user space. +* 대표적으로 입출력 I/O 작업 등을 꼽아볼 수 있다. +* 스케줄러가 컨텍스트 스위칭 하는 단위는 커널 스레드이고 컨텍스트 스위칭으로 저장되는 정보가 Process Control Block(PCB)이다. +## User level threads +* User level threads are supported above the kernel in user space and are managed without kernel support. +* Threads managed entirely by the run-time system (user-level library). +* Ideally, thread operations should be as fast as a function call. +* The kernel knows nothing about user-level threads and manage them as if they where single-threaded processes. +## Case Study +![](https://i.imgur.com/M9kfk74.png) +* 프로세스에 현재 스레드 4개를 생성하였다. 유저 레벨 스레드이다. +![](https://i.imgur.com/zzKN5Q1.png) +* 프로세스가 CPU 사용을 위해 OS에게 스레드 2개를 달라고 요청한다. +![](https://i.imgur.com/2URJg2V.png) +* 2개의 스레드를 현재 프로세스에서 쓸 수 있도록 할당해준다. 커널 레벨 스레드이다. +* OS의 스케줄러에 의해 현재 프로세스를 보고 있는 커널 레벨 스레드를 CPU에 할당한다. 해당 프로세스를 보고 있는 커널 레벨 스레드가 동작하기 시작한다. 사용자 레벨 스레드에 있는 스레드 중 1개가 커널 레벨 스레드와 연결되어 프로세스에서 작업을 진행할 수 있다. +![](https://i.imgur.com/8iWkBJJ.png) +* 위의 그림은 커널 레벨 스레드는 그대로 컨텍스트 스위칭을 당하지 않았지만 사용자 레벨 스레드는 컨텍스트 스위칭을 당해 작업하는 스레드가 바뀜을 알 수 있다. +![](https://i.imgur.com/luUQZut.png) +* 커널 레벨 스레드가 OS의 스케줄러에 의해 컨텍스트 스위칭을 당하면서 TCB가 저장, 스왑되고 어떤 인터럽트가 들어와서 프로세스 자체가 컨텍스트 스위칭될때 PCB가 저장, 스왑된다. + +**Q: 사용자 스레드 방식이 커널 스레드 방식보다 오버헤드가 적은 이유는?** +* A: 스레드간 전환할 때마다 커널 스케줄러를 호출할 필요가 없기 때문이다. +## Multithreading Model +### N:1 사용자 모델 +![](https://i.imgur.com/pR4M13E.png) +* 프로세스 1개(사용자 스레드 N개) 당 커널 스레드 1개가 할당된다. +* 프로세스 내에 스레드 라이브러리가 있어서 커널의 도움없이 스레드의 스케줄링을 할 수 있다. +* 커널은 프로세스 내의 스레드의 존재를 모른다. +* 스레드 정보(TCB, Thead Control Block)는 프로세스 내에서, 프로세스 정보(PCB, Process Control Block)는 커널에서 관리한다. +* 장점으로는, 스레드의 스케줄링 및 동기화에 시스템콜(커널호출)이 필요없기 때문에 오버헤드가 적다. +* 단점으로는, + * 동작 중인 스레드가 시스템콜을 하면 해당 프로세스 내의 모든 스레드가 멈춘다. (Blocking system call) + * 프로세스 단위로 CPU가 할당되므로, 다중CPU환경에서 한 프로세스 내 스레드들을 동시에 실행할 수 없다. (Hardware Parallelism 지원안함) + * 다중 처리 시스템에서는 사용할 수 없다. + * 커널이 스레드를 관리하지 않아, 프로세스에서 스레드 간 보호를 해줘야 한다. +### 1:1 커널 모델 +![](https://i.imgur.com/iULY4j0.png) + +* 프로세스 내의 사용자 스레드 1개 당 커널 스레드 1개가 할당된다. +* 사용자 스레드를 생성하면 할당할 커널 스레드를 1개 생성한다. +* 프로세스 내에 스레드 라이브러리가 없어서 커널 스레드를 스케줄하여 매핑된 사용자 스레드를 동작시킨다. +* 커널이 전체 TCB와 PCB를 관리한다. +* 장점으로는, + * 동작 중인 스레드가 시스템콜을 해도 해당 프로세스 내의 다른 스레드가 동작할 수 있다. + * 스레드 단위로 커널 스레드가 할당되므로, 다중CPU환경에서 한 프로세스 내 스레드들을 동시에 실행할 수 있다. (즉, 시스템 동시성을 지원한다고 말할 수 있다) +* 단점으로는, + * 스레드 문맥교환할 때도 시스템콜이 필요하고 커널이 모든 TCB와 PCB를 관리하여 오버헤드가 크다. + * CPU, RAM 성능에 따라 생성 가능한 커널 스레드 및 사용자 스레드가 한정되어 있다. + +### 혼합된 스레드 모델 (N:M) +![](https://i.imgur.com/Vh2V3gc.png) +[링크](https://www.geeksforgeeks.org/relationship-between-user-level-thread-and-kernel-level-thread/) +* 커널 스레드 마다 1개씩 경량 프로세스(LWP, Light Weight Process)가 매핑되어 있다. +* LWP는 가상 처리기로써, 1:1 모델처럼 한 프로세스에 여러 개가 할당될 수 있고, 프로세스에 할당된 LWP는 N:1 모델처럼 여러 개의 사용자 스레드를 관리한다. (커널과 프로세스 간의 중간자 역할을 한다.) +* 따라서 프로세스 1개(사용자 스레드 N개)에 M 개의 커널 스레드를 할당할 수 있다. (N ≥ M) +* 장점으로는, + * 사용자 스레드 생성 개수가 제한되지 않는다. (N:1) + * 시스템콜을 해도 해당 프로세스 내 (다른 LWP에 연결된) 스레드는 멈추지 않는다. (1:1) +* 단점으로는, + * LWP가 블록되면 LWP에 연결된 모든 사용자 스레드가 멈춘다. (N:1) + * LWP의 사용자 스레드 문맥교환도 1:1 모델만큼 오버헤드가 크다. (1:1) + * 각 LWP에 한 개의 사용자 스레드가 실행되지 않으면, 1:1 모델에 비해 다중CPU환경에서 효율적이지 않다. (N:1) + +**Q: 디스크 입력의 경우 사용자 수준 쓰레드 모델과 커널 수준 쓰레드 모델 중 어느 것이 유리할까?** +A: Kernel-level Thread 모델이 유리하다. +* User-level Thread 모델에서는 스레드가 디스크 입출력을 할 때마다 해당 프로세스 내의 모든 스레드가 중단된다. 반면에, Kernel-level Thread 모델은 커널 스레드가 사용자 스레드에 1:1로 할당되므로 한 스레드에서 시스템콜을 해도 해당 프로세스의 다른 스레드가 중단되지 않는다. +* 유저 레벨 스레드를 이용하면 컨텍스트 스위칭을 프로세스 내부에서 진행하면 되고, 커널로 진입하지 않아도 되어 비용이 적게 든다. + * 즉, 스레드의 스케줄링 및 동기화에 시스템콜(커널호출)이 필요 없기 때문에 오버헤드가 적다. +* 그리고 커널 레벨 스레드는 커널이 직접 스레드를 관리해주는 것이기 때문에 유저 스레드 1개당 커널 1개가 맡아준다. 따라서 하나의 프로세스에 여러 유저 스레드가 있어도 동시에 실행이 가능하다. +* 하지만 여기서 유저 레벨 스레드에 I/O를 이용하면 치명적인 단점이 존재한다. +* 유저 레벨 스레드를 이용하면 커널은 프로세스 내의 스레드 존재를 모르기 때문에 하나의 유저 레벨 스레드가 I/O를 하면 나머지 모든 스레드가 멈추게 된다. +* 따라서 I/O를 하기 위해서라면 커널 레벨 스레드를 이용하는게 더 유리하다. + + +### Reference +https://www.crocus.co.kr/1404 +http://www.it.uu.se/education/course/homepage/os/vt18/module-4/implementing-threads/ +https://www.geeksforgeeks.org/relationship-between-user-level-thread-and-kernel-level-thread/ diff --git a/operating-system/virtual-memory/han/README.md b/operating-system/virtual-memory/han/README.md new file mode 100644 index 0000000..55bf8dc --- /dev/null +++ b/operating-system/virtual-memory/han/README.md @@ -0,0 +1,79 @@ +# 가상 메모리란? +- 메모리 관리 기법 +- 실행하고자 하는 프로세스가 모두 메모리에 올라오지 않아도, 실행하도록 하는 기법임. +- 왜 사용할까? + 1. 물리 메모리의 제약에서 벗어남 (즉 정해져있는 메모리보다 더 많이 사용하는 것 처럼 보임) + 2. 각 프로그램이 더 작은 메모리를 통해, 실행 가능 하기 때문에 여러 프로그램이 동시에 실행가능함. + 3. I/O가 적게 일어남. (왜?) + - 필요한 페이지만 올리기에, 페이지 사용하기 전보다 I/O가 적게 일어날 것. + +## 왜 필요할까? +- 현실적으로 실행되어야할 모든 프로세스의 정보를 물리 메모리에 올려 놓을 순 없다. + - 그러면 자주 사용되는 프로그램의 정보만 우선 물리 메모리에 올려 놓으면 어떨까? +- 즉 프로그램 실행에 필요한 메모리가 적재될 때, 연속적으로 저장될 필요 없음 (다른 프로그램의 메모리와 섞여 적재) + - 그러면 메모리를 더 효율적으로 사용할 수 있을듯. + +## 어떻게 필요한 정보만 물리 메모리에 올릴까? +> Demand Paging (요구 페이징 기법) + +![image](https://user-images.githubusercontent.com/22140570/143865107-61ddee97-88af-45c8-9e7d-07c6d4e5621d.png) +- 주소 공간은 페이지로 구성되어 있음 (뭉쳐있음.) +- 필요한 페이지만 물리 메모리에 그때 그때, 물리 메모리에 올리는 방식 + - *유-무효 비트*를 통해, 페이지가 물리메모리에 있는 지 없는지 알 수 있음. + - 만약에 필요한 페이지가 물리 메모리에 없으면? + - Page fault + - 페이지 폴트 발생 시, 보조 저장 장치 (swap area..?) + +> CPU 가 페이지를 찾는 과정 + +![image](https://user-images.githubusercontent.com/22140570/143865553-572ec7f4-c995-44f4-9c11-cbf6cce70525.png) + +1. 논리적 메모리에서 특정 페이지를 찾음 (1) +2. 페이지 테이블 참고 + - Invalied bit (물리 메모리에 해당 페이지가 없다) +3. Page fault + - Swap area에서 참고하여, 물리적 메모리에 올림 +4. 다시 페이지 테이블에 접근하여, 물리 메모리 주소를 찾음. + +- 페이지 테이블을 프로세스당 하나... +- 테이블 접근하기 전에, 찾는 TLB(Translation Lookaside Buffer) 라는 공간도 있음. + - 여기서 찾고자하는 논리 주소의 값이 있으면 바로 물리 주소를 반환. + - 없으면 페이지 테이블로 조회하러 감. + +## CPU의 연산 이란? +![image](https://user-images.githubusercontent.com/22140570/143863331-7a8fc482-7353-4a67-80ae-fca2c413d434.png) +- CPU는 연산할 때, 메모리의 값을 참조한다. + - 참조하는 메모리 값은 보통 레지스터에 있음 (아주 용량이 작은 메모리) + - 레지스터에 원하는 정보가 없으면 Main memeory (보통 Physical Memeory라 부름) 에서 참조하게 됨. + - CPU는 main memory의 값까지만 참조할 수 있게 됨. + + +## 프로그램이 실행되는 것이란? +![image](https://user-images.githubusercontent.com/22140570/143863461-c861d110-e1dd-4c71-bd70-b323744f634a.png) +- 프로그램 실행 이란, 실행에 필요한 정보들을 메모리에 올리는 걸 의미. + +## 주소 바인딩이란? +![image](https://user-images.githubusercontent.com/22140570/143863654-b0247cb0-80cd-481c-987d-7ee3d2b7de0b.png) +- 논리 주소(Logical Address)가 물리 주소(Physical Address)로 맵핑되는 것 +- 논리 주소는 물리 주소와 같을 수도, 다를 수도 +- 물리적 주소가 결정되는 시점에 따라서, 주소 바인딩이 나뉜다. + 1. 컴파일 + 2. 로드 타임 + 3. 실행 시간 + +## MMU 메모리 관리 장치 +![image](https://user-images.githubusercontent.com/22140570/143864193-540a994c-b658-4ac5-bbdd-5ade9ead399f.png) + + +## Swap Area +![image](https://user-images.githubusercontent.com/22140570/143864436-33723399-f0d3-418d-9619-70ef6bf66a3c.png) +- 외부 장치 (I/O 작업 발생!) +- 물리 메모리의 공간이 부족하기 때문에 등장. +- 실행 중인 프로세스의 메모리 주소를 일시적으로 Disk에 저장하는 것. +- 스왑 영역에 저장되는 주소들은, 프로세스가 동작 중에만 즉 **일시적**으로 저장됨. + + +# 참고 +- https://www.youtube.com/watch?v=5pEDL6c--_k +- http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788993712476 +- https://cyber0946.tistory.com/51 \ No newline at end of file diff --git a/operating-system/virtual-memory/sigrid/README.md b/operating-system/virtual-memory/sigrid/README.md new file mode 100644 index 0000000..6768ac9 --- /dev/null +++ b/operating-system/virtual-memory/sigrid/README.md @@ -0,0 +1,131 @@ +# 가상 메모리란? +## 개요 +* 가상 메모리는 물리 메모리의 용량 한계를 극복하려는 목적을 지니고 있다. + * 예를 들어, 물리 메모리가 100MB인데 200MB의 소프트웨어를 구동하고자 하는 것이다. +* **당장 필요한 부분만** 메모리에 적재시켜 구동하는 방식을 이용한다. +* 기본적으로 가상 메모리의 용량은 무제한이지만, 실질적으로는 물리 메모리의 용량에 국한된다. 가상 메모리의 용량은 **물리 메모리 + 스왑영역**이다. + +## Demanding Paging +![](https://i.imgur.com/ezgs1jj.png) +* 페이징이란 **논리 메모리 == 가상 주소**를 fixed-size로 분할하는 것을 의미하고, 프레임이란 **물리 메모리**를 fixed-size로 분할하는 것을 의미한다. [참고](https://jhnyang.tistory.com/290) +* 페이징 기법은 **고정 분할 방식**을 이용하는데, 물리 주소 공간을 **같은 크기** 로 분할하는 것을 의미한다. +* 페이지와 프레임은 서로 크기가 같으므로, 1:1 매핑이 가능하다. 매핑 내역은 Page Table에 기록된다. +* 현재 프로세스 실행에 요구되는 페이지만 메모리에 올리는 것을 **Demanding Paging**이라고 한다. +* Page Table에는 Valid bit이 추가되어, 해당 페이지-프레임의 메모리 상 존재 여부를 나타낸다. 만약 현재 페이지가 메모리에 적재되어 있다면 1을, 적재되어 있지 않다면 0을 나타낸다. +* 현재 페이지 2번이 메모리에 적재되어 있지 않고, Valid bit도 0으로 표시되어 있다. 페이지 2번을 메모리에 적재시키는 과정을 한 번 살펴보도록 하자. +![](https://i.imgur.com/aGcbk30.png) +* CPU에서 P1의 2번째 페이지에 접근하는데 valid bit값이 0이다. 그러면 CPU에 인터럽트 신호를 발생시켜서 OS 내부의 ISR로 점프한 후, 디스크 내부에 있는 프로세스 P1에 있는 2번째 페이지를 메모리에 할당하는 작업을 수행한다. **-> 과정 더 알아보기** + +### Page Fault (페이지 실패) +![](https://i.imgur.com/dBh57Ap.png) +* CPU가 접근하려는 페이지가 메모리에 없는 경우이다. 다시 말해서, 페이지 테이블의 valid bit가 0인 경우이다. +* Page Fault가 발생했을 때 처리하는 과정이다. + * 해당 페이지가 메모리에 있는 지 보기 위해 valid bit을 확인한다. + * valid bit이 0이라면, CPU에 인터럽트 신호를 보내어 OS 내부의 해당 ISR로 jump한다. + * 해당 ISR에서 디스크[backing store]를 탐색하여, 해당 프로세스의 페이지를 찾는다. + * 해당 페이지를 비어있는 프레임에 할당한다. + * 페이지 테이블에, 프레임 번호를 설정하고 valid bit이 1로 변경하여 해당 테이블을 갱신한다. + * 다시 명령어로 돌아가서 실행한다. + +### Pure Demanding Paging +* 프로세스가 최초로 실행될 때는 어떤 페이지가 필요한지 알 수 없으므로, 아무 페이지도 올리지 않는다. +* 그러므로 프로그램을 실행하자마자 page fault가 발생한다. 즉, 순수하게 필요한 페이지만 올리는 것을 말한다. +* Pure Demanding Paging의 장점은 메모리를 최대한 효율적으로 사용할 수 있다. 하지만 시작부터 page fault가 발생하므로 속도 면에서 느리다. + +### Pre-paging +* pure demanding paging과 반대 개념이다. +* 프로그램을 실행할 때 필요할 것이라 판단되는 페이지를 미리 올리는 것이다. +* 장점은 page fault가 발생할 확률이 적으므로 속도면에서 빠르지만, 단점으로 미리 올라간 페이지를 사용하지 않는다면 메모리가 낭비된다. + +### Swapping & Demanding Paging +* 공통점은 둘 다 메모리와 backing store 사이를 서로 오고 가는 기능을 수행한다. +* 차이점은 Swapping은 프로세스 단위로 이동하고 Demanding Paging은 페이지 단위로 이동한다. + +### Effective Access Time +* Demanding Paging은 페이지 테이블에 해당 페이지가 없으면 backing store에서 메모리로 가져오는 과정이 있다. 따라서, 페이지 테이블에 해당 페이지가 있을 때와 없을 때 시간 차이가 발생한다. +* 이러한 시간 차이를 고려하여 **평균적으로 어느 정도 소요되는지 계산하는 것을 유효 접근 시간**이라 한다. + +``` +p: 페이지 부재 확률(probability of a page fault = page fault rate) +Tm: 메모리를 읽는 시간 +Tp: Page fault가 발생했을 때 소요되는 시간(대부분 backing store(디스크)를 읽는 시간이 차지한다.) +T = (1-p)Tm + pTp +``` +``` +Tm = 200nsec (DRAM) +Tp = 8msec (seek time + rotational delay + transfer time) +T = (1-p)200 + p(8,000,000) = 200 + 7,999,800 * p +p = 1/1,000 => T = 8.2usec (40배 정도 느림) +p = 1/399,990 => T = 220nsec (10% 정도 느림) +``` +* 페이지 부재의 확률은 극히 낮다. 지역성의 원리(Locality of reference)에 의하기 때문이다. 지역성의 원리는 시간적 지역성, 공간적 지역성이 있다. + * **시간적 지역성**: CPU는 어느 메모리 공간을 읽은 후, 시간이 지나도 그 공간을 다시 읽을 확률이 매우 높다는 것을 의미한다. + * **공간적 지역성**: CPU는 어느 메모리 공간을 읽을 때, 인접한 범위 내에서 읽는다는 것을 의미한다. 특히, 절차적 프로그래밍으로 구현되어 있을 경우 순서대로 읽는 경우가 빈번하다. + +## Page Replacement +* Demanding Paging은 요구되는 페이지만 backing store에서 가져온다. 하지만 프로그램들이 계속 실행함에 따라, 요구 페이지도 계속 늘어나게 된다. 그러다보면, 언젠가는 메모리가 가득 차게 될 것이다. +* 여기서, 다른 프로그램이 새로 실행되거나 실행중인 프로세스가 다른 페이지를 요구한다면, 이미 메모리에 적재되어 있는 backing store로 보내고 -- 이를 **page-out**이라고 한다. +* 한 편으로, 이미 backing store로 page-out이 된 페이지를 **victim page**이라고 한다. + +## Victim Page +![](https://i.imgur.com/vJme440.png) + +* 그러면 어느 페이지를 탈락시켜야 할 것인가? 메모리에 올라가 있는 페이지 중, CPU에 수정(modify)되지 않는 페이지를 골라야 한다. + * 수정되지 않은 페이지는 page-out이 될 때, backing store에 쓰기(write) 연산을 하지 않아도 된다. backing store는 읽기(read) 시간도 느리기 때문에, 쓰기 연산까지 하면 시간이 더욱 느려질 것이다. + * 해당 페이지가 수정되었는 지, 수정되지 않았는지 파악하기 위하여 페이지 테이블에 modified bit(dirty bit)를 추가하여 이를 검사한다. 해당 페이지가 수정되었다면 modified bit을 1로, 수정되지 않았다면 0으로 둔다. + * 만약 수정되지 않은 페이지가 여러 개가 있다면, 랜덤으로 하거나 FIFO로 하는 등 알고리즘을 경우에 따라 선택할 수 있다. + +![](https://i.imgur.com/dl99Unh.png) + +## Other Paremeters in Page Table Entry +* Page base addresses +* Flag bit + * Accessed bit: 접근이 있었나? + * Dirty bit: 내용이 수정된 적이 있나? + * Present bit: 현재 페이지에 할당된 프레임이 있나? + * Read/Write bit: 읽기와 쓰기 권한이 있나? + +## 페이징 기법의 동적 주소 변환 과정 +* 가상 메모리의 스왑 공간에 있는 가상 주소를 물리 메모리의 실제 주소로 변환하는 과정을 **동적 주소 변환** 이라고 한다. +![](https://i.imgur.com/hh38gWH.png) +* 가상 주소를 물리 주소로 변환하는 과정 + 1. 가상 주소 30번지가 어느 페이지에 있는지 찾음 -> 페이지 3의 0번째 위치 + 2. 페이지 테이블의 페이지 3으로 가, 해당 페이지가 프레임 1에 있음을 알아냄 + 3. 물리 메모리 프레임 1의 0번째 위치에 접근 -> 가상 주소 30번지의 물리 주소 +* 가상 주소에 값을 저장할 때의 주소 변환 과정 +![](https://i.imgur.com/QOargpt.jpg) + 1. 가상 주소 18번지가 어느 페이지에 있는지 찾음 -> 페이지 1의 8번째 위치 + 2. 페이지 테이블의 페이지 1로 가, 해당 페이지가 프레임 3에 있음을 알아냄 + 3. 프로세스가 저장하려는 값을 프레임 3의 8번 위치에 저장 +* 부족한 물리 메모리의 크기 == 스왑 영역으로 대체 +* 페이지 테이블 수는 프로세스의 크기와 일치 + +### 페이지 테이블의 매핑 방식 +![](https://i.imgur.com/MauGglv.png) + +#### 직접 매핑 +* 페이지 테이블 전체가 운영체제 영역에 위치하는 경우 +* 특징 + * 모든 페이지 테이블이 물리 주소에 있다. + * 변환속도가 빠르다. + * 메이지 테이블의 시작 주소는 페이지 테이블 기준 레지스터(PTBR)가 가지고 있다. +#### 연관 매핑 +![](https://i.imgur.com/uIeNT7d.png) +* 페이지 테이블 전부가 스왑영역에 위치하는 경우 +* 특징 + * 일부 테이블만 무작위로 선정하여 물리 메모리로 가져온다. +* Translation Lookasider Buffer (TLB) +![](https://i.imgur.com/4xrEkMC.png) + * 가상 메모리 주소를 물리적인 주소를 변환하는 속도를 높이기 위해 사용하는 버퍼 + * 페이지 테이블은 주 기억장치(물리)에 기본적으로 위치하므로, 페이지 테이블에 접근하는 과정 하나와 기억 장치에 필요한 데이터 두 번을 액세스하는 과정이 필요함 + * TLB가 hit일 경우 가상 주소를 물리 주소로 변환하기 위한 페이지 테이블에 접근할 필요가 없음. + * TLB는 메모리가 아닌 프로세서에 내장되어 있기에 훨씬 빠름. + * TLB의 프레임 주소를 토대로 데이터를 불러오기 위한 1번의 메모리 접근만 있으면 됨. +* 탐색 과정 + * TLB를 사용하여 페이지가 물리 메모리(프레임)에 올려져 있는지 확인한다. + +## Reference +* https://velog.io/@codemcd/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9COS-15.-%EA%B0%80%EC%83%81%EB%A9%94%EB%AA%A8%EB%A6%AC +* https://velog.io/@thalals/OS-8.%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%9D%98-%EA%B8%B0%EC%B4%88 +* https://velog.io/@kjh3865/%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC-Virtual-Memory +* http://jidum.com/jidums/view.do?jidumId=473