(TIL)메모리 레이아웃 이해와 구현

낚시하는 곰·2025년 6월 1일
2

jungle TIL

목록 보기
6/20

구현 준비 단계

  • 프레임(물리 프레임)은 실제 메모리 상에서 연속된 4096 바이트 영역입니다.
  • 페이지와 마찬가지로 프레임 정렬되어야 합니다.
  • 64비트 물리 주소도 하위 12비트는 오프셋, 상위 비트는 프레임 번호로 나뉩니다.
  • x86-64 아키텍처는 물리 주소에 직접 접근하는 방법을 제공하지 않기 때문에, Pintos에서는 커널 가상 메모리를 물리 메모리에 직접 매핑합니다(즉, 1:1 매핑).
63-48  | 47-39   | 38-30   | 29-21   | 20-12   | 11-0
SignEx | PML4Off | PDPTOff | PDOff   | PTOFF   | Offset

여기서 내가 생각한 것은 64bit 물리 주소를 하위 12비트는 오프셋, 상위 48bit는 프레임 번호로 쪼개서 쓰는 것을 어떻게 구현해야 되는 거지? 라는 생각이였다.

#define PAGE_SHIFT 12          // 2^12 = 4096
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE - 1))  // 상위 비트만 남김

// 주어진 물리 주소에서 프레임 번호만 추출
static inline uint64_t get_frame_number(uint64_t phys_addr) {
    return phys_addr >> PAGE_SHIFT;
}

// 주어진 물리 주소에서 페이지 내부 오프셋만 추출
static inline uint64_t get_page_offset(uint64_t phys_addr) {
    return phys_addr & (PAGE_SIZE - 1);  // 하위 12비트만 추출
}
uint64_t phys_addr = 0x12345ABC;

// 분리
uint64_t frame_num = get_frame_number(phys_addr);  // → 0x12345
uint64_t offset = get_page_offset(phys_addr);      // → 0xABC

// 다시 합치기
uint64_t new_phys_addr = make_phys_addr(frame_num, offset);  // → 0x12345ABC

shift 연산을 사용해서 프레임 번호와 오프셋을 찾을 수 있다.

// 커널 가상 주소 영역의 시작 (Pintos에서 보통 이렇게 정의됨)
#define KERN_BASE 0x8004000000

// 물리 주소를 커널 가상 주소로 변환 (phys → virt)
static inline void *ptov(uintptr_t phys_addr) {
    return (void *)(phys_addr + KERN_BASE);
}

// 커널 가상 주소를 물리 주소로 변환 (virt → phys)
static inline uintptr_t vtop(const void *virt_addr) {
    return (uintptr_t)virt_addr - KERN_BASE;
}
void *kpage = palloc_get_page(PAL_USER); // 커널 가상 주소로 반환됨
uintptr_t phys_addr = vtop(kpage);       // 실제 물리 주소 계산

// 물리 주소에 직접 접근하는 대신, 커널 가상 주소로 접근
memset(kpage, 0, PGSIZE); // 실질적으로 물리 메모리를 초기화

커널 메모리와 물리 메모리의 1대1 매핑 부분은 이런 식으로 구현이 될 수 있다. palloc_get_page()함수를 사용해서 커널 가상 주소를 찾고, 반환된 가상 주소를 이용해서 물리 주소를 계산할 수 있다(?)


메모리 레이아웃

그림은 각 팀꺼 참고했습니다!!

우리는 process_exec에서 시작한다.

  • process_exec code
    /* Switch the current execution context to the f_name.
     * Returns -1 on fail. */
    int process_exec (void *f_name) {
    	char *file_name = f_name;
    	bool success;
    
    	/* We cannot use the intr_frame in the thread structure.
    	 * This is because when current thread rescheduled,
    	 * it stores the execution information to the member. */
    	struct intr_frame _if;
    	_if.ds = _if.es = _if.ss = SEL_UDSEG;
    	_if.cs = SEL_UCSEG;
    	_if.eflags = FLAG_IF | FLAG_MBS;
    
    	/* We first kill the current context */
    	process_cleanup ();
    
    	/*-- Project 2. User Programs 과제 --*/
    	// for argument parsing
        char *parse[64]; // 파싱된 문자열(토큰)들을 저장
        char *token, *save_ptr; // token: 현재 파싱된 문자열, save_ptr: strtok_r의 내부 상태 유지를 위한 포인터
        int count = 0; // 파싱된 문자열의 개수
    		
        for (token = strtok_r(file_name, " ", &save_ptr); // file_name(例: "ls -a -l")에서 첫 번째 공백(" ")을 기준으로 문자열을 자르고, 첫 번째 토큰("ls")을 token에 할당.
    		token != NULL; 								 // token이 NULL이 아닐 때까지 (더 이상 자를 문자열이 없을 때까지).
    		token = strtok_r(NULL, " ", &save_ptr)      // 다음 공백(" ")을 기준으로 문자열을 잘라, 다음 토큰을 token에 할당 (NULL을 넣어 이전 호출의 다음 지점부터 계속 파싱).
    	){
            parse[count++] = token;
    	}
    	/*-- Project 2. User Programs 과제 --*/
    
    	/* And then load the binary */
    	success = load (file_name, &_if);
        /* If load failed, quit. */
        if (!success){
        	palloc_free_page(file_name);
            return -1;
    	}
    
        //  Project 2. User Programs의 Argument Passing ~
        argument_stack(parse, count, &_if.rsp); // 함수 내부에서 parse와 rsp의 값을 직접 변경하기 위해 주소 전달.
        _if.R.rdi = count; // 첫 번째 인자: argc, 즉 프로그램에 전달된 인자의 개수를 레지스터 rdi에 저장 (시스템 V AMD64 ABI 규약???).
        _if.R.rsi = (char *)_if.rsp + 8; // 두 번째 인자: argv. 스택의 첫 8바이트는 NULL이고, 그 다음 주소가 argv[0]이므로, rsp + 8이 argv 배열의 시작 주소.
    
        // hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true); // 디버그용. 유저 스택을 헥스 덤프로 출력.
        // ~  Project 2. User Programs의 Argument Passing
    
        /* Start switched process. */
        do_iret(&_if);
        NOT_REACHED();
    }

load()를 살펴보자

process_exec코드를 보면 크게 두 가지의 경우로 빠진다. 하나는 do_iret(&_if)이고, 나머지 하나는 load(file_name, &_if)이다.

  • load(file_name, &_if)
    /* Loads an ELF executable from FILE_NAME into the current thread.
     * Stores the executable's entry point into *RIP
     * and its initial stack pointer into *RSP.
     * Returns true if successful, false otherwise. */
    static bool load (const char *file_name, struct intr_frame *if_) {
    	struct thread *t = thread_current ();
    	struct ELF ehdr;
    	struct file *file = NULL;
    	off_t file_ofs;
    	bool success = false;
    	int i;
    
    	/* Allocate and activate page directory. */
    	t->pml4 = pml4_create ();
    	if (t->pml4 == NULL)
    		goto done;
    	process_activate (thread_current ());
    
    	/* Open executable file. */
    	file = filesys_open (file_name);
    	if (file == NULL) {
    		printf ("load: %s: open failed\n", file_name);
    		goto done;
    	}
    
    	/* Read and verify executable header. */
    	if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
    			|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7)
    			|| ehdr.e_type != 2
    			|| ehdr.e_machine != 0x3E // amd64
    			|| ehdr.e_version != 1
    			|| ehdr.e_phentsize != sizeof (struct Phdr)
    			|| ehdr.e_phnum > 1024) {
    		printf ("load: %s: error loading executable\n", file_name);
    		goto done;
    	}
    
    	/* Read program headers. */
    	file_ofs = ehdr.e_phoff;
    	for (i = 0; i < ehdr.e_phnum; i++) {
    		struct Phdr phdr;
    
    		if (file_ofs < 0 || file_ofs > file_length (file))
    			goto done;
    		file_seek (file, file_ofs);
    
    		if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
    			goto done;
    		file_ofs += sizeof phdr;
    		switch (phdr.p_type) {
    			case PT_NULL:
    			case PT_NOTE:
    			case PT_PHDR:
    			case PT_STACK:
    			default:
    				/* Ignore this segment. */
    				break;
    			case PT_DYNAMIC:
    			case PT_INTERP:
    			case PT_SHLIB:
    				goto done;
    			case PT_LOAD:
    				if (validate_segment (&phdr, file)) {
    					bool writable = (phdr.p_flags & PF_W) != 0;
    					uint64_t file_page = phdr.p_offset & ~PGMASK;
    					uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
    					uint64_t page_offset = phdr.p_vaddr & PGMASK;
    					uint32_t read_bytes, zero_bytes;
    					if (phdr.p_filesz > 0) {
    						/* Normal segment.
    						 * Read initial part from disk and zero the rest. */
    						read_bytes = page_offset + phdr.p_filesz;
    						zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
    								- read_bytes);
    					} else {
    						/* Entirely zero.
    						 * Don't read anything from disk. */
    						read_bytes = 0;
    						zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
    					}
    					if (!load_segment (file, file_page, (void *) mem_page,
    								read_bytes, zero_bytes, writable))
    						goto done;
    				}
    				else
    					goto done;
    				break;
    		}
    	}
    
    	/* TODO: Your code goes here.
    	 * TODO: Implement argument passing (see project2/argument_passing.html). */
    
    	// project 2. user programs - rox ~
    	// 현재 스레드의 실행 중인 파일에 이 파일을 추가.
    	t->running = file;
    
    	// 지금 읽고 있는 실행 파일에 뭐 쓰면 안되니까.
    	file_deny_write(file); // 해당 파일을 쓰기 금지로 등록
    	// ~ project 2. user programs - rox
    
    	/* Set up stack. */
    	if (!setup_stack (if_))
    		goto done;
    
    	/* Start address. */
    	if_->rip = ehdr.e_entry;
    
    	success = true;
    
    done:
    	/* We arrive here whether the load is successful or not. */
    	// file_close (file); // TODO: 여기 말고 process_exit에서 닫도록 해야.
    	return success;
    }
    

load()함수의 전체 흐름

```c
static bool load (const char *file_name, struct intr_frame *if_)
```

파싱된 file_name을 인자로 받습니다.

```c
/* Allocate and activate page directory. */
	t->pml4 = pml4_create ();
	if (t->pml4 == NULL)
		goto done;
	process_activate (thread_current ());
```

페이지 디렉토리 할당 & 활성화

```c
/* Open executable file. */
	file = filesys_open (file_name);
	if (file == NULL) {
		printf ("load: %s: open failed\n", file_name);
		goto done;
	}
```

실행 파일 열기

```c
/* Read and verify executable header. */
	if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
			|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7)
			|| ehdr.e_type != 2
			|| ehdr.e_machine != 0x3E // amd64
			|| ehdr.e_version != 1
			|| ehdr.e_phentsize != sizeof (struct Phdr)
			|| ehdr.e_phnum > 1024) {
		printf ("load: %s: error loading executable\n", file_name);
		goto done;
	}
```

실행 파일 헤더 읽기 및 검증하기.

```c
/* Read program headers. */
	file_ofs = ehdr.e_phoff;
	for (i = 0; i < ehdr.e_phnum; i++) {
		struct Phdr phdr;

		if (file_ofs < 0 || file_ofs > file_length (file))
			goto done;
		file_seek (file, file_ofs);

		if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
			goto done;
		file_ofs += sizeof phdr;
		switch (phdr.p_type) {
			case PT_NULL:
			case PT_NOTE:
			case PT_PHDR:
			case PT_STACK:
			default:
				/* Ignore this segment. */
				break;
			case PT_DYNAMIC:
			case PT_INTERP:
			case PT_SHLIB:
				goto done;
			case PT_LOAD:
				if (validate_segment (&phdr, file)) {
					bool writable = (phdr.p_flags & PF_W) != 0;
					uint64_t file_page = phdr.p_offset & ~PGMASK;
					uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
					uint64_t page_offset = phdr.p_vaddr & PGMASK;
					uint32_t read_bytes, zero_bytes;
					if (phdr.p_filesz > 0) {
						/* Normal segment.
						 * Read initial part from disk and zero the rest. */
						read_bytes = page_offset + phdr.p_filesz;
						zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
								- read_bytes);
					} else {
						/* Entirely zero.
						 * Don't read anything from disk. */
						read_bytes = 0;
						zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
					}
					if (!load_segment (file, file_page, (void *) mem_page,
								read_bytes, zero_bytes, writable))
						goto done;
				}
				else
					goto done;
				break;
		}
	}
```

프로그램 헤더 읽기

```c
/* Set up stack. */
	if (!setup_stack (if_))
		goto done;
```

스택 설정

```c
/* Start address. */
	if_->rip = ehdr.e_entry;
```

시작 주소 설정

```c
return success;
```

인자 전달

file_read (file, &ehdr, sizeof ehdr)

file을 ehdr size만큼 읽겠다. 이 말은 즉, file의 헤더 정보를 읽어서 ehdr에 저장하겠다는 말임.

file_read (file, &phdr, sizeof phdr)

위와 동일하지만 인자가 phdr로 변경됨.

load_segment()를 타고 들어가보자

load_segment (file, file_page, (void *) mem_page,
								read_bytes, zero_bytes, writable))

file의 특정 부분을 읽어 들인다. file과 file_page 두 개의 인자가 있는 이유는 읽어들일 file이 무엇인지와 어떤 부분을 읽어야 하는 지에 대한 정보를 나타낸다. 파일을 읽었다면 가상 메모리의 어떤 부분에 올려야할 지 정해야 한다. 가상 메모리의 주소와 읽은 byte 값, zero_bytes는 남은 영역을 0으로 채운다는 말 아닐까?, writable로 쓰기 가능 영역인 지 구분한다.

결론은 실행파일을 사용자가 접근할 수 있는 메모리 공간으로 로드하는 역할을 수행한다. user pool의 Upage로 데이터를 로드한다.

  • load_segment
    /* Loads a segment starting at offset OFS in FILE at address
     * UPAGE.  In total, READ_BYTES + ZERO_BYTES bytes of virtual
     * memory are initialized, as follows:
     *
     * - READ_BYTES bytes at UPAGE must be read from FILE
     * starting at offset OFS.
     *
     * - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
     *
     * The pages initialized by this function must be writable by the
     * user process if WRITABLE is true, read-only otherwise.
     *
     * Return true if successful, false if a memory allocation error
     * or disk read error occurs. */
    static bool
    load_segment (struct file *file, off_t ofs, uint8_t *upage,
    		uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
    	ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
    	ASSERT (pg_ofs (upage) == 0);
    	ASSERT (ofs % PGSIZE == 0);
    
    	file_seek (file, ofs);
    	while (read_bytes > 0 || zero_bytes > 0) {
    		/* Do calculate how to fill this page.
    		 * We will read PAGE_READ_BYTES bytes from FILE
    		 * and zero the final PAGE_ZERO_BYTES bytes. */
    		size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
    		size_t page_zero_bytes = PGSIZE - page_read_bytes;
    
    		/* Get a page of memory. */
    		uint8_t *kpage = palloc_get_page (PAL_USER);
    		if (kpage == NULL)
    			return false;
    
    		/* Load this page. */
    		if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes) {
    			palloc_free_page (kpage);
    			return false;
    		}
    		memset (kpage + page_read_bytes, 0, page_zero_bytes);
    
    		/* Add the page to the process's address space. */
    		if (!install_page (upage, kpage, writable)) {
    			printf("fail\n");
    			palloc_free_page (kpage);
    			return false;
    		}
    
    		/* Advance. */
    		read_bytes -= page_read_bytes;
    		zero_bytes -= page_zero_bytes;
    		upage += PGSIZE;
    	}
    	return true;
    }

uint8_t *kpage = palloc_get_page (PAL_USER);

file_read (file, kpage, page_read_bytes)
  • thread 구조체
    /* A kernel thread or user process.
     *
     * Each thread structure is stored in its own 4 kB page.  The
     * thread structure itself sits at the very bottom of the page
     * (at offset 0).  The rest of the page is reserved for the
     * thread's kernel stack, which grows downward from the top of
     * the page (at offset 4 kB).  Here's an illustration:
     *
     *      4 kB +---------------------------------+
     *           |          kernel stack           |
     *           |                |                |
     *           |                |                |
     *           |                V                |
     *           |         grows downward          |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           |                                 |
     *           +---------------------------------+
     *           |              magic              |
     *           |            intr_frame           |
     *           |                :                |
     *           |                :                |
     *           |               name              |
     *           |              status             |
     *      0 kB +---------------------------------+
     *
     * The upshot of this is twofold:
     *
     *    1. First, `struct thread' must not be allowed to grow too
     *       big.  If it does, then there will not be enough room for
     *       the kernel stack.  Our base `struct thread' is only a
     *       few bytes in size.  It probably should stay well under 1
     *       kB.
     *
     *    2. Second, kernel stacks must not be allowed to grow too
     *       large.  If a stack overflows, it will corrupt the thread
     *       state.  Thus, kernel functions should not allocate large
     *       structures or arrays as non-static local variables.  Use
     *       dynamic allocation with malloc() or palloc_get_page()
     *       instead.
     *
     * The first symptom of either of these problems will probably be
     * an assertion failure in thread_current(), which checks that
     * the `magic' member of the running thread's `struct thread' is
     * set to THREAD_MAGIC.  Stack overflow will normally change this
     * value, triggering the assertion. */
    /* The `elem' member has a dual purpose.  It can be an element in
     * the run queue (thread.c), or it can be an element in a
     * semaphore wait list (synch.c).  It can be used these two ways
     * only because they are mutually exclusive: only a thread in the
     * ready state is on the run queue, whereas only a thread in the
     * blocked state is on a semaphore wait list. */
    struct thread {
    	/* Owned by thread.c. */
    	tid_t tid;                          /* Thread identifier. */
    	enum thread_status status;          /* Thread state. */
    	char name[16];                      /* Name (for debugging purposes). */
    	int priority;                       /* Priority. */
    
    	/* Shared between thread.c and synch.c. */
    	struct list_elem elem;              /* List element. */
    
    #ifdef USERPROG
    	/* Owned by userprog/process.c. */
    	uint64_t *pml4;                     /* Page map level 4 */
    #endif
    #ifdef VM
    	/* Table for whole virtual memory owned by thread. */
    	struct supplemental_page_table spt;
    #endif
    
    	/* Owned by thread.c. */
    	struct intr_frame tf;               /* Information for switching */
    	unsigned magic;                     /* Detects stack overflow. */
        
    	/*-- Alarm clock 과제  --*/
    	int64_t wakeup_tick; // Alarm clock 과제 - 어느 틱에 깨울지.
    	/*-- Alarm clock 과제  --*/
    
    	/*-- Priority donation 과제 --*/
    	int original_priority;
        struct lock *wait_lock;
        struct list donations;
        struct list_elem donation_elem;
    	/*-- Priority donation 과제 --*/
    
    	/*-- Project 2. User Programs 과제 --*/
    	int exit_status;
    	struct file **fd_table;
    	int next_fd;// fd테이블에 open spot의 인덱스
    
    	struct intr_frame parent_if;
        struct list child_list;        // 자신의 자식 목록
        struct list_elem child_elem;   // 부모의 child_list에 들어갈 때 사용하는 노드
    
    	struct semaphore load_sema; // 동기화 대기용 세마포어. 자식 프로세스가 load() 완료 후 부모에게 알리기 위함. fork() 직후 자식이 실행을 성공적으로 시작했는지 부모가 알기 위해 사용됨.
    	struct semaphore exit_sema; // 자식 프로세스가 종료되었음을 부모가 확인할 수 있도록 하기 위한 세마포어
    	struct semaphore wait_sema; // 부모가 자식의 종료를 기다릴 수 있도록 하기 위한 세마포어
     
    	struct file *running; // 현재 실행 중인 파일
    	/*-- Project 2. User Programs 과제 --*/
    };

kernel pool에서 struct thread 정보를 가지고 있음.

palloc_get_page (PAL_USER);을 통해서 물리 페이지를 할당받았음. 그러면 유저 모드에서 할당된 물리 메모리에 접근할 수 있게 되는데 물리 메모리 프레임에서 커널 page와 매핑된 가상 주소를 가지고 있기 때문이다. 사용자가 접근한 가상주소에 kpage가 매핑되어 있지 않다면 페이지 폴트가 발생하고 물리 페이지를 매핑하는 과정을 거친다??

install_page (upage, kpage, writable)
  • install_page
    /* Adds a mapping from user virtual address UPAGE to kernel
     * virtual address KPAGE to the page table.
     * If WRITABLE is true, the user process may modify the page;
     * otherwise, it is read-only.
     * UPAGE must not already be mapped.
     * KPAGE should probably be a page obtained from the user pool
     * with palloc_get_page().
     * Returns true on success, false if UPAGE is already mapped or
     * if memory allocation fails. */
    static bool
    install_page (void *upage, void *kpage, bool writable) {
    	struct thread *t = thread_current ();
    
    	/* Verify that there's not already a page at that virtual
    	 * address, then map our page there. */
    	return (pml4_get_page (t->pml4, upage) == NULL
    			&& pml4_set_page (t->pml4, upage, kpage, writable));
    }

유저 가상주소인 upage에서부터 커널 가상 주소인 kpage까지를 매핑하는 함수.

이 매핑과정을 통해서 추후에 upage만으로 pml4함수를 활용해서 kpage를 참조할 수 있게 된다.

pml4_set_page (t->pml4, upage, kpage, writable)

pml4를 통해서 upage를 통해서 kpage가 가리키고 있는 물리 메모리로 접근할 수 있게 함.

결론 : 유저 모드에서 page에 있는 가상 주소를 통해 물리 메모리에 데이터를 할당할 수 있다.

그림과 같이 Upage는 pml4_walk()함수를 통해서 물리 메모리를 가리키는 가상주소 kpage와 연결된다.


팀과 함께 구현

spt_insert_page()

/* 유효성 검사를 통해 PAGE를 spt에 삽입합니다. */
bool spt_insert_page(struct supplemental_page_table *spt,
					 struct page *page)
{
	int succ = false;

	if (spt == NULL || page == NULL) // 좋았어.
	{
		return false;
	}

	if (hash_insert(&spt->hash, &page->hash_elem) == NULL) // null이면 삽입 성공
	{
		succ = true;
	}
	else // 실패
	{
		succ = false;
	}
	return succ;
}

vm_get_frame()

/* Gets a new physical page from the user pool by calling palloc_get_page.
 * When successfully got a page from the user pool, also allocates a frame, initialize its members, and returns it.
 * After you implement vm_get_frame, you have to allocate all user space pages (PALLOC_USER) through this function.
 * You don't need to handle swap out for now in case of page allocation failure.
 * Just mark those case with PANIC ("todo") for now. */

/* palloc()으로 프레임을 할당받습니다.
 * 사용 가능한 페이지가 없다면 페이지를 교체하여 빈 공간을 만듭니다.
 * 항상 유효한 주소를 반환해야 합니다. 즉, 유저 풀 메모리가 가득 차더라도
 * 이 함수는 페이지를 교체해서라도 공간을 확보해야 합니다. */
static struct frame *
vm_get_frame(void)
{
	struct frame *frame = malloc(sizeof(struct frame));
	if (frame == NULL)
	{
		PANIC("TODO");
	}

	frame->kva = palloc_get_page(PAL_USER);
	frame->page = NULL;
	ASSERT(frame != NULL);
	ASSERT(frame->page == NULL);

	if (frame->kva == NULL)
	{
		free(frame);
		frame = vm_evict_frame(); // HACK: evict frame 함수가 아직 구현되지 않음.
	}
	ASSERT(frame->kva != NULL);
	return frame;
}

vm_claim_page()

/* VA에 할당된 페이지를 할당(claim)합니다. */
// - 주소 `va`에 대해 `struct page`를 찾아오고,
// - `vm_do_claim_page()`를 호출합니다.
bool vm_claim_page(void *va)
{
	struct page *page = NULL;
	page = spt_find_page(&thread_current()->spt, va); // va를 가지고 현재 쓰레드의 spt 에서 페이지를 찾아냄.
	if (page == NULL)								  // spt에 없으면 false 리턴.
		return false;
	return vm_do_claim_page(page); // 있으면 바로 프레임 할당해서 돌려줌.
}

vm_do_claim_page()

/* 주어진 PAGE를 할당하고 MMU 설정을 합니다. */
// - 주어진 페이지를 물리 프레임에 연결합니다.
// - 내부적으로 `vm_get_frame()`을 호출해 프레임을 할당한 후,
// 페이지 테이블에 가상 주소 → 물리 주소 매핑을 추가해야 합니다.
// - 성공 여부를 반환해야 합니다.
static bool
vm_do_claim_page(struct page *page)
{
	struct frame *frame = vm_get_frame();

	/* 링크 설정 */
	frame->page = page;
	page->frame = frame;

	/* 페이지의 VA와 프레임의 PA를 매핑하기 위해 페이지 테이블 엔트리를 삽입하세요. */
	if (pml4_get_page(thread_current()->pml4, page->va) == NULL) // HACK: 확신 없음.
	{
		if (!pml4_set_page(thread_current()->pml4, page->va, frame->kva, page->writable)) // HACK: writable 이렇게 추가하는 거 맞나요?
		{
			return false;
		}
	}
	return swap_in(page, frame->kva);
}

vm_alloc_page_with_initializer()

// 주어진 타입으로 초기화되지 않은 페이지를 생성합니다.
// 초기화되지 않은 페이지의 swap_in 핸들러는 페이지를 자동으로 주어진 타입에 맞게 초기화하고, 주어진 AUX와 함께 INIT을 호출합니다.
// 페이지 구조체를 얻은 후, 해당 페이지를 프로세스의 supplemental page table에 삽입합니다.
// vm.h에서 정의된 VM_TYPE 매크로를 사용하는 것이 유용할 수 있습니다.
/* Implement vm_alloc_page_with_initializer().
 * You should fetch an appropriate initializer according
 * to the passed vm_type and call uninit_new with it. */
/* 초기화 함수와 함께 대기 중인 페이지 객체를 생성합니다.
 * 페이지를 생성하고 싶다면 직접 만들지 말고 반드시 `vm_alloc_page_with_initializer`나 `vm_alloc_page`를 사용해야 합니다. */
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
									vm_initializer *init, void *aux)

{

	ASSERT(VM_TYPE(type) != VM_UNINIT)

	struct supplemental_page_table *spt = &thread_current()->spt;

	/* 해당 upage가 이미 존재하는지 확인합니다. */
	if (spt_find_page(spt, upage) == NULL) // 페이지가 존재하지 않는다면
	{
		// HACK: 플래그는? 말록이야? 아니야? 몰라?
		struct page *page = palloc_get_page(PAL_USER); // 물리가 바로 나오는 거잖아. 그럼 lazy loading이 아니잖아.
		/* TODO: 페이지를 생성하고(이러고 나면 page->va 참조 가능), -> va가 여전히 알쏭달쏭.
		 * VM 타입에 맞는 초기화 함수를 가져와서, -> 이건 처리된 거 같음.
		 * TODO: uninit_new를 호출하여 "uninit" 페이지 구조체를 생성하세요. -> 이건?
		 * TODO: 생성 이후 필요한 필드를 수정하세요. -> 뭐가 필요한데요??
		 */
		page->va = upage;
		page->writable = writable;
		switch (VM_TYPE(type))
		{
		case (VM_ANON):
			// HACK: va가 문제. 어떤 값을 va로 넘겨줘야 할지 불분명. VM_uninit에 대해 처리?.
			uninit_new(page, page->va, init, type, aux, anon_initializer);
			break;
		case (VM_FILE):
			uninit_new(page, page->va, init, type, aux, file_backed_initializer);
			break;
		};

		/* 생성한 페이지를 spt에 삽입하세요. */
		return spt_insert_page(&thread_current()->spt, page); // install page 역할을 해주는 거 아닌가?
	}

err:
	return false;
}

load_segment() 수정


/* FILE의 OFS(오프셋)부터 시작하는 세그먼트를
 * UPAGE 주소에 로드합니다. 총 READ_BYTES + ZERO_BYTES 바이트의
 * 가상 메모리를 다음과 같이 초기화합니다:
 *
 * - READ_BYTES 바이트를 FILE에서 OFS 오프셋부터 읽어
 *   UPAGE에 저장합니다.
 *
 * - UPAGE + READ_BYTES 위치부터 ZERO_BYTES 바이트는 0으로 채웁니다.
 *
 * 이 함수가 초기화한 페이지는 WRITABLE이 true이면
 * 사용자 프로세스가 쓸 수 있어야 하고, false이면 읽기 전용이어야 합니다.
 *
 * 메모리 할당 오류나 디스크 읽기 오류가 없으면 true,
 * 오류가 발생하면 false를 반환합니다. */
static bool
load_segment(struct file *file, off_t ofs, uint8_t *upage,
			 uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
	ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
	ASSERT(pg_ofs(upage) == 0);
	ASSERT(ofs % PGSIZE == 0);

	while (read_bytes > 0 || zero_bytes > 0)
	{
		/* 이 페이지를 어떻게 채울지 계산합니다.
		 * FILE에서 PAGE_READ_BYTES 바이트를 읽고
		 * 나머지 PAGE_ZERO_BYTES 바이트는 0으로 채웁니다. */
		size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
		size_t page_zero_bytes = PGSIZE - page_read_bytes;

		/* TODO: lazy_load_segment에 정보를 전달하기 위한 aux를 설정하세요. */
		struct lazy_aux *aux = NULL; // 파일에서 세그먼트 읽어올 때 필요한 정보가 여기?
		// 여기서 file, page_read_bytes, page_zero_bytes 설정?
		// struct lazy_aux: file, page_read_bytes, page_zero_bytes
		aux = malloc(sizeof(struct lazy_aux));
		aux->file = file;
		aux->read_bytes = page_read_bytes;
		aux->zero_bytes = page_zero_bytes;
		if (!vm_alloc_page_with_initializer(VM_ANON, upage,
											writable, lazy_load_segment, aux)) // uninit 페이지를 만들고 익명 페이지로 초기화
			return false;

		/* 다음 페이지로 이동합니다. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
	}
	return true;
}

setup_stack() 수정

/* USER_STACK 위치에 스택용 PAGE를 만듭니다.
 * 성공 시 true를 반환합니다. */
static bool
setup_stack(struct intr_frame *if_)
{
	bool success = false;
	void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE); // 유저 프로세스의 가상 주소상의 stack bottom

	/* stack_bottom에 스택을 매핑한 뒤 즉시 페이지를 claim 하십시오.
	 * 성공하면 rsp 값을 적절히 설정합니다.
	 * 해당 페이지를 스택으로 표시해야 합니다. */
	/* 여기에 코드 작성 */
	// You might need to provide the way to identify the stack. 
	// You can use the auxillary markers in vm_type of vm/vm.h 
	// (e.g. VM_MARKER_0) to mark the page.
	if (vm_alloc_page_with_initializer(VM_MARKER_STACK, stack_bottom, true, anon_initializer, NULL)) // HACK: 마커 이렇게 쓰면 되는지 잘 모르겠음. 전반적으로 잘 모르겠음.
	{
		success = vm_claim_page(stack_bottom); // 바로 이 주소로 프레임을 할당.
		if (success)
		{
			if_->rsp = USER_STACK;
		}
	}

	return success;
}

supplemental_page_table_copy()

/* src에서 dst로 supplemental page table을 복사합니다. */
// - `src`의 supplemental page table을 `dst`에 복사
// - fork 시 부모의 실행 컨텍스트를 자식에게 복사할 때 사용
// - 각 페이지를 순회하며 `uninit_page`로 생성하고 **즉시 claim 처리**해야 함
// TODO: 구현 하다 말았음.
bool supplemental_page_table_copy(struct supplemental_page_table *dst,
								  struct supplemental_page_table *src)
{
	ASSERT(src != NULL);
	ASSERT(dst != NULL);

	struct hash_iterator hi;
	struct hash *src_hash = &src->hash;

	// hash iterator initialized
	hash_first(&hi, src_hash);
	for (; hi.elem != NULL; hash_next(&hi))
	{
		struct page *src_page = hash_entry(hi.elem, struct page, hash_elem);
		struct page *dest_page;
		vm_alloc_page(page_get_type(src_page), src_page->va, src_page->writable); // 결국 uninit_page를 만들긴 함.
		vm_claim_page(src_page->va);											  // src_page의 가상 주소를 가지고 현재 쓰레드의 spt에서 페이지를 찾아서 프레임을 할당해준다. spt 복사하는데 이게 왜 있어야 하는 거?
		spt_insert_page(src, src_page);
	}
}

supplemental_page_table_kill()

/* supplemental page table이 가지고 있는 리소스를 해제합니다. */
void supplemental_page_table_kill(struct supplemental_page_table *spt)
{
	/* TODO: 해당 스레드가 가지고 있는 supplemental page table의 모든 항목을 제거하고,
	 * TODO: 수정된 내용을 저장소에 기록하세요(write-back). */
	// NOTE: 민혁이가 clear 쓰라고 함
	hash_clear(&spt->hash, spt_destructor); // HACK: destructor 뭘로 줘야 하는지 모르겠음

}

void spt_destructor(struct hash_elem *he)
{
	struct page *page = hash_entry(he, struct page, hash_elem);
	vm_dealloc_page(page);
}
profile
취업 준비생 낚곰입니다!! 반갑습니다!!

0개의 댓글