


페이징: 메모리의 물리 주소 공간을 프레임 단위로, 프로세스의 논리 주소 공간을 페이지 단위로 자른 뒤, 각 페이지를 프레임에 할당
페이지 테이블: 페이지 번호와 프레임 번호를 짝지어 줌
현재 프로세스의 페이지 테이블 주소는 PTBR(페이지테이블 레지스터)에 저장됨
논리 주소가 물리 주소로 변환되는 과정


63 48 47 39 38 30 29 21 20 12 11 0
+---------------+-------------+-------------+-------------+------------+-------------+
| Sign-Extend | PML4 Offset | PDP Offset | PD Offset | PT Offset | Page Offset |
+---------------+-------------+-------------+-------------+------------+-------------+
16 bits 9 bits 9 bits 9 bits 9 bits 12 bits
엔트리 주소 = PML4 테이블 주소 + (PML4 Offset × 8)엔트리 주소 = PDP 테이블 주소 + (PDP Offset × 8)페이지 테이블 주소 = PD 테이블 주소 + (PD Offset × 8)프레임 주소 = 페이지 테이블 주소 + (PT Offset x 8)x8을 해 주는 이유: 각 페이지 테이블 엔트리의 크기가 64비트이므로, 8바이트만큼 곱하는 것.include/threads/vaddr.h/* Page offset (0:12). */
#define PGSHIFT 0 /* 첫 비트는 0비트 */
#define PGBITS 12 /* 오프셋 비트는 총 12비트 */
#define PGSIZE (1 << PGBITS) /* 페이지 크기 (바이트) */
PGSIZE: 1 << 12 = 2 ^ 12 = 4096. 한 페이지는 4096Byte, 즉 4KB로 구성됨.#define PGMASK BITMASK(PGSHIFT, PGBITS) /* Page offset bits (0:12). */
#define pg_ofs(va) ((uint64_t) (va) & PGMASK) /* 가상 주소의 page offset만 추출 */
#define pg_no(va) ((uint64_t) (va) >> PGBITS) /* 가상 주소의 page number만 추출 */
va는 우리가 사용할 가상 주소PGMASK는 Page Offset에 해당하는 하위 12비트는 1, 나머지 52비트는 0으로 설정된 비트마스크pg_ofs: va와 PGMASK AND 비트 연산 -> 하위 12비트, 즉 page offset만 추출할 수 있음pg_no: va의 하위 12비트는 page 내 offset이라면, 상위 52비트는 page number로 사용가능/* 현재 가상 주소를, 페이지 단위로 내림 */
#define pg_round_down(va) (void *) ((uint64_t) (va) & ~PGMASK)
/* 현재 가상 주소를, 페이지 단위로 올림 */
#define pg_round_up(va) ((void *) (((uint64_t) (va) + PGSIZE - 1) & ~PGMASK))
pg_round_down은 va가 속한 페이지의 시작 주소를 반환pg_round_up은va가 이미 페이지 시작 주소이면, 시작 주소를 그대로 반환va가 페이지 중간에 있는 경우, va가 속한 다음 페이지의 시작 주소를 반환/* vaddr이 사용자 영역에 위치하면 true */
#define is_user_vaddr(vaddr) (!is_kernel_vaddr((vaddr)))
/* vaddr이 커널 영역에 위치하면 true */
#define is_kernel_vaddr(vaddr) ((uint64_t)(vaddr) >= KERN_BASE)
KERN_BASE엔 커널 영역의 시작 주소가 저장됨vaddr이 위치해 있는지 확인 가능/* 물리 주소 paddr에 대응되는 커널 영역의 가상 주소 반환 */
#define ptov(paddr) ((void *) (((uint64_t) paddr) + KERN_BASE))
/* 커널 영역의 가상 주소 vaddr에 대응되는 물리 주소 반환 */
#define vtop(vaddr) \
({ \
ASSERT(is_kernel_vaddr(vaddr)); \
((uint64_t) (vaddr) - (uint64_t) KERN_BASE);\
})
KERN_BASE -> 물리 주소 0KERN_BASE + 0x1234 -> 물리 주소 1234와 같은 식...KERN_BASE를 빼면 물리 주소가 됨 (vtop)KERN_BASE를 더하면 커널 가상 주소가 됨 (ptov)include/threads/mmu.h/* 페이지 테이블 엔트리가 가리키는 페이지에, 사용자 모드 접근이 가능한지 확인 */
#define is_user_pte(pte) (*(pte) & PTE_U)
#define is_kern_pte(pte) (!is_user_pte (pte))
/* 페이지 테이블 엔트리가 가리키는 페이지에, 쓰기가 허용되는지 반환 */
#define is_writable(pte) (*(pte) & PTE_W)
PTE_U는 0x4. 하위 3번째 비트가 1인 경우 사용자 접근 가능, 0인 경우 커널만 접근 가능.PTE_W는 0x2. 하위 2번째 비트가 1인 경우 쓰기 허용, 0인 경우 쓰기 불허.typedef bool pte_for_each_func (uint64_t *pte, void *va, void *aux);
bool pml4_for_each (uint64_t *pml4, pte_for_each_func *func, void *aux);
pml4_for_eachuint64_t *pml4)pte에 대해 pte_for_each_func 호출pte_for_each_funcpte: 페이지 테이블 엔트리의 포인터va: pte가 매핑하는 가상 주소 범위의 시작 주소. 앞선 매크로 함수들의 매개변수로 사용 가능.false를 반환하면, pml4_for_each는 반복을 멈추고 false 반환static bool
stat_page (uint64_t *pte, void *va, void *aux) {
if (is_user_vaddr (va))
printf ("user page: %llx\n", va);
if (is_writable (va))
printf ("writable page: %llx\n", va);
return true;
}
pml4_for_each의 func에 대입is_user_vaddr), 쓰기 가능한 (is_writable) 페이지들의 가상 주소를 출력.
0부터 KERN_BASE-1 (기본값 0x8004000000)KERN_BASE부터 나머지 영역struct thread의 uint64_t *pml4는, 해당 쓰레드의 PML4 테이블 주소를 저장함KERN_BASE는 물리 주소로 0, KERN_BASE + 0x1234는 물리 주소로 0x1234와 같은 방식KERN_BASE 이상인 경우. is_kernel_vaddr, is_kern_pte 써라!)KERN_BASE 미만인지만 확인하고, 다른 경우는 userprog/exception.c의 page_fault()에서 처리해도 괜찮음. 하지만 위 방법이 간단해 보여서, 정리는 하지 않았음.filesys 폴더의 filesys.h, file.h에 많이 있는데, 얘를 수정할 필요는 없음.make check는 자동으로 해 주는데, 직접 테스트 케이스 돌릴 거면 이런 절차를 진행해야 함.pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
--fs-disk=10-p tests/userprog/args-single:args-single-- 앞쪽 명령어는, 호스트 머신에서 실행되는 핀토스 스크립트의 옵션-p(put): tests/userprog/args-single 파일을 가상파일 시스템에 복사한다: 다음에 args-single으로 지정한다-- -q -f run 'args-single onearg'-- 뒤쪽 명령어는, 핀토스 커널에 전달되는 인자.-q(quit): 명령 실행 후 Pintos를 자동 종료한다-f: 가상 파일 시스템을 포맷(초기화)한다run 'args-single onearg': args-single 프로그램을 onearg 인자를 전달해 실행한다run 'echo x y z'가 실행된 상황이라 치자.run_task에서 argv[0]은 run, argv[1]은 'echo x y z'가 됨.argv[1]인 "'echo x y z'"는 계속 내부 함수의 매개변수로 전달되며, 지겹도록 많이 사용됨.echo, x, y, z로 파싱해서 알아서 프로그램이 잘 실행되게끔 하는 게 우리 목표.벨로그에 이미지 업로드가 잘 안 돼서 별도의 글로 남깁니다.

process_wait 함수가 정상 실행되지 않음process_create_initd에서 쓰레드 만들 때, 쓰레드 이름 수정해야 함thread_create의 file_name 매개변수로는 echo x y z가 전달됨echo x y z은 fn_copy에 백업한 뒤, thread_create의 쓰레드 이름으로는 echo만 전달해야 함.load에서 사용자 스택 초기화가 완료된 이후, argument passing하기'echo x y z'를 echo, x, y, z로 알아서 잘 나눈 뒤, _if (struct intr_frame 구조체: 대충 쓰레드의 레지스터 정보 저장됨)_if->rsp ) 에 전달해야 함do_iret이 _if를 참고해서, 레지스터 정보를 복원한 뒤 사용자 프로그램의 main()로 이동하게 됨.목표: 내일은 매개변수 전달 과정에 대해 조금 더 이해해 보는 걸로