이 글은 pintos 프로젝트의 정답 코드 및 이론을 포함하고 있습니다.
pintos의 취지 특성 상 개인의 공부, 고민 과정이 정말 중요하다고 생각하며
프로젝트 풀이에 대한 스포일러를 받고싶지 않으신 분은 읽으시는걸 비추천 합니다.
이번주는 상당히 힘들다
이전은 비전공자 대려다 놓고 OS를 고치라고? 라는 생각이 들어도 그래도 코드를 손보면 어떻게든 고쳐지기라도 하고 하지만 이번주는 코드에 손 대는거 자체가 엄청 힘들다.
그래서 그냥 마음을 놔버렸다. 그냥 최선을 다해서 할 수 있는 최대한을 뽑아먹기 위해서 노력하자. 송충이는 솔잎을 먹고 살아야지 뽕잎먹다가는 죽어버린다.
그러면 못하는게 억울하지 않겠냐고?
지금 프로젝트 못한다고 개발자 커리어가 박살나는것도아니고
계속해서 나아지는 모습이 훨씬 중요하다고 생각한다.
물론 지금 이런말을 하는건 그냥 도망칠 구실을 찾는게 아니냐고 말을 할 수 있지만
미래의 내가 증명하면 그만이다.
그리고 이번 파트는 이해도가 부족 할 수 있으니까 읽을때 주의를 하기 바란다.
우리는 이제 메모리를 관리해야 한다.
Project 1 에서는 alarm 을 통해 스레드의 생성, 삭제, 그리고 scheduling에 대해 배웠고
Project 2 에서는 User Program 을 통해 Fork로 프로세스를 생성하고, 실행 중 발생하는 다양한 오류들을 Systemcall을 통해 해결하는 방법들을 배웠다.
이제는 스레드와 프로세스를 배웠으니 메모리 관리를 통해서 어떻게 RAM 메모리가 터지지 않고 잘 버틸 수 있는지를 알아보자.
(kaist pintos 강의 자료)
load_segment 함수의 변화를 나타낸 그림이다.
user program 까지는 page를 alloc한 다음 바로 테이블을 세팅했지만
virtual memory 파트에 오면 vm이라는 것이 추가가 된다.
가상메모리가 추가되었기 때문에 물리 메모리에 직접 접근하는 방법은 막히게 되고 VA를 통해 접근을 해야 한다.
이번 Memory Management에서 중요하게 다뤄야 할 과제는 Supplemental Page Table을 만드는 것이다. spt라고 부르는 것인데, 이 구조체는 page table에서 어떤 위치를 참조해야 되는지를 명시한 데이터 이다.
보통 여러 자료구조체로 만들수 있지만 우리 팀은 hash table로 만들었다.
3번째 프로젝트의 핵심 포인트는 '메모리 관리'이다.
프로젝트 2까지의 내용을 보면 데이터를 로딩할때 모든 데이터를 로딩하는 방식을 사용했다. 하지만 사용하지 않고, 심지어 누르지 않으면 있다는것조차 모를 수 있는 데이터까지 모두 로딩을 해야 하기 때문에 프로그램에 큰 오버헤드를 발생시킬 수 있다.
그렇기 때문에 우리는 사용하지 않는 데이터는 필요할때 로딩을 하는, 즉 Lazy Loading(직역 : 개으른로딩 , 지연 로딩을 구현해야 할 필요가 있다.
이러한 로딩을 구현하기 위해서는 Anonymous Page라는걸 알아야 한다.
anonymous page는 다음과 같은 특징을 가진다.
익명 메모리 또는 익명 매핑은 파일 시스템으로 백업되지 않은 메모리를 나타낸다.
이러한 매핑은 프로그램의 스택(stack) 및 힙(heap)에 의해 암묵적으로 생성되거나 mmap(2) 시스템 호출에 의해 명시적으로 생성된다.(PintOS에서의 mmap은 file-backed page만을 생성한다..)
익명 매핑은 보통 프로그램이 액세스 할 수있는 가상 메모리 영역만 정의한다.
읽기 액세스는 특수한 0으로 채워진 물리 페이지를 참조하는 페이지 테이블 항목이 생성된다. 쓰기를 수행하면 쓰여진 데이터를 보유하기 위해 정규 물리 페이지가 할당된다.
disk에서 불러온 RAM 상의 데이터가 변경 되었을때, 이 데이터가 재참조 되기 전까지는 변경내용이 적용되지 않는다.
만약 재참조가 된다면, 이 데이터는 swap out 해서 disk의 내용을 갱신하게 된다.
즉 Anonymous Page는 disk 데이터와 연결되지 않는 데이터임으로 disk에 직접 기록되는 것이 아닌, RAM에서 사용되고 삭제되는 volatile한 메모리라고 해석하면 되겠다. 그리고 anonymous page는 프로세스가 종료될때 같이 해제된다.
그렇다면 anonymous page와 lazy loading의 관계는 다음과 같이 정의할 수 있겠다.
lazy_loading은 예측 불가능한 데이터가 들어온다.
그럼으로 anonymous page를 통해 동적으로 메모리를 할당하고, 최적화를 위해 프로세스가 실행하는 동안에만 데이터를 가지고 있으면 된다.
프로세스가 종료될때 자동으로 페이지가 할당 해제 된다.
그래서 이 Anonymous page를 어떻게 만드는지에 대해 알아봐야 한다.
위와 같은 과정을 거치게 된다.
위의 내용을 글로 적어보자면
process에서 실행 중 load를 사용하게 된다.
코드를 따라 오다가 프로세스에서 사용하기 위해 vm_alloc_page_with_initializer를 실행시킨다.
uninit_new를 실행시킨다.
하지만 실제로 없는 페이지기 때문에 fault를 발생시키고, handler로 넘어가게 된다.
하지만 이는 assult가 아닌 page를 커널에서 할당하기 위한 장치임으로, handler를 따라 간다.
vm_do_claim_page 함수에서 swap_in을 return 한다.
이후 uninit_initializer를 통해서 uninit_page를 만들게 된다.
이후, uninit_page가 만들어 졌기 때문에 init : lazy_load_segment, initializer : anon_initializer를 통해서 anon 페이지를 생성한다.
즉 우리는 anon_page를 만들기 위해서는 uninit 페이지를 만들어야 하고, 이 uninit 페이지가 anon/file-backed page 등으로 바뀌게 된다.
정말 간단하게 말하면 이브이를 생각하면 된다.
uninit = 이브이
anon/file-backed ..... = 진화형
lazy_load 시스템을 적용하면 일단 page를 할당하기 위해서는 uninit page를 생성한 후, 용도에 맞게 변화시키면 된다는 것이다.
말 그대로 스택이 자란다는 것이다.
rsp 위치 할당 때문에 팀원분이 정말 고생한 부분이다. (OOO님 진짜 고생하셨어요)
말로 설명하면 진짜 간단한 문제이다.
왜 우리가 Stack을 키우면서 쓸까?
그것은 heap과 메모리를 나눠먹기 때문이다.
밑에서는 heap영역이 올라오고, 위에서는 stack 영역이 내려가기 때문에 위와 아래서 중립공간을 두고 서로의 크기를 조절하고 있는 것이다. 그렇다면 우리는 여기서 중요하게 생각해야 하는것이
Stack에서 크기를 어떻게 늘릴까? 라는것을 생각해야 한다.
Stack은 다음과 같은 방법으로 크기를 늘려간다.
우선, page fault 핸들러가 발동된다.
User Stack에 있는 rsp 값과 비교해서, 만약에 page fault의 위치가 rsp-8보다 작은 숫자라면, Assult를 띄운다.
rsp와 rsp-8 사이의 숫자라면 rsp의 값을 8만큼 늘리고 핸들러를 종료한다.
여기서 중요한 개념은 2개가 있다.
rsp-8과 rsp 값 사이의 주소에서 page fault가 발생해야 한다.
rsp 값을 User Stack에서 가져와야 한다.
위의 2가지 개념에 대한 추가 설명을 적자면
페이지의 크기가 8이기 때문에 이보다 큰 데이터가 한번에 추가되는 경우는 없다.
그렇기 때문에 한번 페이지를 증가시킬때 늘릴 수 있는 최대 rsp 크기를 8로 고정을 해야
엉뚱한 좌표를 찍어서 데이터가 유출되는 경우를 방지 할 수 있기 때문에, 보안상 꼭 해야한다.
User Stack과 rsp에 대한 개념은 Register와 Kernel 영역에 대한 이해가 필요하다.
우선 Kernel 영역으로 진입하기 전 모든 데이터를 User Stack에 저장한다는 것은 알 것이다.
우리의 컴퓨터는 계산을 하기 위해 Register의 값이 필요한데, Kernel 영역으로 이동을 하고
작업을 진행하게 되면 Register의 값이 바뀌게 된다. 그 말은, rsp의 값도 바뀐다는 것이다.
이러한 상황에서 바뀌어버린 rsp값을 참조한들 정확한 메모리 위치를 찾을 수 없다.
그래서 우리는 page fault가 일어나기전, 그러니까 Kernel 영역으로 진입하기 전의 값을
얻어와야 정확한 메모리 주소를 찾을 수 있다는 것이다.
이곳은 시간이 없어서 건드릴 수 없었던 부분이라 패스하겠다.
괜히 부정확한 정보를 적는것보다는 나은 것 같다.