-
[PintOS] WIL (2) Argument_Passing / System_Call (수정전)SW 정글/운영체제 2024. 10. 8. 01:42
지금 현재 코드는 command line을 입력받으면 함수명과 인자를 분리하지 못 하고 통으로 하나의 문자열로 취급해서 받고 있습니다.
process_exec() 에 코드를 추가해서 간단히 프로그램 파일 이름을 인자로 넣는 것 대신에,
공백이 나올 때마다 단어를 parsing하도록 만들어야 합니다.
이때, 첫 번째 단어는 프로그램명이고
두 번째, 세 번째 단어는 각각 첫 번째, 두 번째 인자입니다.
Gitbook을 읽으면 핀토스가 커널에 통과시킬 수 있는 커맨드 라인의 인자 길이 제한은 128바이트라고 하지만 64로 해도 통과합니다
토큰화하기// 인자 파싱을 수행하는 부분 char *parse[64]; char *token, *save_ptr; int count = 0; for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr)) parse[count++] = token; // 파싱한 인자들을 parse 배열에 저장한다.
strtok_r 함수를 사용하여 file_name 문자열을 공백(" ")을 기준으로 분리합니다.
첫 번째 호출에서는 file_name을 인자로 전달하고, 이후 호출에서는 NULL을 전달합니다.
각 토큰이 NULL이 아닐 동안 루프를 돌며, parse 배열에 토큰을 저장하고 count를 증가시킵니다.
결과적으로 parse 배열에는 프로그램 이름과 인자들이 순서대로 저장됩니다.
&save_ptr
함수 호출 사이에 문자열의 현재 상태를 유지하기 위한 포인터입니다.
즉, strtok_r 함수가 문자열 내에서 어디까지 분리했는지 기억하는 데 사용됩니다.
첫 번째 호출에서는 분리할 문자열 str을 전달하고, 이후 호출에서는 NULL을 전달하여 이전 위치부터 계속 분리합니다.
왜 이후 호출에서 str에 NULL을 전달하는가?•연속적인 분리 수행: strtok_r 함수는 내부적으로 *saveptr를 사용하여 문자열 내에서 현재 위치를 추적합니다.•NULL의 의미: str에 NULL을 전달하면, 이전에 분리 작업을 수행했던 문자열에서 이어서 작업하라는 의미save_ptr의 역할과 필요성
•이를 통해 함수는 각 호출 시마다 이전 위치를 기억하고 다음 토큰을 반환할 수 있습니다.•포인터의 주소 전달:•strtok_r 함수는 saveptr가 가리키는 값을 수정하여 상태를 업데이트합니다.•따라서 함수가 save_ptr의 값을 변경하려면, save_ptr의 주소를 전달해야 합니다.void argument_stack(char **parse, int count, void **rsp) // 주소를 전달받았으므로 이중 포인터 사용 { // 프로그램 이름과 인자 문자열을 스택에 저장 for (int i = count - 1; i > -1; i--) { for (int j = strlen(parse[i]); j > -1; j--) { (*rsp)--; // 스택 포인터를 감소시켜 스택 공간 확보 **(char **)rsp = parse[i][j]; // 인자의 각 문자를 스택에 저장 } parse[i] = *(char **)rsp; // parse[i]에 현재 rsp의 값(해당 인자가 시작하는 스택 주소)을 저장 } // 정렬 패딩 추가 int padding = (int)*rsp % 8; // 현재 스택 포인터가 8의 배수인지 확인 for (int i = 0; i < padding; i++) { (*rsp)--; // 스택 포인터를 1바이트씩 감소 **(uint8_t **)rsp = 0; // 패딩 바이트를 0으로 채움 } // 인자 문자열 종료를 나타내는 0 추가 (*rsp) -= 8; // 스택 포인터를 8바이트만큼 감소 **(char ***)rsp = 0; // NULL 포인터를 스택에 추가하여 인자 문자열 종료 표시 // 각 인자 문자열의 주소를 스택에 추가 for (int i = count - 1; i > -1; i--) { (*rsp) -= 8; // 스택 포인터를 8바이트씩 감소시켜 다음 주소를 저장할 공간 확보 **(char ***)rsp = parse[i]; // 각 인자 문자열의 주소를 스택에 추가 } // 리턴 주소 추가 (*rsp) -= 8; // 스택 포인터를 8바이트 감소 **(void ***)rsp = 0; // 함수 리턴 주소를 0으로 초기화 (리턴 주소가 없음을 나타냄) }
system call flow
1. 사용자 영역(User Space)의 시작•Exec() 함수 호출: syscall.c에 정의된 사용자 프로그램에서 exec() 함수가 호출되고 사용자가 실행하고자 하는 새 프로그램을 로드하기 위해 시스템 콜을 요청합니다.int exec(char *cmd_line) { // cmd_line이 유효한 사용자 주소인지 확인 -> 잘못된 주소인 경우 종료/예외 발생 check_address(cmd_line); // process.c 파일의 process_create_initd 함수와 유사하다. // 단, 스레드를 새로 생성하는 건 fork에서 수행하므로 // exec는 이미 존재하는 프로세스의 컨텍스트를 교체하는 작업을 하므로 // 현재 프로세스의 주소 공간을 교체하여 새로운 프로그램을 실행 // 이 함수에서는 새 스레드를 생성하지 않고 process_exec을 호출한다. // process_exec 함수 안에서 filename을 변경해야 하므로 // 커널 메모리 공간에 cmd_line의 복사본을 만든다. // (현재는 const char* 형식이기 때문에 수정할 수 없다.) char *cmd_line_copy; cmd_line_copy = palloc_get_page(PAL_ZERO); if (cmd_line_copy == NULL) exit(-1); // 메모리 할당 실패 시 status -1로 종료한다. strlcpy(cmd_line_copy, cmd_line, PGSIZE); // cmd_line을 복사한다. // 스레드의 이름을 변경하지 않고 바로 실행한다. if (process_exec(cmd_line_copy) == -1) exit(-1); // 실패 시 status -1로 종료한다. }
•2. 시스템 콜 진입점(System Call Entry)
•Syscall_entry: exec() 함수가 user space에서 시스템 콜 번호와 파라미터를 레지스터에 설정한 후, 시스템 콜 진입점인 syscall-entry.s로 제어를 넘깁니다.3. 커널 영역(Kernel Space)에서의 처리
•Syscall_handler: 커널 모드로 전환된 후, 시스템 콜 핸들러가 실행됩니다. 여기서 시스템 콜 번호를 확인하고 exec()에 해당하는 처리 함수로 넘기며, Exec 함수가 실행될 때 시스템 콜 핸들러는 exec()의 시스템 콜을 처리하기 위해 process_exec() 함수를 호출합니다.4. 사용자 영역으로의 복귀
•Do_iret(): 새 프로그램의 로드가 완료되고 초기 설정이 마무리되면, do_iret() 함수를 통해 다시 사용자 모드로 전환합니다. 이 함수는 새로 로드된 프로그램의 첫 번째 명령어를 실행하기 위해 CPU의 명령 포인터와 관련 레지스터를 새 프로그램의 시작 지점으로 설정합니다.
* 에러
dos2unix: pintos: No such file or directory
>> 상위 핀토스 프로젝트 디렉토리에서 utils 디렉토리 내에서 다음 명령어 실행$ sudo apt install dos2unix $ dos2unix pintos # pintos.py 파일을 리눅스 스타일로 변환 $ dos2unix backtrace # backtrace.py 파일을 리눅스 스타일로 변환
단일 테스트 돌리는 코드
우선 make 후 기다렸다가,make clean && make && cd .. & cd build && pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
userprog/build 에서 실행pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
제대로 동작할 때 결과SeaBIOS (version 1.15.0-1) iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+0FF8B4A0+0FECB4A0 CA00 Booting from Hard Disk..Kernel command line: -q -f put args-single run 'args-single onearg' 0 ~ 9fc00 1 100000 ~ ffe0000 1 Pintos booting with: base_mem: 0x0 ~ 0x9fc00 (Usable: 639 kB) ext_mem: 0x100000 ~ 0xffe0000 (Usable: 260,992 kB) 타이머 보정 중... 130,867,200 loops/s. hd0: unexpected interrupt hd0:0: detected 329 sector (164 kB) disk, model "QEMU HARDDISK", serial "QM00001" hd0:1: detected 20,160 sector (9 MB) disk, model "QEMU HARDDISK", serial "QM00002" hd1: unexpected interrupt hd1:0: detected 118 sector (59 kB) disk, model "QEMU HARDDISK", serial "QM00003" Formatting file system...done. Boot complete. Putting 'args-single' into the file system... Executing 'args-single onearg': (args) begin (args) argc = 2 (args) argv[0] = 'args-single' (args) argv[1] = 'onearg' (args) argv[2] = null (args) end args-single: exit(0) Execution of 'args-single' complete. Timer: 68 ticks Thread: 36 idle ticks, 30 kernel ticks, 2 user ticks hd0:0: 0 reads, 0 writes hd0:1: 85 reads, 264 writes hd1:0: 118 reads, 0 writes Console: 1009 characters output Keyboard: 0 keys pressed Exception: 0 page faults Powering off...
그림 참고글
https://velog.io/@pyotato/PintosProject-2Team-WIL
프로세스
https://rond-o.tistory.com/258(3) woonys
https://woonys.tistory.com/148
userprog/
process.c#include "userprog/process.h" #include <debug.h> #include <inttypes.h> #include <round.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "userprog/gdt.h" #include "userprog/tss.h" #include "filesys/directory.h" #include "filesys/file.h" #include "filesys/filesys.h" #include "threads/flags.h" #include "threads/init.h" #include "threads/interrupt.h" #include "threads/palloc.h" #include "threads/thread.h" #include "threads/mmu.h" #include "threads/vaddr.h" #include "intrinsic.h" #include "userprog/syscall.h" #ifdef VM #include "vm/vm.h" #endif static void process_cleanup(void); static bool load(const char *file_name, struct intr_frame *if_); static void initd(void *f_name); static void __do_fork(void *); struct thread *get_child_process(int pid); /* initd와 다른 프로세스를 위한 일반적인 프로세스 초기화 함수 */ static void process_init(void) { struct thread *current = thread_current(); } /* "initd"라는 첫 번째 사용자 프로그램을 FILE_NAME에서 로드하여 시작합니다. * 새 스레드는 process_create_initd()가 반환되기 전에 스케줄링될 수 있으며, * 심지어 종료될 수도 있습니다. initd의 스레드 ID를 반환하거나, 스레드를 * 생성할 수 없으면 TID_ERROR를 반환합니다. * 주의: 이 함수는 한 번만 호출되어야 합니다. */ tid_t process_create_initd(const char *file_name) { char *fn_copy; tid_t tid; /* Make a copy of FILE_NAME. * Otherwise there's a race between the caller and load(). */ fn_copy = palloc_get_page(0); if (fn_copy == NULL) return TID_ERROR; strlcpy(fn_copy, file_name, PGSIZE); // Argument Passing ~ char *save_ptr; strtok_r(file_name, " ", &save_ptr); // ~ Argument Passing /* Create a new thread to execute FILE_NAME. */ tid = thread_create(file_name, PRI_DEFAULT, initd, fn_copy); if (tid == TID_ERROR) palloc_free_page(fn_copy); return tid; } /* 첫 번째 사용자 프로세스를 시작하는 스레드 함수 */ static void initd(void *f_name) { #ifdef VM supplemental_page_table_init(&thread_current()->spt); #endif process_init(); if (process_exec(f_name) < 0) PANIC("Fail to launch initd\n"); NOT_REACHED(); } /* 현재 프로세스를 `name`으로 복제합니다. 새 프로세스의 스레드 ID를 반환하거나, * 스레드를 생성할 수 없으면 TID_ERROR를 반환합니다. */ tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED) { /* Clone current thread to new thread.*/ // 현재 스레드의 parent_if에 복제해야 하는 if를 복사한다. struct thread *cur = thread_current(); memcpy(&cur->parent_if, if_, sizeof(struct intr_frame)); // 현재 스레드를 fork한 new 스레드를 생성한다. tid_t pid = thread_create(name, PRI_DEFAULT, __do_fork, cur); if (pid == TID_ERROR) return TID_ERROR; // 자식이 로드될 때까지 대기하기 위해서 방금 생성한 자식 스레드를 찾는다. struct thread *child = get_child_process(pid); // 현재 스레드는 생성만 완료된 상태이다. 생성되어서 ready_list에 들어가고 실행될 때 __do_fork 함수가 실행된다. // __do_fork 함수가 실행되어 로드가 완료될 때까지 부모는 대기한다. sema_down(&child->load_sema); // 자식 프로세스의 pid를 반환한다. return pid; } // tid는 단순히 스레드의 ID일 뿐이고, // 실제로 해당 스레드의 데이터(예: 세마포어, // 스택 프레임 등)에 접근하려면 그 스레드의 구조체 포인터가 필요합니다. // 그래서 get_child_process()를 통해 자식 스레드의 구조체를 찾는 과정 struct thread *get_child_process(int pid) { struct thread *cur = thread_current(); struct list *child_list = &cur->child_list; for (struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) { struct thread *t = list_entry(e, struct thread, child_elem); if (t->tid == pid) { return t; } } return NULL; } #ifndef VM /* 부모의 주소 공간을 pml4_for_each 함수에 전달하여 복제합니다. * 이는 프로젝트 2에만 해당됩니다. */ static bool duplicate_pte(uint64_t *pte, void *va, void *aux) { struct thread *current = thread_current(); struct thread *parent = (struct thread *)aux; void *parent_page; void *newpage; bool writable; /* 1. TODO: If the parent_page is kernel page, then return immediately. */ if (is_kernel_vaddr(va)) return true; /* 2. Resolve VA from the parent's page map level 4. */ parent_page = pml4_get_page(parent->pml4, va); if (parent_page == NULL) return false; /* 3. TODO: Allocate new PAL_USER page for the child and set result to * TODO: NEWPAGE. */ newpage = palloc_get_page(PAL_USER | PAL_ZERO); if (newpage == NULL) return false; /* 4. TODO: Duplicate parent's page to the new page and * TODO: check whether parent's page is writable or not (set WRITABLE * TODO: according to the result). */ memcpy(newpage, parent_page, PGSIZE); writable = is_writable(pte); /* 5. Add new page to child's page table at address VA with WRITABLE * permission. */ if (!pml4_set_page(current->pml4, va, newpage, writable)) { /* 6. TODO: if fail to insert page, do error handling. */ return false; } return true; } #endif /* 부모의 실행 컨텍스트를 복사하는 스레드 함수 * 힌트) parent->tf는 프로세스의 사용자 영역 컨텍스트를 포함하지 않습니다. * 즉, process_fork의 두 번째 인자를 이 함수에 전달해야 합니다. */ static void __do_fork(void *aux) { struct intr_frame if_; struct thread *parent = (struct thread *)aux; struct thread *current = thread_current(); /* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */ struct intr_frame *parent_if = &parent->parent_if; bool succ = true; /* 1. Read the cpu context to local stack. */ memcpy(&if_, parent_if, sizeof(struct intr_frame)); if_.R.rax = 0; // 자식 프로세스의 리턴값은 0 /* 2. Duplicate PT */ current->pml4 = pml4_create(); if (current->pml4 == NULL) goto error; process_activate(current); #ifdef VM supplemental_page_table_init(¤t->spt); if (!supplemental_page_table_copy(¤t->spt, &parent->spt)) goto error; #else if (!pml4_for_each(parent->pml4, duplicate_pte, parent)) goto error; #endif /* TODO: Your code goes here. * TODO: Hint) To duplicate the file object, use `file_duplicate` * TODO: in include/filesys/file.h. Note that parent should not return * TODO: from the fork() until this function successfully duplicates * TODO: the resources of parent.*/ // FDT 복사 for (int i = 0; i < FDT_COUNT_LIMIT; i++) { struct file *file = parent->fdt[i]; if (file == NULL) continue; if (file > 2) file = file_duplicate(file); current->fdt[i] = file; } current->next_fd = parent->next_fd; // 로드가 완료될 때까지 기다리고 있던 부모 대기 해제 sema_up(¤t->load_sema); process_init(); /* Finally, switch to the newly created process. */ if (succ) do_iret(&if_); error: sema_up(¤t->load_sema); exit(TID_ERROR); } /* f_name으로 현재 실행 컨텍스트를 전환합니다. * 실패 시 -1을 반환합니다. */ 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(); // Argument Passing ~ char *parse[64]; char *token, *save_ptr; int count = 0; for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr)) parse[count++] = token; // ~ Argument Passing /* And then load the binary */ lock_acquire(&filesys_lock); success = load(file_name, &_if); lock_release(&filesys_lock); // 이진 파일을 디스크에서 메모리로 로드한다. // 로드된 후 실행할 메인 함수의 시작 주소 필드 초기화 (if_.rip) // user stack의 top 포인터 초기화 (if_.rsp) // 위 과정을 성공하면 실행을 계속하고, 실패하면 스레드가 종료된다. // Argument Passing ~ argument_stack(parse, count, &_if.rsp); // 함수 내부에서 parse와 rsp의 값을 직접 변경하기 위해 주소 전달 _if.R.rdi = count; _if.R.rsi = (char *)_if.rsp + 8; // hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true); // user stack을 16진수로 프린트 // ~ Argument Passing /* If load failed, quit. */ palloc_free_page(file_name); if (!success) return -1; /* Start switched process. */ do_iret(&_if); NOT_REACHED(); } void argument_stack(char **parse, int count, void **rsp) // 주소를 전달받았으므로 이중 포인터 사용 { // 프로그램 이름, 인자 문자열 push for (int i = count - 1; i > -1; i--) { for (int j = strlen(parse[i]); j > -1; j--) { (*rsp)--; // 스택 주소 감소 **(char **)rsp = parse[i][j]; // 주소에 문자 저장 } parse[i] = *(char **)rsp; // parse[i]에 현재 rsp의 값 저장해둠(지금 저장한 인자가 시작하는 주소값) } // 정렬 패딩 push int padding = (int)*rsp % 8; for (int i = 0; i < padding; i++) { (*rsp)--; **(uint8_t **)rsp = 0; // rsp 직전까지 값 채움 } // 인자 문자열 종료를 나타내는 0 push (*rsp) -= 8; **(char ***)rsp = 0; // char* 타입의 0 추가 // 각 인자 문자열의 주소 push for (int i = count - 1; i > -1; i--) { (*rsp) -= 8; // 다음 주소로 이동 **(char ***)rsp = parse[i]; // char* 타입의 주소 추가 } // return address push (*rsp) -= 8; **(void ***)rsp = 0; // void* 타입의 0 추가 } /* 스레드 TID가 종료될 때까지 기다린 후 그 종료 상태를 반환합니다. * 커널에 의해 종료된 경우(예: 예외로 인해 종료됨), -1을 반환합니다. * TID가 유효하지 않거나 호출 프로세스의 자식이 아닌 경우, * 또는 이미 주어진 TID에 대해 process_wait()이 성공적으로 호출된 경우, * 기다리지 않고 즉시 -1을 반환합니다. * * 이 함수는 문제 2-2에서 구현될 것입니다. 현재는 아무 것도 하지 않습니다. */ int process_wait(tid_t child_tid UNUSED) { /* XXX: 힌트) Pintos가 process_wait(initd)일 때 종료됩니다. * XXX: process_wait을 구현하기 전에 여기에 무한 루프를 추가하는 것을 추천합니다. */ struct thread *child = get_child_process(child_tid); if (child == NULL) return -1; sema_down(&child->wait_sema); list_remove(&child->child_elem); sema_up(&child->exit_sema); return child->exit_status; } /* Exit the process. This function is called by thread_exit (). */ void process_exit(void) { struct thread *cur = thread_current(); // 1) FDT의 모든 파일을 닫고 메모리를 반환한다. // for (int i = 2; i < FDT_COUNT_LIMIT; i++) // close(i); // palloc_free_page(cur->fdt); //@@@ // 1) FDT의 모든 파일을 닫고 메모리를 반환한다. for (int i = 2; i < FDT_COUNT_LIMIT; i++) { process_close_file(i); // 각 파일 디스크립터 닫기 } palloc_free_multiple(cur->fdt, FDT_PAGES); //@@@ file_close(cur->running); // 2) 현재 실행 중인 파일도 닫는다. process_cleanup(); // 3) 자식이 종료될 때까지 대기하고 있는 부모에게 signal을 보낸다. sema_up(&cur->wait_sema); // 4) 부모의 signal을 기다린다. 대기가 풀리고 나서 do_schedule(THREAD_DYING)이 이어져 다른 스레드가 실행된다. sema_down(&cur->exit_sema); } /* 현재 프로세스의 자원을 해제합니다. */ static void process_cleanup(void) { struct thread *curr = thread_current(); #ifdef VM supplemental_page_table_kill(&curr->spt); #endif uint64_t *pml4; /* 현재 프로세스의 페이지 디렉토리를 삭제하고 커널 전용 페이지 디렉토리로 전환합니다. */ pml4 = curr->pml4; if (pml4 != NULL) { /* 여기에서 올바른 순서가 중요합니다. cur->pagedir을 NULL로 설정한 후에 * 페이지 디렉토리를 전환해야 타이머 인터럽트가 프로세스 페이지 디렉토리로 전환하지 않습니다. * 기본 페이지 디렉토리를 활성화한 후에 프로세스의 페이지 디렉토리를 삭제해야 하며, * 그렇지 않으면 활성 페이지 디렉토리가 해제(또는 정리)된 페이지 디렉토리가 됩니다. */ curr->pml4 = NULL; pml4_activate(NULL); pml4_destroy(pml4); } } /* 다음 스레드에서 사용자 코드를 실행하기 위해 CPU를 설정합니다. * 이 함수는 매 컨텍스트 전환 시 호출됩니다. */ void process_activate(struct thread *next) { /* 스레드의 페이지 테이블을 활성화합니다. */ pml4_activate(next->pml4); /* 인터럽트 처리를 위해 스레드의 커널 스택을 설정합니다. */ tss_update(next); } /* 우리는 ELF 바이너리를 로드합니다. 다음 정의는 ELF 사양([ELF1])에서 가져온 것입니다. */ /* ELF 타입. [ELF1] 1-2 참조. */ #define EI_NIDENT 16 #define PT_NULL 0 /* 무시. */ #define PT_LOAD 1 /* 로드 가능한 세그먼트. */ #define PT_DYNAMIC 2 /* 동적 링크 정보. */ #define PT_INTERP 3 /* 동적 로더의 이름. */ #define PT_NOTE 4 /* 보조 정보. */ #define PT_SHLIB 5 /* 예약됨. */ #define PT_PHDR 6 /* 프로그램 헤더 테이블. */ #define PT_STACK 0x6474e551 /* 스택 세그먼트. */ #define PF_X 1 /* 실행 가능. */ #define PF_W 2 /* 쓰기 가능. */ #define PF_R 4 /* 읽기 가능. */ /* 실행 가능한 헤더. [ELF1] 1-4부터 1-8 참조. * 이는 ELF 바이너리의 맨 앞에 나타납니다. */ struct ELF64_hdr { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; }; /* ELF 프로그램 헤더 구조체 */ struct ELF64_PHDR { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; }; /* Abbreviations */ #define ELF ELF64_hdr #define Phdr ELF64_PHDR static bool setup_stack(struct intr_frame *if_); static bool validate_segment(const struct Phdr *, struct file *); static bool load_segment(struct file *file, off_t ofs, uint8_t *upage, uint32_t read_bytes, uint32_t zero_bytes, bool writable); /* FILE_NAME에서 ELF 실행 파일을 현재 스레드로 로드합니다. * 실행 파일의 진입 지점을 *RIP에 저장하고 * 초기 스택 포인터를 *RSP에 저장합니다. * 성공 시 true를 반환하고, 실패 시 false를 반환합니다. */ 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(); // 페이지 dir(페이지 테이블 포인터) 생성 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; } } // 스레드가 삭제될 때 파일을 닫을 수 있게 구조체에 파일을 저장해둔다. t->running = file; // 현재 실행중인 파일은 수정할 수 없게 막는다. file_deny_write(file); /* Set up stack. */ if (!setup_stack(if_)) // user stack 초기화 goto done; /* Start address. */ if_->rip = ehdr.e_entry; // entry point 초기화 // rip: 프로그램 카운터(실행할 다음 인스트럭션의 메모리 주소) /* TODO: Your code goes here. * TODO: Implement argument passing (see project2/argument_passing.html). */ success = true; done: /* We arrive here whether the load is successful or not. */ // 파일을 여기서 닫지 않고 스레드가 삭제될 때 process_exit에서 닫는다. // file_close(file); return success; } /* Checks whether PHDR describes a valid, loadable segment in * FILE and returns true if so, false otherwise. */ static bool validate_segment(const struct Phdr *phdr, struct file *file) { /* p_offset and p_vaddr must have the same page offset. */ if ((phdr->p_offset & PGMASK) != (phdr->p_vaddr & PGMASK)) return false; /* p_offset must point within FILE. */ if (phdr->p_offset > (uint64_t)file_length(file)) return false; /* p_memsz must be at least as big as p_filesz. */ if (phdr->p_memsz < phdr->p_filesz) return false; /* The segment must not be empty. */ if (phdr->p_memsz == 0) return false; /* The virtual memory region must both start and end within the user address space range. */ if (!is_user_vaddr((void *)phdr->p_vaddr)) return false; if (!is_user_vaddr((void *)(phdr->p_vaddr + phdr->p_memsz))) return false; /* The region cannot "wrap around" across the kernel virtual address space. */ if (phdr->p_vaddr + phdr->p_memsz < phdr->p_vaddr) return false; /* Disallow mapping page 0. Not only is it a bad idea to map page 0, but if we allowed it then user code that passed a null pointer to system calls could quite likely panic the kernel by way of null pointer assertions in memcpy(), etc. */ if (phdr->p_vaddr < PGSIZE) return false; /* It's okay. */ return true; } int process_add_file(struct file *f) { struct thread *curr = thread_current(); struct file **fdt = curr->fdt; // limit을 넘지 않는 범위 안에서 빈 자리 탐색 while (curr->next_fd < FDT_COUNT_LIMIT && fdt[curr->next_fd]) curr->next_fd++; if (curr->next_fd >= FDT_COUNT_LIMIT) return -1; fdt[curr->next_fd] = f; return curr->next_fd; } struct file *process_get_file(int fd) { struct thread *curr = thread_current(); struct file **fdt = curr->fdt; /* 파일 디스크립터에 해당하는 파일 객체를 리턴 */ /* 없을 시 NULL 리턴 */ if (fd < 2 || fd >= FDT_COUNT_LIMIT) return NULL; return fdt[fd]; } // void process_close_file(int fd) // { // struct thread *curr = thread_current(); // struct file **fdt = curr->fdt; // if (fd < 2 || fd >= FDT_COUNT_LIMIT) // return NULL; // fdt[fd] = NULL; // } void process_close_file(int fd) { struct thread *curr = thread_current(); struct file **fdt = curr->fdt; if (fd < 2 || fd >= FDT_COUNT_LIMIT) return; if (fdt[fd] != NULL) { file_close(fdt[fd]); // 파일 닫기 fdt[fd] = NULL; // 파일 테이블에서 제거 } } #ifndef VM /* 이 블록의 코드는 프로젝트 2에서만 사용됩니다. * 프로젝트 2 전반에 걸쳐 함수를 구현하려면 #ifndef 매크로 외부에서 구현하십시오. */ /* load() helpers. */ static bool install_page(void *upage, void *kpage, bool writable); /* FILE에서 OFS 오프셋에서 시작하는 세그먼트를 주소 UPAGE에 로드합니다. * 총 READ_BYTES + ZERO_BYTES 바이트의 가상 메모리가 초기화됩니다: * * - READ_BYTES 바이트는 FILE에서 OFS에서 시작하여 UPAGE에 읽어야 합니다. * * - READ_BYTES 뒤에 있는 UPAGE + ZERO_BYTES 바이트는 0으로 채워야 합니다. * * 이 함수로 초기화된 페이지는 WRITABLE이 true일 경우 사용자 프로세스가 수정할 수 있고, * 그렇지 않은 경우 읽기 전용입니다. * * 성공 시 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); file_seek(file, ofs); 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; /* 메모리 페이지를 가져옵니다. */ uint8_t *kpage = palloc_get_page(PAL_USER); if (kpage == NULL) return false; /* 이 페이지를 로드합니다. */ 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); /* 프로세스의 주소 공간에 페이지를 추가합니다. */ if (!install_page(upage, kpage, writable)) { printf("fail\n"); palloc_free_page(kpage); return false; } /* 다음으로 진행합니다. */ read_bytes -= page_read_bytes; zero_bytes -= page_zero_bytes; upage += PGSIZE; } return true; } /* USER_STACK에 0으로 채워진 페이지를 매핑하여 최소한의 스택을 만듭니다. */ static bool setup_stack(struct intr_frame *if_) { uint8_t *kpage; bool success = false; kpage = palloc_get_page(PAL_USER | PAL_ZERO); if (kpage != NULL) { success = install_page(((uint8_t *)USER_STACK) - PGSIZE, kpage, true); if (success) if_->rsp = USER_STACK; else palloc_free_page(kpage); } return success; } /* 사용자 가상 주소 UPAGE에서 커널 가상 주소 KPAGE로의 매핑을 페이지 테이블에 추가합니다. * WRITABLE이 true일 경우, 사용자 프로세스가 페이지를 수정할 수 있고, * 그렇지 않으면 읽기 전용입니다. * UPAGE는 이미 매핑되어 있으면 안 됩니다. * KPAGE는 palloc_get_page()로 사용자 풀에서 얻은 페이지여야 합니다. * 성공 시 true를 반환하고, UPAGE가 이미 매핑되어 있거나 메모리 할당이 실패한 경우 false를 반환합니다. */ static bool install_page(void *upage, void *kpage, bool writable) { struct thread *t = thread_current(); /* 해당 가상 주소에 이미 페이지가 없는지 확인한 후, 페이지를 매핑합니다. */ return (pml4_get_page(t->pml4, upage) == NULL && pml4_set_page(t->pml4, upage, kpage, writable)); } #else /* From here, codes will be used after project 3. * If you want to implement the function for only project 2, implement it on the * upper block. */ static bool lazy_load_segment(struct page *page, void *aux) { /* TODO: Load the segment from the file */ /* TODO: This called when the first page fault occurs on address VA. */ /* TODO: VA is available when calling this function. */ } /* 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); 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; /* TODO: Set up aux to pass information to the lazy_load_segment. */ void *aux = NULL; if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux)) return false; /* Advance. */ read_bytes -= page_read_bytes; zero_bytes -= page_zero_bytes; upage += PGSIZE; } return true; } /* Create a PAGE of stack at the USER_STACK. Return true on success. */ static bool setup_stack(struct intr_frame *if_) { bool success = false; void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE); /* TODO: Map the stack on stack_bottom and claim the page immediately. * TODO: If success, set the rsp accordingly. * TODO: You should mark the page is stack. */ /* TODO: Your code goes here */ return success; } #endif /* VM */
'SW 정글 > 운영체제' 카테고리의 다른 글
[Sorting] 56. Merge Intervals (0) 2024.10.24 [PintOS] WIL (4) 난 이해를 한 걸까..? (0) 2024.10.22 [PintOS] WIL (3) Lazy load segment / 지연 로딩 간단한 수준으로만 (0) 2024.10.14 [PintOS] WIL (1) alarm-clock 위주 (추후 보완 예정) (2) 2024.10.01