크래프톤 정글/pintOS
pintOS - project2(Userprog) process.c
문미새
2024. 3. 30. 23:05
728x90
process.c
< argument_stack >
더보기
// 명령줄 인자를 스택에 배치한다.
void argument_stack (char **argv, int argc, struct intr_frame *if_){
int minus_addr;
int address = if_->rsp; // 스택 포인터(rsp)의 현재 위치를 담는다.
// 인자를 거꾸로 넣기 위해 마지막부터 루프
for (int i = argc-1; i >= 0;i-- ){
// 현재 인자의 길이 + '\0'(1개)를 minus_addr에 저장
minus_addr = strlen(argv[i]) + 1; //if onearg, value = 7
address -= minus_addr; // 인자 크기만큼 address를 감소시켜 저장공간을 확보한다.
memcpy(address, argv[i], minus_addr); // 현재 인자를 스택에 복사
argv[i] = (char *)address; // 배열 내 해당 인자의 포인터를 스택에 복사된 인자 위치로 변경
}
// 주소값을 8로 나눠 나머지가 있으면(8바이트로 정렬되어있는지 확인)
if (address % 8){
int word_align = address % 8; // 나머지를 word_align에 넣고
address -= word_align; // 주소에서 나머지만큼 뺀다.
memset(address, 0, word_align); // 정렬을 위해 추가된 공간을 0으로 채운다.
}
address -= 8; // 주소 값을 8을 빼 공간을 확보한다.
memset(address, 0, sizeof(char*)); // 배열의 시작부분에 널포인터 삽입
address -= (sizeof(char*) * argc); // 인자 포인터들을 저장할 충분한 공간을 만든다.
memcpy(address, argv, sizeof(char*) * argc); // 인자 포인터 배열을 스택에 복사한다.
address -= 8; // 리턴 주소를 위해 공간을 확보한다.
memset(address, 0, 8); // 리턴 주소 공간을 0으로 초기화한다.
if_->rsp = address; // 스택 포인터에 주소값을 넣는다.
}
< process_create_initd >
더보기
// 프로세스를 생성하고 초기화한다.
tid_t
process_create_initd (const char *file_name) {
char *fn_copy;
tid_t tid;
/* FILE_NAME의 복사본을 만듭니다.
* 그렇지 않으면 발신자와 load() 사이에 경합이 발생합니다. */
fn_copy = palloc_get_page (PAL_USER);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy (fn_copy, file_name, PGSIZE);
// 스페이스 전 첫 부분을 실행하고자 하는 파일의 이름으로 지정 (argument passing)
char *save_ptr;
strtok_r (file_name, " ", &save_ptr);
// 새 스레드를 생성하여 FILE_NAME을 실행
tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
// tid가 TID_ERROR라면(할당이나 생성 과정에서 오류 발생)
if (tid == TID_ERROR) {
palloc_free_page (fn_copy); // 복사본을 할당해제한다.
palloc_free_page (file_name); // 파일도 할당해제한다.
}
// TID_ERROR라면 tid에 에러를 넣고 아니면 tid를 넣기
return tid == TID_ERROR ? TID_ERROR : tid;
}
< initd >
더보기
// 처음 프로세스를 시작할 때 초기화한다.
static void
initd (void *f_name) {
// VM 부분은 아직 안봐도 된다.
#ifdef VM
supplemental_page_table_init (&thread_current ()->spt);
#endif
// 현재 스레드를 실행해서 0 미만이라면(에러) PANIC을 일으킨다.
process_init ();
if (process_exec (f_name) < 0)
PANIC("Fail to launch initd\n");
NOT_REACHED (); // 도달할 수 없다는 뜻
}
< process_fork >
더보기
/* 현재 프로세스를 'name'으로 복제합니다. 새 프로세스의 스레드 ID를 반환하거나
* 스레드를 만들 수 없는 경우 TID_ERROR입니다. */
tid_t
process_fork (const char *name, struct intr_frame *if_) {
/* 현재 스레드를 새 스레드로 복제한다.*/
struct parent_info my_data;
my_data.parent = thread_current();
my_data.parent_f = if_;
struct thread *cur = thread_current();
memcpy(&cur->parent_tf, my_data.parent_f , sizeof(struct intr_frame));
// __do_fork 함수를 이용해 스레드를 생성한다.
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, &my_data);
// 생성에 실패시 TID_ERROR 반환
if (tid == TID_ERROR){
return TID_ERROR;
}
struct thread *child = get_thread_from_tid(tid); // 인자인 tid를 가진 스레드를 가져와 child에 저장
sema_down(&child->process_sema); // 동시성 문제를 위해 세마를 감소시켜 블락상태로 유지
// 가져온 스레드의 exit_status가 생성 에러라면 감소시킨 세마를 다시 증가시키고 에러 반환
if(child->exit_status == TID_ERROR)
{
sema_up(&child->exit_sema);
return TID_ERROR;
}
// 오류가 아니라면 tid를 반환
return tid;
}
< duplicate_pte >
더보기
// 이 함수를 다음에 전달하여 부모 주소 공간을 복제한다.
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;
// 부모 페이지가 커널 페이지인 경우 true 반환
if (is_kernel_vaddr(va)){
return true;
}
// 상위 페이지 맵 레벨 4에서 VA를 해경해 parent_page에 넣고 없으면 false 반환
parent_page = pml4_get_page (parent->pml4, va);
if (parent_page == NULL){
return false;
}
// 자식에 새 PAL_USER 페이지를 할당하고 결과를 NEWPAGE로 설정하고 없으면 false 반환
newpage = palloc_get_page(PAL_USER);
if (newpage == NULL){
return false;
}
/* 부모 페이지를 새 페이지에 복제하고 부모 페이지가 쓰기 가능한지 여부를 확인
(결과에 따라 쓰기 가능으로 설정). */
memcpy(newpage, parent_page, PGSIZE);
writable = is_writable(pte);
/* 5. 쓰기 가능한 권한으로 주소 VA의 어린이 페이지 테이블에 새 페이지를 추가 */
if (!pml4_set_page (current->pml4, va, newpage, writable)) {
/* 6. 작업: 페이지를 삽입하지 못할 경우 오류 처리를 수행 */
return false;
}
return true;
}
< __do_fork >
더보기
/* 부모의 실행중인 프로세스의 복사본을 생성한다.
* parent->tf는 프로세스의 사용자 및 컨텍스트를 유지하지 않는다.
* 즉, process_fork의 두 번째 인수를 이 함수에 전달해야 함 */
static void
__do_fork (struct parent_info *aux) {
struct intr_frame if_;
struct thread *parent = aux->parent;
struct thread *current = thread_current ();
struct intr_frame *parent_if = aux->parent_f;
bool succ = true;
// CPU 컨텍스트를 로컬 스택으로 읽는다.
memcpy(&if_, parent_if, sizeof(struct intr_frame));
if_.R.rax = 0; // 자식 프로세스의 리턴값은 0
// 자식 프로세스를 위한 새로운 페이지 테이블을 생성한다. 없으면 error로 점프
current->pml4 = pml4_create();
if (current->pml4 == NULL)
goto error;
// 자식 프로세스를 활성화한다.
process_activate(current);
// VM은 project3이라 지금은 필요없다.
#ifdef VM
supplemental_page_table_init(¤t->spt);
if (!supplemental_page_table_copy(¤t->spt, &parent->spt))
goto error;
#else
/* 부모 페이지 테이블의 모든 엔트리를 순회해 duplicate_pte함수를 실행하여
자식 프로세스 페이지 테이블에 복제한다. 실패 시 error로 점프 */
if (!pml4_for_each(parent->pml4, duplicate_pte, parent))
goto error;
#endif
/* 현재 스레드의 마지막으로 생성된 fd가 126이면 생성할 수 없기 때문에 error로 점프
(multi-oom의 테스트를 위한 생성제한 조건) */
if (current->last_created_fd == 126) {
goto error;
}
// e에 부모 프로세스의 fd_table의 시작 요소를 가져온다.
struct list_elem* e = list_begin(&parent->fd_table);
// parent_list에 부모 프로세스의 fd_table의 포인터를 가져온다.
struct list *parent_list = &parent->fd_table;
// 부모 프로세스의 fd_table에 데이터가 있으면
if(!list_empty(parent_list)){
// fd_table을 끝까지 순회하며
for (e ; e != list_end(parent_list) ; e = list_next(e)){
// 현재 순회하고 있는 fd의 요소를 가져온다.
struct file_descriptor* parent_fd =list_entry(e,struct file_descriptor, fd_elem);
// 가져온 fd의 파일이 존재하면
if(parent_fd->file != NULL){
// 새로 fd 구조체를 동적할당한다.
struct file_descriptor *child_fd = malloc(sizeof(struct file_descriptor));
// 자식의 fd 파일에 부모의 fd 파일 포인터를 담는다.
child_fd->file = file_duplicate(parent_fd->file);
// 자식의 fd를 부모의 fd와 동일하게 맞춘다.
child_fd->fd = parent_fd->fd;
// 자식의 fd_table에 새 fd요소를 추가한다.
list_push_back(¤t->fd_table, & child_fd->fd_elem);
}
// 각 과정 진행 후 자식 프로세스의 생성fd를 부모 프로세스의 fd로 설정
current->last_created_fd = parent->last_created_fd;
}
current->last_created_fd = parent->last_created_fd;
} else {
current->last_created_fd = parent->last_created_fd;
}
if_.R.rax = 0; // 자식 프로세스의 리턴 값을 0으로 설정(자식 프로세스는 0 리턴이 정상)
// 로드가 완료될 때까지 기다리고 있던 부모 대기 해제
sema_up(¤t->process_sema);
process_init();
// 성공시 마지막으로 새로 생성된 프로세스로 전환한다.
if (succ)
do_iret(&if_);
// error로 점프되었을 시 세마포어를 증가시키고 TID_ERROR로 종료한다.
error:
sema_up(¤t->process_sema);
exit(TID_ERROR);
}
< process_exec >
더보기
// 현재 실행중인 프로세스를 f_name으로 변경해 새로 실행한다.
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* 스레드 구조에서 intr_frame을 사용할 수 없습니다.
* 현재 스레드가 다시 예약되면 실행 정보를 회원에게 저장하기 때문입니다. */
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG; // 데이터, 엑스트라, 스택 세그먼트들을 사용자 데이터 세그먼트로 설정
_if.cs = SEL_UCSEG; // 코드 세그먼트를 사용자 코드 세그먼트로 설정
_if.eflags = FLAG_IF | FLAG_MBS; // 인터럽트 플래그와 기본 상태 플래그를 설정
// 현재 프로세스를 종료하고 자원 정리
process_cleanup ();
// 동시성 문제를 위해 lock을 걸어주고 file_name으로 불러와서 반환 값인 성공 여부를 success에 담는다.
lock_acquire(&filesys_lock);
success = load (file_name, &_if);
lock_release(&filesys_lock);
// 잘 받아오는지 테스트를 위한 hex_dump
// hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true);
// 더 이상 필요하지 않은 file_name을 메모리 해제한다.
palloc_free_page (file_name);
// 불러오기 실패 시 -1 반환
if (!success)
return -1;
// 불러온 프로세스를 실행하기 위해 인터럽트 리턴을 수행한다.
do_iret (&_if);
// 도달할 수 없고 반환되지 않는다.
NOT_REACHED ();
}
< process_wait >
더보기
// 프로세스를 대기시킨다.
int
process_wait (tid_t child_tid) {
/* 이 함수를 작성하기 전 다른 시스템 콜 테스트를 위해 for문으로 대기 시간을 야매로 설정해주었다. */
// for(int i = 0; i < 3000000000; i++) {
// }
// 자식 스레드의 tid를 가져와서 t에 저장. 없으면 -1 반환
struct thread *t = get_thread_from_tid(child_tid);
if (t == NULL) {
return -1;
}
// 세마포어를 감소시키고 자식프로세스가 종료될 때까지 부모 프로세스는 블락 상태로 유지
sema_down(&t->wait_sema);
// 자식 프로세스가 종료될 시, 부모 프로세스의 자식 목록에서 제거
list_remove(&t->child_list_elem);
// 세마포어를 증가시키며 블락 상태를 해제시킨다.
sema_up(&t->exit_sema);
// 자식 프로세스의 종료 상태를 반환
return t->exit_status;
}
< process_exit >
더보기
// 프로세스를 종료시킨다.
void
process_exit (void) {
struct thread *t = thread_current();
// pml4(가상 메모리 주소 공간을 가리키는 포인터)가 존재하면
if (t->pml4 != NULL){
// 스레드의 이름과 exit의 상태를 출력
printf("%s: exit(%d)\n", t->name, t->exit_status);
// 현재 실행중인 프로세스(file)을 닫는다.
file_close(t->running);
// 닫고 나면 실행중인 프로세스를 NULL로 비우기
t->running = NULL;
}
struct list *exit_list = &t->fd_table;
struct list_elem *e = list_begin(&exit_list);
// fd를 2부터 마지막 생성 fd까지 순회하면서 닫는다.
for(int i = 2; i< t->last_created_fd; ++i){
close(i);
}
process_cleanup(); // 프로세스를 종료하고 자원 정리
sema_up(&t->wait_sema); // 자식 프로세스 종료를 부모 프로세스에 알리기 위해 sema를 증가
sema_down(&t->exit_sema); // 완전히 종료되기전 다른 작업이 있으면 마무리하기 위해 블락하여 대기
}
< process_cleanup >
더보기
/* 현재 프로세스의 리소스를 확보합니다.*/
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);
}
}
< process_activate >
더보기
/* 네스트 스레드에서 사용자 코드를 실행할 CPU를 설정합니다.
* 이 기능은 모든 컨텍스트 스위치에서 호출됩니다. */
void
process_activate (struct thread *next) {
/* 스레드의 페이지 테이블을 활성화합니다.*/
pml4_activate (next->pml4);
/* 인터럽트 처리에 사용할 스레드의 커널 스택을 설정합니다. */
tss_update (next);
}
< load >
더보기
/* 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; // ELF 파일의 헤더 정보를 저장하는 변수
struct file *file = NULL;
off_t file_ofs; // 파일 내에서의 위치를 저장할 변수
bool success = false;
int i, j;
//스택에 전달받은 인자를 쌓아주는 작업
char *stk[64];
int argc = 0;
char *token, *save_ptr;
for (token = strtok_r (file_name, " ", &save_ptr); token != NULL; token = strtok_r (NULL, " ", &save_ptr)){
stk[argc] = token;
argc++;
}
/* 페이지 디렉토리를 할당하고 활성화 */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
// file_name에 맞는 실행 파일을 연다. 파일을 가져오지 못했으면 실패 메세지 출력 후 done으로 이동
file = filesys_open (file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
// ELF 파일을 로드하는 과정의 일부이며, 실행 파일 헤더를 읽고 확인한다.
// 조건들이 만족하지 않았을 경우 오류 메세지를 출력하고 done으로 이동
/* ELF 파일은 세 가지로 분류되는데 헤더, 프로그램 헤더 테이블, 섹션 헤더 테이블로 구성된다.
UNIX나 그 계열 운영체제에서 사용되는 파일 형식 */
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;
}
/* 프로그램 헤더 테이블을 순회하며 각 세그먼트 처리
(세그먼트는 실행 파일이 메모리에 로드될 때 필요한 정보를 담고 있다.) */
file_ofs = ehdr.e_phoff; // 파일 내에서 프로그램 헤더 테이블의 시작 위치로 파일 위치 설정
// 프로그램 헤더 테이블의 모든 세그먼트를 순회
for (i = 0; i < ehdr.e_phnum; i++) {
struct Phdr phdr; // 프로그램 헤더의 정보를 저장할 구조체
// 파일 위치의 유효성을 검증해 음수거나, 파일 길이를 초과하면 done으로 이동(오류 처리)
if (file_ofs < 0 || file_ofs > file_length (file))
goto done;
file_seek (file, file_ofs); // 현재 프로그램 헤더 위치로 포인터를 이동
// 현재 프로그램 헤더 정보를 읽고 예상한 크기와 다르면 done으로 이동
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
goto done;
file_ofs += sizeof phdr; // 다음 프로그램 헤더로 위치 이동
// 세그먼트의 타입에 따라 다른 처리를 위해 switch문 작성
switch (phdr.p_type) {
case PT_NULL:
case PT_NOTE:
case PT_PHDR:
case PT_STACK:
default:
/* 위의 세그먼트들을 무시한다. */
break;
// 밑의 세 세그먼트는 done으로 이동(에러 처리)
case PT_DYNAMIC:
case PT_INTERP:
case PT_SHLIB:
goto done;
// 세그먼먼트가 PT_LOAD일 때
case PT_LOAD:
/* 세그먼트 유효성을 검증하고 load_segment함수를 통해 실제로 메모리에
세그먼트를 불러온다. 세그먼트가 파일에서 차지하는 부분과 메모리에 차지해야
할 부분을 계산해, 필요한 부분은 디스크에서 읽고 나머지는 0으로 채운다. */
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) {
/* 일반 세그먼트. 디스크에서 초기 부분을 읽고 나머지는 제로화한다. */
read_bytes = page_offset + phdr.p_filesz;
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
- read_bytes);
} else {
/* 완전히 제로. 디스크에서 아무것도 읽지 마세요. */
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 넣기
file_deny_write(file); // 실행 중인 파일에 대해 변경되는 것을 금지한다.(파일 변경 방지)
/* 프로그램의 스택을 초기화 후, 인터럽트 프레임의 스택 포인터를 적절한 위치로 이동
이동에 실패 시 done으로 이동(에러 처리) */
if (!setup_stack (if_))
goto done;
if_->rip = ehdr.e_entry; // 프로그램 실행을 시작할 메모리 주소를 지정한다.
// stk에 저장된 인자들을 스택에 배치하고, 인터럽트 프레임의 스택 포인터를 조정한다.
argument_stack(stk, argc, if_);
if_->R.rsi = if_->rsp + 8; // 8바이트 더해 다음 인자를 가리키는 포인터로 설정
if_->R.rdi = argc; // 프로그램에 전달된 인자의 수를 저장
success = true; // 성공 처리를 위해 true로 저장
/* 위의 에러 처리로 done으로 보내졌을 시 success를 반환.
done으로 간 건 위의 true 저장을 안 거쳤기 때문에 false가 들어있음 */
done:
// file_close (file);
return success;
}
< validate_segment >
더보기
/* PHDR에서 로드 가능한 유효한 세그먼트를 설명하는지 확인합니다
* 파일을 입력하면 true, 그렇지 않으면 false를 반환합니다. */
static bool
validate_segment (const struct Phdr *phdr, struct file *file) {
// p_offset과 p_vaddr의 페이지 오프셋이 같아야 합니다.
if ((phdr->p_offset & PGMASK) != (phdr->p_vaddr & PGMASK))
return false;
// p_offset은 파일 내를 가리켜야 합니다.
if (phdr->p_offset > (uint64_t) file_length (file))
return false;
// p_memsz는 최소한 p_filesz만큼 커야 합니다.
if (phdr->p_memsz < phdr->p_filesz)
return false;
// 세그먼트는 비어 있지 않아야 합니다.
if (phdr->p_memsz == 0)
return false;
// 가상 메모리 영역은 사용자 주소 공간 범위 내에서 시작과 끝이 모두 이루어져야 합니다.
if (!is_user_vaddr ((void *) phdr->p_vaddr))
return false;
if (!is_user_vaddr ((void *) (phdr->p_vaddr + phdr->p_memsz)))
return false;
// 이 영역은 커널 가상 주소 공간 전체에 걸쳐 "wrap around"할 수 없습니다.
if (phdr->p_vaddr + phdr->p_memsz < phdr->p_vaddr)
return false;
/* 매핑 페이지 0을 허용하지 않습니다.
* 페이지 0을 매핑하는 것은 나쁜 생각일 뿐만 아니라,
* 이를 허용하면 시스템 호출에 널 포인터를 전달한 사용자 코드가
* memcpy() 등에서 널 포인터 인수를 통해 커널을 패닉시킬 가능성이 높습니다. */
if (phdr->p_vaddr < PGSIZE)
return false;
return true;
}
< get_thread_from_tid >
더보기
// tid(스레드 고유 id) 를 가져온다.
struct thread *get_thread_from_tid(tid_t thread_id){
struct thread * t = thread_current();
struct list* child_list = &t->child_list;
struct list_elem* e;
// 자식 프로세스를 처음부터 끝까지 순회하며 t에 각 요소 값을 가져와 같으면 tid를 반환
for (e = list_begin (child_list); e != list_end (child_list); e = list_next (e))
{
t = list_entry(e, struct thread, child_list_elem);
if (t->tid == thread_id){
return t;
}
}
// 다 돌아도 맞는게 없으면 NULL 반환
return NULL;
}
// VM 관련 함수도 작성되어 있는 부분이 있었는데 본인이 VM을 들어가지 못해서 보류했다.
728x90