-
Chapter 10 (Virtual Memory)운영체제(OS) 2022. 12. 10. 17:35728x90
Motivation of Virtual Memory
프로그램을 실행하려면 메모리에 있어야 하지만, 프로그램 전체가 사용되지는 않습니다.
프로그램의 일부분만 메모리에 있어 실행되면 많은 이점을 가져올 수 있습니다.
가상 메모리란 물리 메모리로 부터 사용자 logical(가상) 메모리 분리 입니다.
즉, process 전체가 메모리에 올라오지 않더라도 실행이 가능하도록 하는 기법입니다.
(= 프로그램이 실행되는데 최소한 얼만큼의 메모리가 필요한지에 집중)
나머지는 보조기억 장치, 즉 디스크에 저장되있습니다.
주요 장점 중 하나는 사용자 프로그램이 물리 메모리 보다 커져도 된다는 것입니다.
따라서 개발자들은 더이상 물리 메모리 크기에 국한받지 않고 개발할 수 있습니다.
Virtual Memory > Physical Memory
Virtual Address Space
virtual address space는 process가 메모리에 저장되는 논리적인 모습을 말합니다.
특정 가상 주소에서 시작하여 연속적인 공간을 차지합니다.
(0 ~ max)
물리 메모리는 frame들로 구성되며 process에 할당된
frame들이 실제로는 연속된것들이 아닐 수 있습니다.
logical page를 physical frame들로 맵핑 시켜주는 것은
MMU입니다.
힙은 동적 할당 메모리를 사용함으로써 주소공간이
위로 확장됩니다.
스택은 함수 호출에 따라 주소 공간이 아래로 확장됩니다.
process가 동적 메모리를 요청할 때, 추가적인 frame을
받지 않고 대신 새로운 범위의 가상 주소 공간을 사용할 수 있는
권한을 얻게 됩니다.
Shared Library Using Virtual Memory
가상 메모리는 page 공유를 통해 파일이나 메모리가 둘 또는 그 이상의 process들에 의해 공유되는 것을
가능하게 합니다.
이를 통해 process들이 메모리를 공유할 수 있습니다.
Copy-on-Write (COW)
fork를 하게 되면 부모 process를 복제해 자식 process를 만들어 냅니다.
그러면 자식 process는 exec()를 이용해 새로운 process로 태어납니다.
그렇게 되면 부모로 부터 복사해온 page들은 다 쓸모없는 것들이 됩니다.
그럼 굳이 새로운 복사를 할 필요가 있을까? 해서 나온게 Copy on Write 입니다.
COW는 부모 process가 가지고 있는 물리 메모리의 주소를 공유하는 방식을 통해 자식 process를 사용하는
방법 입니다.
자식 process가 생성될 때, 부모 process의 page들을 read-only상태로 바꿔줍니다.
read는 동시에 여러개가 가능하지만 write는 한번에 하나만 가능하기 때문에 write를 하려면 허가를 받고
해야 합니다.
data가 실제로 공유된 page에 write될 때(process1에서 page C에 data를 write한다고 가정)
protection fault가 발생합니다. (read-only page에 write할려고 시도하기 때문)
따라서 OS는 새로운 물리 메모리를 할당(Copy of page C)하고, mapping table을 바꿔줍니다.
즉, 공유한 page는 내용이 바뀌지 않을 때까지 공유하게 되고, 내용이 불가피하게 바뀌게 되면 새로운
page를 할당해서 그때 복사를 해줍니다. (복사 overhead 감소)
Demand Paging
demand paging은 CPU가 요청할 때 process의 데이터를 메모리에 올리는 것을 의미합니다.
즉, 처음부터 모든 데이터를 메모리에 load하지 않습니다.
demand paging은 paging 시스템중 swapping과 유사합니다.
결과적으로 prcess가 실행되는 동안 일부 page는 메모리에 있고, 일부는 secondary storage(보조 기억장치)
에 있습니다. 따라서 이 둘을 구별하기 위해 어떤 형태로든 하드웨어 자원이 필요합니다.
Valid-Invalid Bit
각 page table 항목에 valid-invalid bit이 할당되어 있다.
해당 bit가 valid 하다고 설정되면 해당 page가 메모리에 있다는 의미((1) in-memory) 이고,
해당 bit가 invalid 하다고 설정되면 해당 page가 유효하지 않거나 유효하지만 보조기억장치에 존재한다는 것을
의미합니다.((0) not-in-memory)
처음에는 모두 0(not -in -memory)으로 초기화 합니다.
만약 valid-invalid bit가 1이라면, page에 접근할 수 있습니다.
반면에 valid-invalid bit가 0인데도 page에 접근하려면 page fault가 발생합니다.
page fault는 어떤 프로그램이 자신의 주소 공간(가상 메모리 공간)에는 존재하지만 시스템의 메모리에는
현재 존재하지 않는 데이터 또는 코드에 접근을 시도할 경우 발생하는 현상입니다.
Page Fault Steps
1. process에 대한 내부 테이블을 검사해
그 메모리가 valid / invalid 한지 알아냄
2. 만약 invalid 라면 OS에 trap을 요청 (page fault)
만약 valid인데 page가 아직 메모리에
올라오지 않았다면, 보조기억장치로부터
가져와야 함.
3. OS는 내부 테이블 (PCB와 함께 보관)을
검사하여 invalid 참조인지, 메모리에 없는 것인지
체크
그 다음 물리 메모리의 빈공간, 즉 free frame을 찾는다.
4. 새로 할당된 frame으로 해당 page를 읽어 들이도록
보조기억장치로부터 요청한다.
5. 보조기억장치로부터 읽기가 끝나면 이 page가 이제는 물리 메모리에 있다는 것을 알리기 위해
page 테이블을 갱신하며, proces가 유지하고 있는 내부 테이블을 수정한다. (valid bit으로 바꿈)
6. trap에 의해 중단되었던 명령어를 다시 수행한다.
이제 process는 마치 그 page가 항상 메모리에 있었던 것처럼 해당 page에 접근할 수 있다.
Problem in Restart Instruction
block move란 source block과 destination block이 겹치면 source block이 수정될 수 있습니다.
즉, 명령을 다시 시작할 수 없습니다.
이를 방지하기 위해
덮어쓴 위치의 내용을 일시적으로 보관하고 복원하는 방법 등이 있습니다.
Performance of Demanding Paging
demand paging의 성능은 컴퓨터 성능에 큰 영향을 끼칩니다.
page fault의 확률을 p라고 하면 (0 ≤ p ≤ 1.0)
p = 0이라면 page fault가 전혀 발생하지 않는다는 의미이고
p = 1이라면 모든 참조 마다 page fault가 발생한다는 의미입니다.
Effective access time(실질 접근 시간 =EAT)
EAT = (1-p) * memory access time + p * page fault time
page fault time은 다음과 같은 요소로 이루어져 있습니다.
- page fault overhead
- Time for swapping page in
- Time for swapping page out
- Restart overhead (page fault이후 memory access time 포함)
EAT는 page fault rate에 비례합니다.
따라서 page fault rate를 낮추는 것이 성능을 높이는 데 중요합니다.
ex)
memory access tiem = 200 nanosec
average page fault service time = 8 msec
EAT = (1-p) * 200 + p * 8 msec
= (1-p) * 200 + p * 8000000
= 200 + p * 7999800
1000개 중 하나의 접근으로 인해 page fault가 발생하면 EAT = 8.2 usec이 됩니다.
이는 40배 속도 저하입니다.
Need for Page Replacement
다음과 같이 free frame이 없으면 page replacemet가 필요합니다.
Page Replacement
page replacement는 page fault를 최소화하기 위한 방법입니다.
물리 메모리보다 더 큰 메모리 공간을 필요로하는 process가 실행 시 page 부재를 최소화하면서
효율적으로 실행하기 위한 메커니즘입니다.
page replacement는 다음과 같이 행해집니다.
1) 보조 기억 장치(secondary storage)에서 원하는 page의 위치를 찾는다.
2) free frame를 찾는다.
(1) 만약 그곳에 free frame이 있으면 그것을 사용한다.
(2) 그렇지 않다면, page replacement 알고리즘을 사용해 victim frame을 선택한다.
(3) victim frame을 보조 기억 장치에 write 한다(필요한 경우)
3) 원하는 page를 free frame으로 read하고, page및 frame table을 업데이트 한다.
free frame이 없는 경우에 disk를 두번 접근해야 하므로 page fault service time이 2배 소요됩니다.
이러한 overhead는 modify bit 또는 dirty bit를 사용해 감소시킬 수 있습니다.
demand paging은 아래 두 가지 중요한 문제를 해결해야 한다.
1) frame allocation algorithm
2) page replacement algorithm
즉, 각 process에게 얼마나 많은 frame을 할당해야 하는지와 어떤 page를 교체해야하는 지를 결정해야합니다.
(같은 page가 여러번 메모리에 저장될 수 있습니다.)
Page Replacement Algorithms
즉, page fault 비율이 낮게 하는것을 원합니다.
특정 메모리 참조 문자열(referenct strig)에서 알고리즘을 실행하고, 해당 문자열의 page fault수를
계산하여 알고리즘을 평가합니다.
CPU가 내는 주소는 이진수 단위이지만, page replacement 알고리즘을 계산하기 위해서는 이진수 주소 단위가 아닌
page 단위로 계산해야 합니다.
예를 들어 CPU가 내는 주소를 10진수로 표현하여 {100, 101, 102, 432, 612, 103, 104, 611, 612}라고 하고
page당 크기가 100byte라면, 위 주소를 page number로 나타내면 {1, 1, 1, 4, 6, 1, 1, 6, 6} 입니다.
주소 100번지는 1번 page에서 offset이 0인 위치이고, 101은 1번 page의 offset 1인 위치입니다.
이를 reference string으로 나타내면 {1, 4, 6, 1, 6} 입니다. 연속된 page는 생략하고 하나의 page 번호만 나타낸것입니다.
연속된 page를 참조할 떄는 한번 page fault가 발생하면 같은 page를 사용하는 동안에는 절대 page fault가
발생할 수 없기 때문입니다.
예를 들어 특정 process를 추적하는 경우
위의 address를 기록할 수 있습니다.
page 당 100 byte크기라면 reference string은 1, 4, 1, 6, 1, 6, 1, 6, 1, 6, 1 입니다.
첫번 째 참조 후 즉시 참조해도 fault가 발생하지 않습니다.
Page Faults vs Number of Frames
일반적으로 frame 수가 증가함에 따라 page fault 수는 감소하고, 최소 수준으로 감소합니다.
First-In-First-Out (FIFO) Algorithm
FIFO 알고리즘은 가장 먼저 page-in 한 page를 먼저 page-out 시킵니다.
즉, 큐와 똑같다고 보면 됩니다.
ex)
위 예제를 보면 총 frame의 개수는 3개입니다.
총 15번의 page-fault가 발생하였습니다.
이러한 FIFO 알고리즘은 Belady's Anomaly가 발생할 수 있습니다.
belady's anomaly는 frame수가 증가하는데도 불구하고 page fault가 증가하는 것입니다.
예를 들어 reference string이 {1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5}이고 4개의 frame을 사용하면
3개의 frame을 사용한 것보다 page fault가 더 늘어납니다.
따라서 FIFO 알고리즘은 그렇게 효율적인 알고리즘은 아닙니다.
Optimal (OPT or MIN) Algorithm
가장 오랫동안 사용되지 않을 page를 대체합니다.
가장 낮은 page fault 비율과 belady's anomaly가 절대 일어나지 않습니다.
여기서 가장 오랫도안 사용되지 않을 page를 계산하기 위해 현재 시점에서 그 이후에 최초로 나타나는
시점의 거리를 dist로 계산해 둡니다.
dist 값이 가장 큰 page가 가장 오랫동안 사용되지않은 page로 정합니다.
OPT는 좋지만 구현하기 어렵습니다. (미래를 알아야 하기 때문)
알고리즘이 얼마나 잘 수행되는지 측정하는데 사용됩니다.
Least Recently Used (LRU) Algorithm
마지막으로 사용된 시간이 가장 오래된 page를 대체하는 방식입니다.
최근에 사용되지 않으면 나중에도 사용되지 않을 것이라는 개념으로 과거의 page기록을 통해 진행합니다.
LRU 알고리즘을 구현하기 위해서는 하드웨어의 자원이 필요하며, 아래 두가지 방법이 가능합니다.
1) Counter
모드 page table에는 counter가 있습니다.
이 항목을 통해 page를 참조할 때 마다 (logical) clock를 counter에 복사합니다.
clock은 모든 메모리 참조에 대해 증가합니다.
page를 변경해야 할 경우, 변경할 page를 결정하기 위해 counter를 확인합니다.
2) Stack
page number를 doubly linked list형식의 stack으로 보관합니다.
page가 참조될 때마다 맨 위로 이동합니다.
stack의 맨위는 항상 가장 최근에 사용된 page이고, 맨 아래는 LRU page입니다.
stack의 각각의 업데이트는 overhead가 많이 들고 대체할 것을 검색할 수 없습니다.
LRU Approximation Algorithms
LRU 알고리즘을 충분히 지원할 수 있는 하드웨어는 거의 없습니다.
그러나, 많은 시스템들이 reference bit(참조 bit)의 형태로 어느 정도 지원을 하려고 합니다.
(1) Reference Bit Alogithm
각 page를 처음에 reference bit를 0으로 초기화 해줍니다.
page가 참조되었을 때, reference bit를 1로 셋팅해줍니다.(by hardware)
존재하는 경우 0인것을 교체합니다.
우리는 순서는 모르지만 어떤 page가 사용되었고, 어떤 page가 사용되지 않았는지는 알 수 있습니다.
(2) Additional Reference Bits Algorithm (Reference Byte Algorithm)
일정한 간격마다 reference bit를 기록함으로써 추가적인 선후 관계 정보를 알 수 있습니다.
각 page table 마다 8bit byte를 메모리에 보관합니다.
일정한 간격을 기준 bit를 8bit byte의 상위 bit로 이동하고 다른 bit를 오른쪽 1bit로 이동하여
하위 bit를 삭제합니다.
이 8bit shift 레지스터에는 최근 8번 동안의 page 사용 내역이 포함되어 있습니다.
page fault가 발생할 때, 우리는 이 8 bit를 부호 없는 정수로 해석하고 가장 낮은 숫자의 page는
LRU page이며 교체할 수 있습니다.
(3) Second Chance (Clock) Algorithm
FIFO 알고리즘과 유사합니다.
page가 선택될 때 마다 reference bit를 확인합니다.
메모리에 load된 page들을 circular list형태로
저장합니다.
page가 처음에 load될 때 reference bit는 1로
설정됩니다.
page fault handler에서 page를 교체할 때,
우리는 page의 reference bit를 검사합니다.
bit값이 0이면 교체해줍니다.
bit값이 1이면
referene bit를 0으로 설정합니다.
(다시 참조되는지 확인할 수 있는 두번째 기회를 부여)
따라서 page를 아직 memory에 남깁니다.
그 다음 page를 clock order에 따라 검사합니다.
ex)
reference string : a, b, g, a, d, e, a, b, a, d, e, g, d, e
frame의 개수는 4개
page fault는 총 10번 발생했습니다.
Allocation of Frames
여러 개의 process들에 대해 제한된 메모리를 어떻게 할당할 것인가에 대한 문제입니다.
전체 frame개수 보다 더 많은 수는 할당할 수 없습니다.
최소 frame 개수 만큼은 할당해야 합니다.
각 process는 최소 개수의 page(minimum number of pages)들이 필요합니다.
각 process에 할당되는 frame의 개수가 줄어들면 page fault 율이 증가하고 process 실행은 늦어집니다.
또한 명령어 수행이 완료되기 전에 page fault가 발생하면 그 명령어를 처음부터 재실행 해야 합니다.
따라서 하나의 명령어가 참조하는 모든 page는 동시에 메모리에 올라와 있어야 그 명령어의 수행이
끝날 수 있게 됩니다.
예를 들어, 모든 메모리 참조 명령어가 단 하나의 주소만을 갖는 기계가 있다면, 명령어에 의한 메모리 접근을 위해
최소한 하나의 frame이 필요합니다.
여기에 간접 주소 지정이 가능한 경우 16번 frame에 있는 load 명령이 23번 frame에 대한 간접 참조를
담고 있는 0번 frame에 있는 주소를 가리키는 것입니다.
따라서 총 process당 3개의 frame이 필요합니다.
Frame Allocation Algorithm
Equal allocation (균등 할당)
만약 93개의 free frame이 존재하고, 5개의 process가 있으면 각 process는 18개의 frame을 받고
나머지는 free frame buffer 저장소로 사용됩니다.
Proportional allocation (비례 할당)
어떤 이가 만든 10KB의 작은 process와 127KB의 데이터베이스 프로그램이 64개의 free frame(frame당 1KB)
을 갖는 시스템에서 수행되고 있다면, 각각 32 frame씩 할당받는 것은 좋은 방법이 아닙니다.
10KB process는 10개의 frame만 사용할 것이므로 22frame이 낭비되기 때문입니다.
대신에 process의 크기 비율에 맞추어 frame을 할당하는 비례 할당 을 사용할 수 있습니다.
ai는 각 process당 할당 받게 되는 frame의 개수입니다.
따라서 10KB process는 5개의 frame을 할당 받고
127KB process는 59개의 frame을 할당받습니다.
균등할당과 비례할당 모두 process의 우선순위를 고려하지는 않습니다.
우선 순위가 낮은 process에 손실을 주더라도 우선순위가 높은 process에 더 많은 frame을 할당하여
빨리 수행해야 할 수도 있습니다.
이때, 비례 할당 방법을 사용하면서 frame 비율을 process의 크기가 아닌 우선순위를 사용하여 또는
크기와 우선순위의 조합을 사용하여 결정할 수 있습니다.
Global vs Local Replacement
다수의 process가 frame 할당을 위해 경쟁하는 환경에서 page 교체 알고리즘은 크게 두가지 범주로
나눌 수 있습니다.
(1) Global replacement
process가 교체할 frame을 다른 process에 속한 frame을 포함한 모든 frame을 대상을 찾는 경우입니다.
global replacement는 process의 메모리에 있는 page 집합이 현재 process의 paging 동작뿐만 아니라
다른 process의 paging동작에도 영향을 받습니다.
따라서 동일한 process도 외부환경에 영향을 받아서 실행시간이 매번 달라질 수 있습니다.
(2) Local replacement
각 process가 자신에게 할당된 frame중에서만 교체될 frame을 선택하는 경우입니다.
유일하게 그 process의 paging 동작에만 영향을 받기 때문에 실행시간에 대한 영향은 받지 않습니다.
그러나 잘 안쓰이는 page frame이 있다면 그것을 그대로 낭비할 수 있다는 단점이 있습니다.
일반적으로 global replacement가 local replacement보다 더 좋은 시스템 성능을 보입니다.
Thrashing
OS는 cpu utilization을 검사하는데, 만약 cpu 이용률이 너무 낮아지면 새로운 process를 시스템에 추가해
multiprogramming의 정도(degree)를 높입니다.
현재 사용중인 process가 너무 많아서 working set의 page를 지원하는 데 필요한 최소 frame도 없는 경우,
현재 process는 바로 page fault를 일으킵니다.
이 때 page replacement가 발생하지만 이미 활발하게 사용되는 page들 만으로 이루어져있으므로
어떤 page가 교체되든 바로 다시 필요해질 것입니다.
결과적으로 page fault는 계속해서 발생하고 교체된 page는 얼마 지나지 않아 다시 읽어올 필요가 생기게 됩니다.
이에 따라 process들이 page를 기다리는 동안 cpu utilization은 계속해서 떨어집니다.
cpu utilization은 떨어지니까 os는 계속해서 multiprogramming degree를 높히려 할것입니다.
이러한 과도한 paging 작업을 thrashing 이라고 합니다.
어떤 process가 실제 실행보다 더 많은 시간을 paging에 사용하고 있으면 thrashing이 발생했다고 합니다.
그래서 실제 동작을 전혀 못하는 상태일때 강제로 process를 삭제하게 됩니다.
결국 cpu가 다운됩니다.
thrashing의 영향을 제한하기 위해서는 local replacement 또는 priority replacement를 사용해
thrashing의 영향을 제한해야 합니다.
따라서 각 process가 필요로 하는 최소한의 frame 개수를 알 수 있다면 thrashing 현상을 방지할 수 있을것입니다.
이는 process 실행의 locality model을 기반으로 알 수 있습니다.
Locality of a Process Execution
Locality model이란 process가 실행될 때는 항상 어떤 특정한 지역(page 집합)에서만 메모리를
집중적으로 참조하는 것을 의미합니다.
위의 그림과 같이 process가 실행 중에, 특정 시간에는 일정 범위의 page를 주로 참조하는것을
알 수 있습니다. 이를 통해 특정 시간에 따라 사용하는 page의 개수 만큼 frame을 할당해줄 수 있습니다.
이 방법 역시 단점이 존재하는데, 바로 process를 미리 수행해 봐야 할 수 있는 것이고, process를 수행할 떄마다
사용하는 기능의 달라질 수 있으므로, Locality를 이용하는 방법은 비현실적입니다.
이를 해결하는것이 Working Set입니다.
Working Set Model
working set model은 한 process가 최근 delta개의 page를 참조했다면, 그 안에 들어있는 서로 다른 page들의
집합을 의미합니다.
너비가 delta인 창(window)를 움직인다고 생각할 수 있습니다.
만약 delta = 10이라면, 시간 t1에서의 working set은 {1, 2, 5, 6, 7} 이 되고,
시간 t2에서는 {3, 4}가 됩니다.
working set의 정확도는 delta의 선택에 따라 죄우됩니다.
만약 delta 값이 너무 작으면 전체 locality를 포함하지 못할 것이고, delta값이 너무 크면 여러 locality를 과도하게
수용할것 입니다.
delta값이 ∞가 되면 전체 프로그램을 포함할 것입니다.
일단 delta 값을 적절하게 설정하면 OS는 각 process에게 working set 크기(delta)에 맞는 충분한
frame을 할당합니다.
그러다 실행중인 process의 수가 너무 많아져 delta가 전체 메모리의 크기 보다 커지게 되면, process를 하나 선택해
그 process의 page들을 빼앗고, 연기시키고, frame들을 다른 process에 주게 됩니다.
연기된 process는 나중에 재개될 수 있습니다.
Page-Fault Frequency Scheme
page-fault율의 고점은 새로운 locality로 들어가 demand paging이 시작되는 경우에 발생합니다.
일단 새로운 지역의 working set이 메모리에 올라오고 나면 page-fault율은 낮아집니다.
새로운 working set으로 이동할 때 다시 page-fault 율이 높아지게 됩니다.
thrashing을 간단히 말하면 page-fault 율이 너무 높은 경우를 의미합니다.
그렇다고 page-fault 율이 너무 낮은 경우 process가 너무 많은 frame을 할당받았다는 것을
의미할 수도 있습니다.
따라서 page-fault율의 상한과 하한을 정해놓고, 만약 page-fault율이 상한을 넘으면 그 process에
frame을 더 할당해 주고, 하한보다 낮아지면 그 process의 frame수를 줄입니다.
Memory-Mapped Files
open(), write() , read()등 systemcall을 사용해 disk에 있는 file을 순차적으로 읽는다고 가정해봅니다.
이러한 방식을 사용하면 file이 매번 access될 때 마다, system call을 해야 합니다.
이와 같이 하는 대신에 disk I/O을 메모리 참조 방식으로 대신할 수 있습니다.
이러한 방식을 memory-mapping 이라고 합니다.
Other Considerations (기타 고려 사항)
Pre-paging
과도한 page-fault를 방지하기 위한 기법입니다.
필요한 모든 page를 한번에 메모리로 가져와 초기 paging의 높은 비용을 방지합니다.
사전에 paging을 사용하는 비용이 해당 page-fault를 처리하는 비용보다 적는지 고려해야합니다.
pre-paging을 하는 이유는 미리 올라온 page들이 실제로 곧 사용될 것이라고 예상하기 때문입니다.
Page size selection
fragmentation - page 크기가 크면 internal fragmentation 증가시킵니다.
Table size - page의 크기가 작으면 page table의 크기가 커집니다.(table의 크기가 작아야 좋음)
I/O overhead - page크기가 클수록 탐색 횟수가 줄어들기 때문에 I/O시간을 최소화할 수있습니다.
Locality - page 크기가 작으면 더 좋은 locality를 차지합니다.
TLB Reach
TLB에서 access할 수 있는 메모리의 양입니다.
TLB Reach = TLB size * page size
각 process의 working set이 TLB에 저장되는 것이 이상적입니다.
프로그램의 구조가 성능에 영향을 미칠 수 있습니다.
program 2가 훨씬더 좋은 성능을 냅니다.
'운영체제(OS)' 카테고리의 다른 글
File System (0) 2022.12.15 Chapter 9 (Main Memory) (0) 2022.11.21 Chapter 5 (CPU Scheduling) (0) 2022.10.26 Chapter 6 - 7 (Synchronization Tools and Examples) (0) 2022.10.22 Chapter 4 (Thread and Concurrency) (0) 2022.10.19