가상 메모리 작동방식 요약
Cpu가 가상 메모리 주소에 접근하려고 하면
먼저 cache에 그 내용이 있는가를 확인하고
cache에 없으면, mmu에 물리 주소로 변환해달라고 요청
mmu는 tlb를 먼저 확인하고
tlb에 없으면 페이지테이블을 순회한다.
순회해서 pte를 확인했는데 현재 메인 메모리에 올라가있다면(present bit가 set된 상태) 물리 주소를 반환한다.
할당되어있지만, 현재 메인 메모리에 올라가 있지 않다면, os에 page fault를 처리해달라고 요청하고, cpu가 다시 load를 하게 만든다.
할당 된 적 없는 주소라면, 잘못된 접근이라고 알린다.
강의 요약
왜 가상메모리가 필요한가?
실제 메모리는 매우 난잡한 상태이기 때문이다.
초기의 컴퓨터와는 달리 여러 프로세스가 동시에 실행되고, 한 개의 프로세스가 물리메모리 공간에 연속적으로 매핑되지 않는다.
때문에 추상화를 통해 개발자가 자세한 내용에 신경쓰지 않고 코드를 작성할 수 있도록 해줄 필요가 있다.
os가 제공하는 추상화.
1. 주소공간 :유저 프로그램이 매우 크고 연속적인 메모리공간을 가진 것 처럼 느끼게 해준다. 덕분에 유저 프로그램은 한 공간에 코드, 힙, 스택이 전부 들어있는 것 같은 착각 속에서 돌아간다.
=>실제로는 os와 하드웨어가 va를 pa로 전환해준다. os가 위치를 기억하고 있다가 mmu라는 하드웨어에게 주소를 전환해달라고 요청한다.
페이징
os는 가상메모리를 전부 페이지 단위로 나눈다. 같은 방식으로 물리 주소 또한 같은 크기로 나눈다 (이렇게 쪼갠걸 프레임이라고 한다)
Os는 가상메모리를 할당할때 페이지와 프레임과 1:1로 매핑하고 어떤식으로 매핑을 해뒀는지를 페이지 테이블에 기억한다. 이 페이지 테이블을 mmu에게 넘기면 mmu가 어떤 주소에 접근하면 매핑된 물리 주소를 준다
가상화의 목표
투명성: 유저 프로그램이 메모리의 자세한 내용을 모르게 하는 것
효율성: 가상화에 너무 많은 메모리가 사용되거나 메모리에 접속되는데 시간이 너무 많이 걸리지 않게 하는 것.
격리와 보호: 프로세스가 자신에게 할당된 부분이 아닌 메모리에 접근하지 못하게 하는 것
프로그램은 가상주소를 어떻게 쓰는가
코드와 정적변수, 전역변수는 코드영역에 적힌다
함수를 호출하면 지역변수와 인자 등이 스택에 저장된다.
런타임에 메모리를 동적으로 할당하면 힙에 저장된다.
때문에 필요에 따라서 메모리의 어디에 저장할지 생각하고 코드를 짜야한다. 만약에 배열을 선언하는데 얼마만큼을 쓸지가 확실하면 정적으로 선언하고, 얼마만큼이 필요할지 확실하지 않으면 malloc으로 동적할당한다
Os 는 독자적인 프로세스가 아니고, 자신만의 가상주소를 가지고 있지 않다. os는 모든 프로세스의 코드의 일부로서 들어가 있다.
모든 프로세스는 커널이 자신의 일부라고 생각하지만, 커널은 물리 메모리 안에서 자신만의 공간을 따로 가지고 있고 따로 매핑되어있다
단순화된 os에서 메모리는 한 덩어리로 구성되어있고, 시작점과 범위를 가지고 메모리가 적절한 곳에 있는가를 확인한다.
하드웨어가 하는 일
Cpu는 실행에 필요한 특권 모드를 제공한다.
특권 모드는 os만 특정 주소에 접근할 수 있도록 하기 위해 필요하다.
MMU가 가상 주소값과 시작점의 주소를 받아 모든 메모리를 물리 메모리로 전환해준다.
MMU는 접근하려는 주소가 적절한 값이 아니면 os에게 fault나 trap을 걸도록 한다.
OS가 하는 일
가용영역을 관리한다.
프로세스에게 메모리를 할당하고 실행이 끝나면 가용상태로 만든다.
각 프로세스의 메모리가 어디에 할당되었는가를 기억한다.
하드웨어가 주소값을 변환시켜주기 위한 정보를 기억한다.
문맥 전환이 일어날 때 위의 정보들을 새로운 문맥으로 대체한다.
부정한 메모리에 접근할 때 트랩을 건다.
모든 메모리를 페이지 단위로 관리한다.
메모리 할당의 최소단위가 페이지(4kb)이기 때문에 작은 메모리를 할당하면 큰 내부단편화가 생기지만 외부단편화에 비해 큰 문제는 아니다
페이지테이블
페이지테이블은 프로세스별로 존재하며 가상 페이지 번호와 물리 프레임 번호의 매핑을 저장하는 배열이다
mmu는 이 배열에 접근해서 가상주소를 물리 메모리와 매핑해준다.
Page table entry는 가상페이지의 번호를 인덱스로 사용하며, 리스트 안에는 물리프레임의 주소와 페이지의 정보(프로세서에서 사용 중인가, 읽기/쓰기가 가능한가, 현재 물리 메모리 상에 있는가, 최근에 접근하고, 변경된 적이 있는가, 등)를 담은 비트가 들어있다.
가상주소는 가상 페이지 주소와 오프셋으로 이루어져 있어 mmu에 의해 가상페이지 주소가 물리 페이지 주소로 변환되고, 그 페이지 안에서 오프셋을 통해 자료가 들어있는 곳을 찾아간다.
(페이지 테이블에는 많은 페이지테이블 엔트리가 있지만 그 중 일부만 준다? pte가 가상주소의 마지막 종착점이고, 여기서 물리 프레임 주소로 전환이 일어난다?)
cpu가 mmu에게 메모리를 요청하면 mmu가 페이지와 매핑된 프레임을 찾아서 주소를 변환해주는 과정을 거치기 때문에 발생하는 오버헤드는 캐싱으로 감쇄시킨다.
Tlb(translation lookaside buffer)는 최근에 사용된 가상주소-물리주소 조합을 캐시에 저장하는 테이블로, 요청이 들어오면 먼저 tlb를 확인한 뒤에 없으면 페이지테이블을 순회한다. 지역성을 고려해서 미리 tlb의 캐시에 올려놓는 것으로 오버헤드를 줄인다. 가상주소는 프로세스 마다 다르기 때문에 문맥전환이 일어나면 tlb도 다시 쓰여야한다.(하지만 tlb를 관리하는 것은 하드웨어의 몫이지 os가 관여하는 일이 아니다)
페이지를 관리하기 위한 페이지 테이블만으로도 용량이 크기 때문에 페이지테이블의 주소를 가지고 있는 페이지 테이블을 만드는 식으로 관리한다.
많은 페이지를 순회해야하기 때문에 tlb에서 미스가 나면 비용이 크다.
생성 가능한 가상 메모리 주소는 엄청나게 많고, 물리 메모리에는 한계가 있기 때문에 할당을 받은 모든 내용이 물리 주소 안에 있을 수는 없다. 그런 한계를 극복하기 위해 당장 활성화 된 프로세스의 내용만을 메인 메모리에 올리고 나머지는 보조 기억장치에 보관한다.
현재 메인 메모리에 존재하는가 확인하기 위해 pte에 present bit를 만들어둔다. 메모리에 접근을 요청하면, mmu가 pte를 확인하고 물리 메모리의 주소를 반환해준다. 그러나 메모리 안에 없으면, 페이지 폴트가 일어났다고 os 에게 trap flag를 세운다.
os는 신호를 받아서 커널모드로 전환하고 메모리가 어디에 저장되어있는가 확인한다. 보조기억장치에서 정보를 가져오는 것은 매우 오래걸리기 때문에 os는 현재 프로세스를 block상태로 만들고 문맥전환을 한다.
디스크에서 읽기가 끝나면, 디스크는 인터럽트를 걸고, os는 페이지테이블을 갱신하고 block되었던 프로세스를 ready상태로 바꾼다.
메모리를 할당해야하는데 이미 메모리가 꽉 차있으면 보조기억장치로 내용을 옮기는데 이를 swap out이라 한다. 이때 어떤 페이지를 swap out 시킬까 정하는 policy는 다양하다.
가장 오랫동안 쓰지 않을 메모리(예측하기 어렵다)
가장 처음에 들어온 메모리(자주 쓰는 페이지라 먼저 들어왔을 수 있다. belady’s anomaly: 페이지가 많은 상황에 fifo policy와 같은 특정 방식은 엄청나게 많은 page fault를 일으킬 수 있다)
마지막으로 쓴지 가장 오래된 메모리(현대os가 가장 자주 쓰는 방법. 현대 os가 모든 메모리 정보를 확인하는 것이 아닌데 어떻게 이게 가능할까: 주기적으로 pte의 accessd bit와 dirty bit를 확인해서 최근에 사용되었는가를 확인한다)
ps. 이론을 열심히 봤지만, 핀토스 구현에 있어서 가장 난해했던 부분은 해시테이블이나 함수포인터와 구조체로 c언어에서 class를 구현한 메모리 관리와 관계 없는 부분들이었다.