본문 바로가기
공부/OS

[운영체제] 가상 메모리 2 (paging)

by algosketch 2021. 5. 2.

페이징을 시작하기 앞서, 프로젝트2 하기 전에 정리한 내용(Segmentation)들...

일단 교수님이 IA-32 인텔 아키텍쳐 매뉴얼을 보면 도움이 된다고 하셨는데,... pdos.csail.mit.edu/6.828/2005/readings/ia32/IA32-3.pdf 그 내용이 여기에 있다. 영어로 800페이지 있는 거 보고 깔끔하게 무시했다.

프로젝트2 요구사항은 geekOS 에서 segmentation 을 이용하여 userseg.c 파일을 완성하는 것이다. userseg.c 의 Create_User_Context(), Load_User_Program() 함수를 완성하면 되었다. C언어가 이렇게까지 어려워질 수 있구나라는 걸 프로젝트2 하면서 느꼈다.

LDT : Local Descriptor Table - 프로세스마다 하나씩 갖고 있다. 세그먼트의 주소가 담겨 있다.
GDT : Global Descriptor Table - 커널이 하나 갖고 있다. LDT 의 주소가 담겨 있다.
Descriptor : 주소
Selector : index, 가상 주소
Linear Address : segmentation 을 통해서 변환된 주소. segmentation 에서는 physical 주소와 같다. paging 에서는 이 주소를 한 번 더 변환해야 한다.
GDTR : GDT 주소를 가리키는 레지스터
LDTR : LDT Selector - GDT 에서 찾아가야할 index, GDTR 은 주소이지만 LDTR 은 주소가 아님에 유의해야 한다.
GDTR, LDTR 에 정확한 값을 넣어주면 MMU 가 알아서 주소를 변환해 준다.

argument 는 사실 스택 다음에 둔다.

정리하다가 보게된 건데, 교수님이 시스템콜에 대해서 설명을 해주셨구나... 나는 코드를 타고 타고 들어가다가 뭐 이런 괴랄한 코드가 다 있나 했다. 갑자기 어셈블리는 왜 나오는데??

페이징

segmentation 에서 생기는 외부 단편화 문제를 해결할 수 있다. 내부 단편화가 조금 있지만 페이지 크기를 작게하면 내부 단편화도 그만큼 작아진다.

frame : page (physical memory)
page : (virtual memory)
둘의 크기는 같고 서로 매핑된다.

32비트, 4기가 메모리에서, 1page = 4kb 라면 1m 개의 page 가 생긴다. page 주소 하나에 4byte 씩 사용하면 프로세스 하나가 생길 때마다 최소 4mb 의 메모리가 필요하다. → 메모리 낭비

valid : 포인터 연산시 이 값을 확인하여 잘못된 접근이라면 segmentation fault 발생 (새 page 할당할 때 확인하는 듯)
page table 은 커널만 접근할 수 있는 메모리에 저장한다.
CR3 : 현재 수행중인 프로세스 페이지를 가리키는 레지스터

페이징 사용시 fork 를 가볍게 할 수 있다.
copy-on-write : 메모리를 복사하지 않고 사용하다가(일단 Read Only) 쓰기 연산이 나오면 그때 복사한다. 이때 쓰려고 하는 프로세스가 쓰려고 시도했던 메모리의 소유권을 갖는다. (다른 프로세스는 복사한 곳을 소유)

위 내용만으로 구현시 페이징의 문제점
속도가 느리다.
메모리를 많이 차지한다.

CPU 는 빠르고 메모리는 느리다. 어셈블리 코드를 실행하는데 메모리에 많이 접근하게 된다면 그만큼 성능 저하가 일어난다. 페이징을 사용하면 페이지 테이블에 접근할 때, instruction 에 접근할 때 각각 메모리에 접근해야 하므로 속도가 느려진다.

글 쓰다가 내용이 기억 안 나서 다음에 마저 쓰기로 함... 5월 16일 오전 0시 다시 쓰기 시작

페이지 테이블 속도 문제점을 해결하기 위해서 캐싱을 한다. 이 캐싱은 TLB 를 이용하는 것이다.
TLB : Translation Lookaside Buffer → CPU 와 비슷한 속도, PTE 를 저장하는 캐시
이 내용을 쓰다가 갑자기 의문이 들었던 것은 페이지 테이블의 시작점만 저장한다고 하면 테이블 각각의 인덱스는 MMU 가 계산해주는 것인가? 아니면 각 요소에 대해서도 캐싱이 되는 것인가? 용량 문제도 있어서 후자의 가능성은 없고 아마 전자일 거라고 생각했고, 의문 해소를 위해 MMU 가 수행하는 코드를 확인하였다.
PTEAddr : PTBR + (VPN * sizeof(PTE)) PTE 는 page 의 주소, sizeof(PTE)는 주소 하나의 크기(page size 가 아님), 따라서 전체 식은 (가상)페이지 테이블의 어떤 하나의 인덱스 주소이다. 이 값을 통해 페이지(frame)를 구해야 하고 구한 값을 shift 해서 offset 을 더하여 실제 주소를 구한다. physAddr = (PTE.PFN << PFN_SHIFT) | offset 이 코드에서 PFN 을 쉬프트하는 게 물리 주소로 바꾸는 부분인데, 이 부분을 인지하는 게 오래 걸렸다. PFN_SHIFT 가 결국 page size 가 된다.
MMU는 VPN 을 확인했을 때 유효한 주소인지 확인하고 TLB hit 와 miss 를 판별한다. hit 날 경우 TLB 에서 PFN 을 꺼내 바로 shift 해주고 offset 을 더해 실제 주소를 구한다. 결론은 TLB 는 PFN 을 저장하는 것이다. 이는 지역성을 이용한 것이다.
공간적 지역성 : 어떤 메모리에 접근하게 되면 그 근처를 접근할 가능성이 높다.
시간적 지역성 : 어떤 주소에 접근했다면 가까운 시간 내에 다시 접근할 가능성이 높다.
TLB 에 값을 넣어줄 때 TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits) 이런 인자를 넣어준다. TLB 는 VPN 과 PFN 을 쌍으로 갖고 있기 때문에 두 인자를 넣는 것을 알 수 있다. pretectBits 는 permission bit 인 것 같다. (read, write, execute)

TLB 는 초기 상태를 cold 로 만들어 준다. (cold : TLB miss 가 나도록 설정)
컨텍스트 스위칭 후 접근할 때 TLB 가 ASID 와 동일한 지 비교한다.
ASID : Address Space IDentifier - process ID 로 프로세스마다 고유하게 갖고 있는 ID 값이다. 이 값으로 TLB Hit 여부를 판단한다. PID 와 다른 점은 ASID 를 통해 메모리를 공유할 수 있다.

Replacement
TLB 는 여러 개의 페이지를 저장하고 있다. TLB 는 cache 이기 때문에 사이즈가 굉장히 작은데, cache 가 꽉 찼다면 기존에 저장되어 있는 페이지를 대체해야 한다. FIFO 나 Random 하게 뺄 수도 있지만 보통 LRU 를 사용한다.(가장 오랫동안 사용하지 않은 페이지를 내보낸다.)

TLB 를 관리는 SW 와 HW 로 할 수 있다. x86 에서는 하드웨어가 관리한다. SW 에서는 valid 값만 관리한다. HW 지원의 장점은 커널에서 구현할 게 거의 없다. SW 의 장점은 페이지 테이블 구현에 대한 자유도가 높다. replacement policy, data struct 가 자유롭다.