거북이의 쉼터

(2022.03.09) Memory Mapped Files 가이드라인 (2) 본문

코딩 삽질/KAIST PINTOS (CS330)

(2022.03.09) Memory Mapped Files 가이드라인 (2)

onlim 2022. 3. 9. 20:15

지난 시간에 매뉴얼은 다 봤으니 설계를 해 보자.

 

VM_FILE에 저장을 해야 하는 것들을 생각해보면:

 

  • 인자로 들어가는 type (마커가 있을지도 모르기에)
  • swap in/out을 하기 위해 해당 페이지가 어디에서 기원했는지를 알려주는 정보 (file 포인터, ofs 등)
  • nullbyte 패딩을 위한 길이

해당 인자들은 struct file_page에 넣어주고 관리할 것이다. 문제는 해당 인자들을 넘기려면 aux에서 넘겨야 하기 때문에 file_backed_initializer에서는 해줄 것이 마땅히 없다. 중요한 것은 lazy_load_segment와 유사하게 aux가 넘겨지는 init을 구현해주는 것이다. 

 

아직도 initializer 계열 함수에 kva가 들어가 있는 이유를 모르겠다. 왜지? swap_in의 계열 함수의 경우에는 구조체 내의 인자를 기반으로 실제 프레임에 내용을 쓸 필요가 있어서 kva가 필요하지만, initializer 계열 시점에서는 아직 필요한 정보가 구조체 내에 없기 때문에 kva에는 아무것도 할 수 없다. 굳이 한다고 하면 0으로 초기화? 이건 나중에 생각이 나면 구현해주도록 하자.

 

aux에 인자를 저장하는 시점은 mmap 내부에서이다. 만약 mmap을 해줄 때, 영역의 크기가 크다면 PGSIZE로 분할해서 각 페이지를 할당해줄 필요가 있다. 이 때, 영역이 기존에 존재하는 영역에 중복되는지를 우선 검사해야 하며, 이상이 없다면 할당을 시작하는 식으로 진행해 불필요한 자원 소모를 막는다. 

 

각 인자를 어떻게 할당할지도 생각해보자. type의 경우 현재 별개로 마커가 필요하다고 생각이 들진 않는다. 굳이 필요하다면 COW에서 필요한 경우일텐데 이것도 애매하다. 일단 저장만 해두고 어디에 쓸지는 고민해보자. 필요하면 쓰이겠지...

 

file 포인터는 이전 포스팅에서 매뉴얼이 시킨대로 file_reopen을 통해 fd와 연동된 file을 복사하도록 한다. 여기까지 보면 단순하지만, fork에서 VM_FILE을 복사하는 경우를 생각해본다. 한 mmap에서 여러 페이지가 생성된 경우, 현재 구현으로는 fork는 spt를 단순 순회하면서 각 페이지별로 file 포인터를 file_reopen을 통해 복사해, 자식 프로세스에서 각 페이지가 다른 파일 객체로 연결될 것이다. 해당 파일 포인터를 무지성으로 복사해서 늘리면 안되므로 fork는 해당 페이지들이 같은 mmap에서 기원한 것이라는 사실을 알 필요가 있다. 

 

그래서 file 포인터를 포함하는 구조체 하나를 만들 것이다. VM_FILE에서 다음의 구조체를 가리키는 포인터를 들고 있게 한다면

struct XXX 
{
	struct XXX *copied;
	struct file *fp;
};

copied가 NULL인 경우에는 malloc을 통해 새로운 구조체를 가리키는 포인터를 하나 만들어 fp를 복사하고, copied 멤버를 지정한다. 만약 NULL이 아닌 경우는 copied 멤버에 이전에 복사된 멤버가 지정이 되었을 것이므로 그것을 그냥 가져온다. 코드로 보면 이런 식이다.

struct XXX *original;

if (original->copied == NULL)
{
	struct file *new_fp = file_reopen(original->fp);
	struct XXX *new_XXX = malloc ();
	new_XXX->copied = NULL;
	new_XXX->fp = new_fp;
	original->copied = new_XXX;
    
	/* set new_XXX to VM_FILE */
}

else
{
	struct XXX *new_XXX = original->copied;

	/* set new_XXX to VM_FILE */
}

이런식으로 지정하면 fp의 무지성 복사를 막을 수 있다. 최종적으로는 spt를 다시 순회하면서, original->copied를 모두 NULL로 바꾸어주면 다음 fork를 위한 setting도 끝난다.

 

나중에 munmap이나 exit 등에서 페이지가 해제될 때의 상황도 생각을 해보자. munmap에서는 addr 인자 하나밖에 존재하지 않으므로, 어디까지 해제해야하는지 알기 위해서는 미리 저장을 해 놔야 한다. 또한 해당 addr이 mmap이 된 시작점인지를 알아야 유효하지 않은 주소가 들어왔을 때 거부할 수 있다. 내 생각에는 이걸 위해 마커를 세팅하라는 것 같다. 시작 페이지와 마지막 페이지에 다른 마킹을 해 두면 주소의 유효성과 어디까지 해제를 해야 하는지를 바로 알 수 있기 때문이다. file을 닫는 것은 마지막 페이지까지 write를 해준 뒤에 해주면 된다.

 

exit에서는 조금 상황이 다르다. 따로 mmap을 한 주소가 주어지지 않기에 시작점을 찾아서 주소를 통해 추적하는 방식으로는 해제할 수 없다. 또한 hash의 특성상 여러 페이지가 있을 때 어떤 페이지가 마지막에 나올지는 알 수 없다. 추가로 마지막 페이지가 나오기 전까지는 write의 가능성을 배제할 수 없기 때문에 fp를 닫을 수 없다. 그러면 남은 가능성은 fp를 임시적으로 저장한 뒤에 모든 순회가 마무리될 시점에 일괄적으로 해제해주는 방법이다. 이를 위해 시작 마커, 또는 끝 마커에 해당하는 페이지가 나올 때에 잠시 해당 정보는 리스트 같은 곳에 저장을 해 두었다가, 끝날 때 해당 리스트를 순회하며 자원 해제 및 close를 해주면 될 것이다. 일단 이를 위해서도 마커는 필수적이다.

 

어쨌든 최종적으로 필요한 것을 모두 나열한 구조체는 다음과 같으며,

struct XXX 
{
	struct XXX *copied;
	struct file *fp;
	struct list_elem list_elem;
};

fp와 관련된 것은 끝난것 같다.

 

이제 정리해보자. 

 

  • mmap : 해당 syscall이 요청하는 영역을 확인하고 영역이 타당할 경우, 해당하는 페이지를 spt에 넣어 예약해준다. (VM_UNINIT으로) spt에 넣어줄 때 aux에 필요한 인자를 모두 넣어준다. 이 때, fp가 계속 새로 열리는 것을 방지하기 위해 모든 페이지의 aux에는 같은 fp를 넣어줄 것이다.
  • vm_try_handle_fault : 해당 함수에서 VM_UNINIT에서 VM_FILE로 페이지가 바뀌면서 파일의 내용을 메모리로 읽어올 것이다. 파일의 오프셋과 얼마나 읽어올지의 위치가 aux에 저장되어 있을 것이므로 해당 사항을 참조해 fp에서 읽어오도록 한다.
  • eviction : 아직 구현하지는 않았지만 dirty bit가 켜져있다면 fp에 해당하는 파일에 프레임의 내용을 쓰도록 한다.
  • fork : VM_FILE을 복사할 때, 위의 구조체 방식대로 복사를 하도록 한다. 열리게 되는 fp가 한 mmap당 하나씩만 추가로 열리게 되도록 주의한다.
  • exit : VM_FILE 또는 VM_FILE이 될 VM_UNINIT을 해제할 때, 타입의 마커를 보고 mmap이 시작되는 지점에 해당하는 페이지라면 위의 struct를 잠시 리스트에 저장하고, 나머지 자원은 모두 file_backed_destory 함수로 해제하도록 한다. spt에 대한 모든 순회가 이루어진 이후, 해당 리스트를 순회하며 자원을 정리하도록 한다. fp를 닫고 해당 구조체도 해제해준다.
  • munmap : 해제가 합당한 주소가 입력되었는지 검사하고, 만약 합당한 주소일 경우, 해제가 필요한 upage의 주소를 계산해 spt에서 찾아서 해제하도록 한다. 마지막 upage에 도달했을 때, 파일에 모든 write를 끝낸 뒤, 위의 구조체 자체와 fp를 해제하여 자원을 반환하도록 한다.

다음 포스팅에서는 계획한대로 코딩을 해보도록 하자.

Comments