Pintos 프로젝트 2 오늘부터 시작이다
뭐 얼마나 어렵기에 2주씩이나 주는 지 모르겠다;;
7일과 14일, 단순 숫자로 보자면 딱 2배지만
실제 활용시간은 궤를 달리한다
3배, 어쩌면 그 이상의 시간이라 보니 말이다
아무래도 발표 준비나 첫날 팀코어 시간 정하고 이것저것 하면 7일중 앞뒤는 거의 빼다시피 해야 하는데
그리 된다면 7일이 아닌 5일, 일요일 제대로 안하면 4일
순 공부 몰입 시간이 줄어드는 데다가 특강이나 이것저것 더하면 이것보다도 적다
그치만, 2주, 14일
이정도의 시간?
차포 떼고 9점 주고 수수료 떼도 이보다 7일 더 있는거다
3~4일 집중할 시간인 무려 10~11일로 늘어난,
말 그대로 3~4배 가까이의 시간을 더 벌었다
이 정도면 신화를 새로 쓸 정도의 시간이라 할 수 있다
잡설이 길었다
오늘은 GitBOOK 내용 요약할거다
바로 들어가 보자
기본 코드에는 사용자 프로그램 로드, 실행은 가능하지만
입출력(I/O)이나 상호작용은 불가능하다
그렇다는 건 이번에 만든다는 거다
시스템 콜을 통해 프로그램이 OS와 상호작용할 수 있도록 만들어야 한다
userprog
디렉토리 위주로 작업하지만 Pintos 거의 다 건든다고 한다
프로젝트 1과 달리 2에서는 테스트 코드 말고 전부 자유롭게 수정가능
단, 절대 #ifdef VM
블록 내부에 코드 금지
프로젝트 3의 내용임
그리고 #ifndef VM
블록 내부는 3에서 생략
손댈 파일 나온다
process.c
, process.h
ELF 바이너리 로드, 프로세스 시작
syscall.c
, syscall.h
시스템 콜 용도
syscall-entry.S
어셈블리 코드 이해할 필요 X
exception.c
, exception.h
권한 없거나 금지된 작업 수행시 예외/폴트로 커널에 트랩해줌
gdt.c
, gdt.h
GDT 설정. 궁금하면 읽고 아님말고. 수정은 불필요
tss.c
, tss.h
커널 스택 포인터 찾는 역할이고 수정은 불필요
정확히 뭐하는 건지는 몰?루
파일 시스템은 피치 못하게 시스템 콜과 연관되어 있고
그렇다고 파일 시스템을 지금 구현하는게 목적은 아니기에
단순하고 완전한 파일 시스템을 제공해준다
filesys.h
와 file.h
를 검토해보면 된다
코드 건드리는 건 X
몇가지 제약이 존재한다
내부 동기화 없음:
동시 접근이 서로 간섭한다
한 번에 한 프로세스만 파일 시스템 코드를 실행하도록 동기화 필요
파일 크기 고정:
생성 시 크기가 고정
루트 디렉터리도 파일로 표현되므로, 생성 가능한 파일 수 역시 제한
단일 연속(extent) 할당:
한 파일의 데이터가 디스크의 연속된 섹터 범위에 있어야 한다
시간이 지나면 외부 단편화가 심각해질 수 있다
서브디렉터리 없음
파일명 14자 제한
중간 크래시 시 복구 불가:
동작 중 시스템 크래시가 나면 디스크가 자동 복구 불가능한 방식으로 손상될 수 있습니다
복구 도구도 없다
그리고 개별 테스트 직접 실행 하고 싶을시 동작 방법 나오는데
여긴 일단 길고 복잡해 스킵하겠다
9기에서 만든 걸로 개별테스트가 불가할 시 보겠다
대충 부족한 기능 많아 안되는 거 많다는 내용이다
디버깅 하다가 filesys.dsk
망가질 수 있으니
깨끗한 기봊 디스크 만들어 필요할때마다 복사하는 방법도 있다고 한다
Pintos에서 가상메모리는 사용자 가상 메모리와 커널 가상 메모리 두 영역으로 나뉜다
사용자 가상 메모리는 0 ~ KERN_BASE
이고 나머지는 커널 가상 메모리다
KERN_BASE
는 include/threads/vaddr.h
의 0x8004000000
가 기본값이다
사용자 가상 메모리
프로세스별로 존재해 전환이 이루어지면 가상 주소 공간도 같이 전환된다
전환 시, 페이지 테이블 기준 레지스터가 바뀐다
struct thread
는 프로세스의 페이지 테이블을 보유한다
커널 가상 메모리
전역으로 존재하여 어떤 스레드가 실행중이든 동일하게 매핑된다
Pintos에서는 물리 메모리 1:1 매핑되어 KERN_BASE
부터 시작
사용자 프로그램은 자신의 사용자 가상 메모리에만 접근 가능
커널 스레드는 사용자 가상 메모리에도 접근 가능
그치만, 매핑되지 않은 사용자 가상 메모리는 불가능
그냥 그거다
csapp 교재에 나오는 배치
USER_STACK +----------------------------------+
| user stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| grows upward |
| ^ |
| | |
| | |
+----------------------------------+
| uninitialized data segment (BSS) |
+----------------------------------+
| initialized data segment |
+----------------------------------+
| code segment |
0x400000 +----------------------------------+
| |
| |
| |
| |
| |
0 +----------------------------------+
지금은 사용자 스택 크기 고정이다
커널은 시스템 콜 처리 중 메모리에 접근하는데
사용자 프로그램이 제공한 포인터를 잘 확인해야한다
이상한 거 주면 프로세스 종료하고 즉시 자원 해제 처리 필요
이를 위한 두가지 방법 (둘 중 하나 선택)
검증 후 역참조
사용자 포인터의 유효성을 먼저 검사 후 역참조 한다
이거 쓰고자 한다면 thread/mmu.c
와 include/threads/vaddr.h
의 함수 살펴봐야 한다
가장 간단한 방식이다
경계만 확인 후 페이지 폴트 처리
사용자 포인터가 KERN_BASE
아래인지 만 확인하고 역참조한다
이거 쓰려면 userprog/exception.c
의 page_fault()
를 수정해야 한다
MMU1를 활용해 더 빠르고 실제로 흔히 쓴다
둘 중 무엇을 쓰던 간에 자원누수 조심
잘못된 사용자 포인터 만나도 자원 해제는 반드시 해야한다
유효성 검사 하면 이거 쉽지만 안하면(2번 방식) 어렵기에 헬퍼 코드가 있다
/* 사용자 가상 주소 UADDR에서 1바이트를 읽습니다.
* UADDR는 반드시 KERN_BASE 아래여야 합니다.
* 성공하면 읽은 바이트 값을 반환하고,
* 페이지 폴트가 발생하면 -1을 반환합니다. */
static int64_t
get_user (const uint8_t *uaddr) {
int64_t result;
__asm __volatile (
"movabsq $done_get, %0\n"
"movzbq %1, %0\n"
"done_get:\n"
: "=&a" (result) : "m" (*uaddr));
return result;
}
/* BYTE 값을 사용자 주소 UDST에 기록합니다.
* UDST는 반드시 KERN_BASE 아래여야 합니다.
* 성공하면 true를, 페이지 폴트가 발생하면 false를 반환합니다. */
static bool
put_user (uint8_t *udst, uint8_t byte) {
int64_t error_code;
__asm __volatile (
"movabsq $done_put, %0\n"
"movb %b2, %1\n"
"done_put:\n"
: "=&a" (error_code), "=m" (*udst) : "q" (byte));
return error_code != -1;
}
코드가 뭐하는지 간략히 설명 덧붙이자면
복구 주소 저장 (RAX에)
-> 사용자 메모리 접근 시도
-> 폴트
-> RAX= -1
, RIP=RAX(복구주소)
로 바꾸고 복귀
이를 통해 호출자가 성공/실패 여부를 알아차려
자원 해제 등을 통해 정상적으로 코드 정리한다
= 자원 누수 해결
메모리 관리 장치: 변환과 접근 권한 검사 자동으로 해줘 커널이 일일이 하는 것보다 빠름 ↩