💁🏻♀️ Summary
☑ MMU in CPU
☑ Page Table in Physical Memory
☑ TLB(cache) in MMU
개발자가 메모리의 이슈들을 신경쓰지 않아도 편하게 개발할 수 있도록 추상화 하는 것
→ To provide a convenient abstraction for programming
메모리는 비싼 자원이기 때문에 아껴 쓰고 효율적으로 사용해야 한다.
→ To allocate scarce memory resources among competing processes to maximize performance with minimal overhead
프로세스간의 양쪽이 사용하는 메모리 영역을 완전히 격리 시킨다 (메모리 프로텍션)
→ To provide isolation between processes
한 번의 하나의 프로그램만 동작을 하니 메모리 관리가 필요하지 않았다. 그냥 프로그램이 운영체제에서 추상화 필요없이 피지컬 메모리를 그냥 써도 상관이 없었다.
다수의 사용자가 컴퓨터를 동작, 여러 프로세스가 동작 하는 상황… → 메모리 관리가 필요해짐
✔ Requirements
Support for multiple processe
Enable a process to be larger than the amount of memory allocated to it
실제 피지컬 메모리보다 더 큰 용량을 가지는 프로세스도 지원을 해주어야 한다 (virtual memry로 해결)
Protection
Sharing
Support for multiple regions per process (segments)
Performance
✅ 메모리 자체를 virtual(로지컬) 메모리랑 피지컬한 메모리로 나누어서 관리를 한다는 것이 핵심
✅ 데이터를 메모리에 저장하기 위해서 메모리 공간을 확보하면, 확보된 메모리 공간에 주소를 알 수 있어야 하는데 그 주소를 명시하는 타임에 따라 세가지로 나뉜다.
✅ Compile time
→ 컴파일, 로드, 실행할 때 모두 overhead가 없지만 실행주고가 정적이라는 단점.
✅ load time
✅ execution time
⇒ cpu가 더하기 연산을 하면 overhead가 크니 hw 지원을 받아 성능의 한계를 극복한다.
→ 실제로 사용하는 방식이며 , 하드웨어 로직으로 mmu에 넣어서 위의 문제를 해결
✅ Address mapping
→ 메모리가 모자른 경우에 세컨더리 스토리지에 넣었다 뺐다 하면서 진행
→ 세컨더리 스토리지의 일부를 메모리처럼 활용하여 피지컬 메모리보다 더 큰 메모리를 가상으로 가지고 컴퓨터가 동작하도록
✅ Logical Memory, Physical Memory 주소간의 매핑을 주도적으로 담당하는 하드웨어 로직을 MMU라고 한다. MMU는 CPU 안에 있다.
Physical address = logical address + relocation register
→ MMU가 다 처리를 해준다.Hole
이 생김 → fragmentation
⇒ 짜투리 공간 생김 (cpu utiliaztion 떨어짐)
First-fit
무지성으로 처음 만나는 들어갈 수 있는 곳에 넣기
Best-fit
전체 홀 중에서 올려야 하는 메모리의 크기와 가장 딱 맞는것에 가까운 것에 집어 넣자
Worst-fit
제일 큰 홀에 넣기
→ 제일 큰 홀이 무엇인지 알아내기 위해서 모든 홀을 다 조사 해야한다. overhead가 아주 크다. 그럴바에는 처음부터 끝까지 다 조사하고 비교하지 말고 그냥 처음 만나 곳에다가 넣자! 해서 나온 관점이 First-Fit
이다.
⇒ 이 세가지를 시뮬레이션으로 검증을 해보았더니 first와 best가 worst보다 조금 더 좋다. 지금은 메모리가 많이 싸졌지만 예전에는 크기도 작고 엄청 비쌌다. 메모리 메니지먼트를 할 때 여러 사용자가 프로세스를 생성했다가 없앴다가 생성했다가 없앴다가를 반복하면 단편화가 심해진다. fragmentation 영역들이 못쓰는 영역이 될 수도 있다. utilization이 떨어진다.
→ Internal Fragmentation, External Fragmentation
→ External Fragmentation
(contiguos allocation에서 발생)이 문제다
compaction이라는 홀을 합쳐주는 해결방안이 있기는 하다. 그런데 i/o가 두번 발생하면서 매우 느리고 오버헤드가 매우 크다
💁🏻♀️ 결론 Contiguous Allocation
이론적으로 가능 - 가장 단순하게 로지컬 주소와 피지컬 주소를 트랜지션할 수 있는 방법 이다. 동작하는 로직의 특성상Hole
이 생기게 되고 그 홀이 점차 단편화가 이루어지고fragmentation
때문에 메모리가 낭비되는 일이 많아진다. 메모리가 낭비되는 것은 큰 문제인데 이것을 해결하기 위한 방법으로compaction
이라는 홀을 재구성하는 방법이 존재한다. 그러나 이것을 수행시키기 위해서는 세컨더리 스토리지에다 파편화된 것들 사이에 있는 프로세스를 전부 뺐다가 다시 넣어서 재구성을 해야해서 i/o가 쓰는 것 한 번 읽는 것 한 번 두번이나 해야함으로 느려진다. 그래서 이론적으로 가능하지만 사용하진 않는다.
Internel Fragmentation
✅ Translating addresses
✅ Page Tables
: 비어 있는 Frame
안 쓰는 것을 swap out 필요한 것을 swap in → Virtual memory
⇒ 이를 해결하기 위해서..
Cache the virtual-to-physical translation in hardware
짧은 시간안에 근처에 액세스할 확률이 매우 높음
Translation Look-aside Buffer (TLB)
: 캐시
: Page Table을 Caching하고 있는 것, MMU 안에 특별한 캐시 메모리를 버퍼로 사용하여 페이지 테이즐의 일부를 캐싱할 수 이도록.
: 로켈리티를 활용한 캐싱의 장점을 가져다 쓸 수 있음
→ MMU만 사용하는 캐시
= CAM (Content Addressable Memory)
- 특정 페이지에 접근을 하고 싶을 때 MMU에게 페이지 넘버를 물으면 캐싱 되고 있는 테이블이 존재한다면 그에 해당하는 Frame 넘버가 튀어나오는데 그 frame 넘버 가지고 offset 값을 적용해서 메모리에 접근하며 된다. (cache hit)
- 그런데 만약 페이지 5번에 접근하고 싶다고 요청해서 MMU가 TLB에 5번을 집어 넣었는데 대응되는 것이 없어 아무것도 뱉어내지 못할 때는 메모리에 있는 페이지 테이블에 5번에 다시 참조해서 가지고 돌아오고 한 번 참조된 것은 다시 쓰일 수 있으니 버퍼에 업데이트를 해준다.
💁🏻♀️ 페이지 테이블의 사이즈가 크다. 페이지 테이블은 프로세스마다 독립적으로 가지고 있기 때문에 프로세스 개수만큼 용량도 n배로 늘어난다. 이것들을 전부 mmu에 둘 수 없으니 메모리로 내려야 하는데 메모리에 있으면 2번 접근해야하는 오버헤드가 발생을 한다. mmu에 TLB라고 하는 특이한 캐싱을 할 수 있는 버퍼를 둔다. TLB는 content addressavle memory로 구성이 되어 있기 때문에 값을 던져주면 값을 바로 반환하는 성격을 가지고 있어서 페이지 테이블을 구축하기에 아주 적합한 메모리다. MMU 에 TLB를 집어 넣고 자주 사용되는 페이지들만 캐싱을 해서 사용하게 되면 cache hit이 발생하여 성능이 좋아지게 된다. 실제로 돌려 보았더니 cache hitting rate이 99%가 넘는다.
✅ Handling TLB misses
✅ Managing TLBs
페이지 번호가 0부터 시작해서 최대치를 초과하는지 아닌디를 체크하기
valid-invalid bit → 메모리가 가용한 상태(Frame할당을 받음)이면 valid 가용하지 않은 상태(Frame에 할당이 되어 있지 않음)이면 invalid
페이지 테이블 사이즈가 프로세스마다 4MB이면 좀 과하자나..
Paging 에서의 근본적인 문제
→ Page Table 이 너무 크다 —> 그럼 작게 하면 되겠네?
📌 각각의 기법들이 페이지 테이블의 크기를 줄일 수 있는지, 왜 페이지 테이블이 줄어드는지에 초점을 맞추어 살펴보자
→ 실제로 사용되는 방식
페이지 넘버에 해당되는 20bit를 통으로 다 쓰는 것이 아니라 반으로 쪼개서 사용
해쉬 알고리즘을 사용해서 테이블의 사이즈를 줄이자
특정 프로세스가 할당받아 사용하는 페이지 테이블은 독립되어 있는데 페이지 테이블을 두는 것이 아닌 프레임 테이블을 둔다.
실제 프로세스가 동작할 때 논리주소는 다 쓰는 경우가 드물어서 페이지 테이블 자체가 낭비이다. 이 문제를 해결 → 피지컬 메모리 관점에서 메모리의 특정 공간은 한 순간에 하나의 프로세스에게만 할당이 된다. 물리주소를 논리주소로 바꾸어서 테이블을 만든다. 로직컬 메모리에 대한 페이지 테이블을 만드는 것이 아니라 frame table을 만든 것이라 생각하면 된다.
→ 프로세스마다 독립적으로 가지는 page table을 만들지 않아도 된다.
→ 효율이 떨어진다. 피지컬 메모리 계속 늘어나..?
페이지 테이블을 유지함으로써 데이터를 공유할 수 있고 이것을 활용해서 피지컬 메모리를 아껴 쓸 수 있다.
EX) 처음 한 번만 실행된 프로세스를 할당을 하고 이후 실행되는 프로세스들은 이미 할당받아진 것들을 공유해서 사용한다. Code Segment는 write가 불가는하고 read와 execution만 가능하다. 그렇기 때문에 가능하다
✔ page
✅ 장점
✅ 단점
페이지로 나눈 부분에 cs랑 const부분이랑 혼재되어 들어갈 수도 있다.
→ protection하기 어렵다
특정 Segment에서 어느정도 오프셋을 적용해서 데이터를 읽어줘 라고 명령이 떨어지면 MMU가 그 명령을 받아와서 Segment 테이블에서 s번째 있는 Segment를 찾고 거기에 대응되어 있는 베이스 주소를 주고 혹시나 오프셋이 리밋을 넘어서 이상한 곳을 접근하는 것은 아닌지 검사하여 memort protection. 문제가 없으면 베이스 주소만큼 더하기 해서 오프셋 적용을 해서 피지컬 메모리 읽기
✅ 장점
✅ 단점
사이가 다 다르다 → External fragmentation
⇒ 실제로 쓰는건 Paging과 Segmentation을 섞어서 사용한다.