오늘은 하루를 쏟아 트러블 슈팅을 했던 mmap_unmap 테스트 케이스에 대해 다뤄보겠습니다.
test : mmap_unmap
테스트 코드를 보면서 이야기 하겠습니다.
테스트가 무엇을 검증하려는 건지 주석에 이렇게 나와 있습니다.
페이지 공간을 mmap -> munmap 한뒤 mmap으로 매핑되었던 페이지 주소 공간이 munmap 이후 접근 불가능한지 확인합니다.
즉 mmap으로 매핑된 주소공간을 munmap 으로 매핑 해제한 뒤
이후에 접근하려 하면 fail되는지 확인하는 테스트 입니다.
테스트 코드를 다시 보겠습니다.
sample.txt 파일은 '794 바이트' 입니다.
해당 파일을 열고
'ACTUAL'에 저장되어있는 가상 주소 '0x10000000' 공간에 sample.txt를 mmap으로 메모리 매핑합니다.
CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
CHECK ((map = mmap (ACTUAL, 0x2000, 0, handle, 0)) != MAP_FAILED, "mmap \"sample.txt\"");
이때, mmap의 할당 크기 인자인 length를 0x2000으로 하여
2개의 페이지 크기에 이 파일을 매핑하도록 요청하고 있습니다.
(참고로, PGSIZE 는 '4096 바이트 = 0x1000' 입니다.)
즉, 794byte의 파일을 9192byte의 물리메모리에 할당 요청합니다.
그럼 메모리 공간은 다음과 같이 됩니다.
처음에 구현을 했을 땐
파일의 크기보다 할당 요청 크기가 더 크게 들어오면, 파일의 크기에 맞게 페이지에 할당하도록 무시했었습니다.
즉, 위 상황이라면 두번째 페이지는 아예 할당하지 않아야 된다고 생각했습니다.
그런데 코드에서 mmap이 잘 매핑되었는지 체크하는 부분을 보면
' ACTUAL 주소 + PGSIZE '를 참조하여 두번째 페이지도 mmap 매핑이 되어있는지 확인합니다.
즉 핀토스는 파일보다 할당하려는 크기가 압도적으로 크더라도 위의 그림처럼 할당하기를 원하는 듯 합니다.
그래서 이때, '아! 할당해달라는 크기가 파일보다 커도 할당해달라는 만큼 해줘야 하는 구나.'
라고 생각하고 코드를 수정했습니다.
근데 이후 상황을 간과하여 발생하는 문제가 하나 있습니다.
오른쪽은 이 테스트를 pass하는 예상 결과이고 , 오른쪽은 테스트 소스 파일입니다.
오른쪽 테스트 코드의 fail() 줄을 보면
munmap 으로 매핑을 해제한 이후에 mmap 했던 주소를 참조하면
page_fault를 통해 exit(-1)되는지를 테스트하는 것 같습니다.
하지만 왼쪽 예상 결과 사진의 화살표를 잘 보시면
mmap 이 정상적으로 매핑되었는지 테스트하는 msg 출력 이후 exit(-1)만 되면 pass입니다.
따라서 만약 munmap()에서 실패하여 exit(-1)로 탈출하여도 pass가 되는 것입니다.
하지만 디버깅해 본 결과,
저는 제 코드가 "munmap(map);" 이후에 printf()가 찍히지 않는 것을 확인했고,
테스트는 pass했지만 munmap에서 실패하는 잘못된 상황이라는 것을 확인했습니다.
(많은 사람들이 이 상황을 간과했을 수 있다고 생각합니다.)
왜 그런가 디버깅으로 확인해보니 테스트 코드 msg 2번째 줄.
msg ("memory is readable %d", *(int *) ACTUAL + 0x1000);
이 부분이 알 수 없는 이유로 page_fault를 일으키지 않아서 두번째 페이지는 uninit page로 남아있었고
제 코드에서는 만약 mmap 매핑된 페이지가 uninit인 상태로 munmap요청을 받으면 예외처리 되었습니다.
이 부분은 충분히 그럴 수 있는 예외상황을 간과하지 않았다는 걸 깨닫고 코드를 수정했지만
주소를 참조했는데 왜 두번째 페이지에서 page_fault를 일으키지 않았는가? 의 고민에 빠졌습니다.
아무리 찾아도 제 코드의 작동 상 uninit으로 페이지 생성을 정상적으로 마쳤고,
page_fault를 일으켜주는 역할은 하드웨어가 하기 때문에 도무지 알 수 없었습니다.
아마 이 테스트 코드를 모두 돌려보시면 두번째 페이지에서 폴트를 일으키지 않을 것입니다.
왜냐하면 원인은 테스트 코드 자체에 있습니다.
해당 테스트를 의도한 것인지 아니면 실수인지 모르겠지만
연산 우선순위에 의해 msg속 %d 에서 역참조하는 포인터는
'ACTUAL+0x1000' 이 아니라 'ACTUAL' 값입니다.
위 코드는 역참조 결과 값에 0x1000을 더한 것일 뿐입니다.
따라서 두번째 msg 속 %d는 첫번째 msg 속 %d 에 0x1000,
즉 4096이 더해진 값을 출력할 뿐이었습니다.
처음에는 테스트 코드에서 괄호를 잘못 사용한 것이라고 생각했습니다.
하지만 pintos-kaist는 수많은 학생들을 거쳐간 학습물인 만큼 오류가 아닌 다른 의도가 있을 것이라고 생각합니다.
만약 숨겨진 의도가 있다면 두번째 msg는 도대체 무엇을 확인하기 위한 코드일까요 ?
혹시 그냥 쉽게 통과했을 수도 있는 교육생들을 위해 해당 테스트에 대한 내용을 공유드렸습니다.
'Jungle' 카테고리의 다른 글
[TIL] pintos : 가상 메모리 구현 (2) - fork , mmap , swap (1) | 2023.12.29 |
---|---|
[TIL] pintos : 가상 메모리 구현 (1) - lazy loading (1) | 2023.12.29 |
[TIL] pintos : stack growth 지원하기 (1) | 2023.12.23 |
[TIL] pintos : VM - fork uninit 시 aux도 복사해주기 (1) | 2023.12.23 |
[TIL] pintos : vm anon 페이지 구현 (1) | 2023.12.22 |