점심 식사 때 우연히 코치님과 함께 식사하게 되었다.
방향성에 관해 많이 말씀해주심
https://github.com/whwogur/PintOS_Project3/tree/master
하루밖에 안 남아서
아예 깃허브에서 정답 코드 검색하고 다른 부분 따라서 고쳐보기
tid_t process_fork(const char *name, struct intr_frame *if_)
{
// thread_current()->user_tf = *if_;
memcpy(&thread_current()->user_tf, if_, sizeof(struct intr_frame));
그냥 =
이 아니라 memcpy를 사용
if (tid == TID_ERROR) // 쓰레드를 생성하지 못했다면 바로 에러 코드 리턴
return tid;
struct thread *child = find_child(tid);
sema_down(&child->child_sema);
if (child->dying_status == -1) // 이미 커널에 의해 죽었다면 반환
return TID_ERROR;
이 부분들 전부 새로 추가
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);
if (newpage == NULL)
return false;
보호 구문 추가
// if (is_writable(pte))
// writable = true;
writable = is_writable(pte);
간결하게 바꾸기 (어차피 자료형이 bool이라 결과는 똑같긴 했을듯. 앞에서 false로 초기화도 해줘서.)
__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->user_tf;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy(&if_, parent_if, sizeof(struct intr_frame));
if_.R.rax = 0;
if_.R.rax = 0; 여기에 추가
// process_init(); 삭제
이거 원래 있었던 거 아닌가?
일단 답지에 없었으니까 삭제
페이지 폴트 오류도 여기 근처에서 생기긴 했었음
error:
/* 복제 실패 */
current->dying_status = TID_ERROR;
sema_up(¤t->child_sema);
exit(TID_ERROR);
// thread_exit();
챙겨줄 거 챙겨주고 바로 exit
파일 디스크립터 부분만 베끼지 않았는데, (로직이 달랐음)
따라할 만큼 다 따라한 상황에서도 똑같은 오류가 남.
파일 디스크립터 테이블 복사 부분에서 페이지 폴트가 나고,
주석 처리하면 Interrupt 0x0d (#GP General Protection Exception) at rip=800421fda5
이 일어난 후 Interrupt 0x06 (#UD Invalid Opcode Exception) at rip=80042183ec
이 여러 번 반복되다가 끝남.
이게 문제일 가능성이 굉장히 높아보이니 malloc으로 구현했던 기존 코드를 수정해서
palloc으로 구현하는 방식을 도입해주자
https://yskisking.tistory.com/244
내 코드를 전부 의심하겠다는 마인드로 천천히 처음부터 다 고쳐보자
오만 곳에 다 lock을 걸어주는 느낌
무지성으로 복붙했는데도 안되길래 멘탈이 완전히 나가서 헤롱헤롱거리다가
코치님이 말씀해주셨던 것도 곱씹고 내가 무엇을 어떻게 해야할지에 대한 생각을 한 결과
지금 당장 하지 못하는 것에 괴로워할 이유가 단 하나도 없다는 사실을 깨달음
그리고 무지성 베끼기 하지 말고 내가 내 머리를 써서 해야지 안 그러면 아무 의미 없는 시간을 보낼 뿐이라는 걸 깨달음
일단 file descriptor table을 64개만 되어있는 malloc이 아니라 palloc으로 고쳐보기로 함.
이것 외에도 문제 되는 부분들 다 고쳤는데 여전히 똑같은 오류 발생.
나까지는 되는거 확실?Kernel PANIC at ../../threads/thread.c:341 in thread_current(): assertion `t->status == THREAD_RUNNING' failed.
Call stack: 0x8004218407 0x8004207161 0x800420a9ea 0x800421b626 0x800421b744 0x800421590a 0x800421d0ec 0x8004208fbb 0x80042093d9 0x800422aa80 0x8004207397 0x800420a1d9 0x800420a981 0x800420c310 0x800421ee3e 0x800421c756 0x400404370.
오류가 막 올라갔어서 앞을 못(안) 봤었는데, 침착하게 원인을 파악하려고 보니 thread_current()에서 thread_running이 아니라는 것을 찾아냄.
do_iret 이후에 자식 쓰레드를 깨우려고 보니까 thread_running이 아니었다는 건가?
0x0000008004218407: debug_panic (lib/kernel/debug.c:32)
0x0000008004207161: thread_current (threads/thread.c:343)
0x000000800420a9ea: lock_held_by_current_thread (threads/synch.c:340)
0x000000800421b626: acquire_console (lib/kernel/console.c:87)
0x000000800421b744: vprintf (lib/kernel/console.c:122)
0x000000800421590a: printf (lib/stdio.c:80)
0x000000800421d0ec: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208fbb: intr_handler (threads/interrupt.c:352)
0x00000080042093d9: intr_entry (threads/intr-stubs.o:?)
0x000000800422aa80: (unknown)
0x0000008004207397: check_priority_and_yield (threads/thread.c:414)
0x000000800420a1d9: sema_up (threads/synch.c:124)
0x000000800420a981: lock_release (threads/synch.c:331)
0x000000800420c310: free (threads/malloc.c:233)
0x000000800421ee3e: file_close (filesys/file.c:70)
0x000000800421c756: load (userprog/process.c:604)
뭔가가 굉장히 많이 얽혀있다.
load부터 검색해봄. exec가 나옴. 디버깅 문구를 찍어봄. 테케에 나와있는 것과는 달리 exec도 쓰이는 모양임. 어딘가에서 호출되는듯. 그럼 사실 이게 문제였다???
답지 무지성으로 베끼지 않겠다는 다짐이 무색하게 (어쩔 수 없긴 하니) exec과 process_exec를 통째로 베꼈지만, 역시 똑같은 오류를 내뱉음.
case SYS_FORK:
f->R.rax = fork(f->R.rdi, f);
break;
/* THREAD_NAME이라는 이름을 가진 현재 프로세스의 복제본인 새 프로세스를 만듭니다. */
pid_t fork(const char *thread_name, struct intr_frame *f)
{
return process_fork(thread_name, f);
}
결국 물어봤는데, 이게 문제였다고 함.
뭔가 이제 fork가 되는 것 같긴 함.
Executing 'fork-once':
(fork-once) begin
pid: 4
(fork-once) Parent: child exit status is 0
(fork-once) end
child: exit(0)
(fork-once) Parent: child exit status is 0
(fork-once) end
fork-once: exit(0)
exit(81) 나와야되는데 잘 안됐었는데
tid_t process_fork(const char *name, struct intr_frame *if_)
{
thread_current()->user_tf = if_;
/* Clone current thread to new thread.*/
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, thread_current());
sema_down(&find_child(tid)->child_sema);
fork가 완료되기 전까지 잠들어야 했음
Executing 'fork-once':
(fork-once) begin
(fork-once) child run
child: exit(81)
(fork-once) Parent: child exit status is -1
(fork-once) end
fork-once: exit(0)
Execution of 'fork-once' complete.
이거 추가해주니까 child: exit(81) 뜸
Executing 'fork-once':
(fork-once) begin
(fork-once) child run
child: exit(81)
(fork-once) Parent: child exit status is -1
(fork-once) end
fork-once: exit(0)
Execution of 'fork-once' complete.
근데 child exit status는 -1로 나옴
struct semaphore fork_sema;
struct semaphore free_sema;
struct semaphore wait_sema;
동기가 말해준 부모 지정 후 각 자식의 죽기 전 상황을 유서로 전달하는 방식은 너무 비효율적인 것 같고
다른 답지들에서도 안 그랬던 것 같아서 일단 다른 답지를 봄
struct semaphore fork_sema;
struct semaphore free_sema;
struct semaphore wait_sema;
여기까지 와서야 다른 답지들에서 봤던 sema 3종의 의미를 깨달음
Executing 'fork-once':
(fork-once) begin
(fork-once) child run
child: exit(81)
(fork-once) Parent: child exit status is 81
(fork-once) end
fork-once: exit(0)
Execution of 'fork-once' complete.
자식이 죽으면 반드시 dying_status를 돌려주기 위해서 죽기 전에 sema_down을 함.
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
fork는 다 통과 떴다.
int exec(char *file_name)
{
check_address(file_name);
// file_name의 길이를 구한다.
// strlen은 널 문자를 포함하지 않기 때문에 널 문자 포함을 위해 1을 더해준다.
int file_name_size = strlen(file_name) + 1;
// 새로운 페이지를 할당받고 0으로 초기화한다.(PAL_ZERO)
// 여기에 file_name을 복사할 것이다
char *fn_copy = palloc_get_page(PAL_ZERO);
if (fn_copy == NULL)
{
exit(-1);
}
// file_name 문자열을 file_name_size만큼 fn_copy에 복사한다
strlcpy(fn_copy, file_name, file_name_size);
// process_exec 호출, 여기서 인자 파싱 및 file load 등등이 일어난다.
// file 실행이 실패했다면 -1을 리턴한다.
if (process_exec(fn_copy) == -1)
{
return -1;
}
NOT_REACHED();
return 0;
}
무지성 복붙 하지 말자고 생각했는데 또 무지성 복붙해버리고 있다.
어쩔 수 없음 시간이 없음
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
pass tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
그랬더니 exec-missing, exec-read, wait-twice, wait-killed
이 네 개 빼고 exec, wait는 다 통과함.
/* 현재의 프로세스가 cmd_line에서 이름이 주어지는 실행가능한 프로세스로 변경됩니다. */
int exec(const char *file)
{
check_address(file);
char *fn_copy;
off_t size = strlen(file) + 1;
fn_copy = palloc_get_page(PAL_ZERO);
if (fn_copy == NULL)
return -1;
strlcpy(fn_copy, file, size);
int result = process_exec(fn_copy);
palloc_free_page(fn_copy);
if (result == -1)
{
exit(-1);
}
return result;
}
https://github.com/JunglePintOS/pintos-kaist/tree/master
이걸로 바꿔봐도 통과되는 개수는 똑같
/* 현재의 프로세스가 cmd_line에서 이름이 주어지는 실행가능한 프로세스로 변경됩니다. */
int exec(const char *file)
{
struct dir *dir = dir_open_root();
struct inode *inode = NULL;
if (dir != NULL)
dir_lookup(dir, file, &inode);
dir_close(dir);
if (inode == NULL)
return -1;
아무리 다른 답지들 봐도 이거 예외 처리를 안 해주길래
내 코드에 뭔가 이상이 있나?? 했는데
포기하고 그냥 filesys_open에 있는 예외처리 함수 그대로 때려박아버림
Acceptable output:
(exec-arg) begin
(exec-arg) I'm your father
(args) begin
(args) argc = 2
(args) argv[0] = 'child-args'
(args) argv[1] = 'childarg'
(args) argv[2] = null
(args) end
exec-arg: exit(0)
Differences in `diff -u' format:
(exec-arg) begin
(exec-arg) I'm your father
- (args) begin
- (args) argc = 2
- (args) argv[0] = 'child-args'
- (args) argv[1] = 'childarg'
- (args) argv[2] = null
- (args) end
+ (exec-arg) end
exec-arg: exit(0)
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/exec-boundary:exec-boundary -p tests/userprog/child-simple:child-simple -- -q -f run exec-boundary < /dev/null 2> tests/userprog/exec-boundary.errors > tests/userprog/exec-boundary.output
perl -I../.. ../../tests/userprog/exec-boundary.ck tests/userprog/exec-boundary tests/userprog/exec-boundary.result
pass tests/userprog/exec-boundary
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/exec-missing:exec-missing -- -q -f run exec-missing < /dev/null 2> tests/userprog/exec-missing.errors > tests/userprog/exec-missing.output
perl -I../.. ../../tests/userprog/exec-missing.ck tests/userprog/exec-missing tests/userprog/exec-missing.result
FAIL tests/userprog/exec-missing
Test output failed to match any acceptable form.
Acceptable output:
(exec-missing) begin
load: no-such-file: open failed
exec-missing: exit(-1)
Differences in `diff -u' format:
(exec-missing) begin
- load: no-such-file: open failed
- exec-missing: exit(-1)
+ (exec-missing) exec("no-such-file"): -1
+ (exec-missing) end
+ exec-missing: exit(0)
그랬더니 exec-args도 안되고 exec-missing도 뭔가가 뜨는 척 하면서 안됨.
그니까 load에서 open failed가 떠야 된다는 건데...
이건 filesys_open에서 알아서 걸러줘야 된다.
근데 왜??
fdt 문제인가?
https://github.com/whwogur/PintOS_Project3/tree/master/userprog
이거대로 fdt 관련 코드 갈아엎었더니 read는 통과함
근데 앞에 open 2개가 갑자기 터짐
아 근데 자꾸 복붙에만 의존하면 공부 안되는데 어떡하지
아무 생각도 안 떠올라서 그냥 다시 되돌렸다
결국 fdt가 문제라는 건 맞음
그리고 계속 느낀건데 무지성으로 베끼면 아무 의미도 없고
결국 어딘가에서 막힌다
진짜 막힐때 살짝 아이디어를 얻거나 부분만 베끼면 모르겠는데
전부 다 베끼려고 하면 의미 없는듯
시즌 5684535153번째 초심찾기
참고하던 깃허브 레포랑 블로그 다 닫았다
안되도 혼자서 가보자
못한다는거 인정하고 겸손하게 내 템포로 가자
추석도 있잖음
하나하나 정성들여가며 정독할 것
fdt 넣을 때 굳이 next_fd를 갖고 있는게 아니라 어차피
page단위로 할당받는걸로 바꿨으니까 파일 열 때 t->fd_idx++;로 바꾸기
메모리 풀 (Memory Pools)
"System memory is divided into two 'pools' called the kernel and user pools."
시스템 메모리는 두 개의 풀(pool)로 나뉩니다: 커널 풀과 유저 풀입니다.
커널 풀(Kernel Pool): 커널에서 사용하는 메모리를 관리하는 풀로, 시스템 동작을 유지하기 위해 커널이 사용할 메모리 공간입니다.
유저 풀(User Pool): 사용자 프로세스들이 사용하는 메모리를 관리하는 풀로, 사용자 프로그램이 실행될 때 할당되는 메모리 공간입니다.
유저 풀과 커널 풀의 역할
"The user pool is for user (virtual) memory pages, the kernel pool for everything else."
유저 풀(User Pool)은 사용자 가상 메모리 페이지를 할당하는 데 사용됩니다. 이 풀은 사용자 프로세스가 메모리를 요청할 때 해당 프로세스에게 페이지를 할당해줍니다.
커널 풀(Kernel Pool)은 그 외의 모든 것들, 즉 커널의 운영에 필요한 메모리를 할당합니다. 커널의 중요한 시스템 동작과 운영체제 자체를 유지하는데 필요한 메모리가 이 풀에서 할당됩니다.
메모리 풀 분리의 이유
"The idea here is that the kernel needs to have memory for its own operations even if user processes are swapping like mad."
이 시스템은 커널과 사용자 메모리를 분리하여, 커널의 안정성을 보장하는 것이 목적입니다.
사용자가 많은 메모리를 요구하고, 스왑(swap)이 자주 발생하는 상황에서도 커널은 자신의 운영에 필요한 메모리를 확보해야 합니다. 사용자 프로세스가 메모리 부족으로 인해 스왑이 많이 발생하더라도, 커널이 안정적으로 작동할 수 있게 하기 위해 두 개의 풀로 나누어 관리합니다.
메모리 분배 비율
"By default, half of system RAM is given to the kernel pool and half to the user pool."
기본적으로 시스템 메모리의 절반은 커널 풀에, 나머지 절반은 유저 풀에 할당됩니다.
이 비율은 데모나 테스트 목적으로 적절히 설정된 것으로, 실제 시스템에서는 커널이 필요로 하는 메모리는 상대적으로 적기 때문에, 커널 풀에 많은 메모리를 할당할 필요는 없습니다.
"That should be huge overkill for the kernel pool, but that's just fine for demonstration purposes."
커널 풀에 할당된 메모리는 실제로는 커널이 필요로 하는 것보다 훨씬 많은 양이지만, 데모나 교육적 목적으로는 충분한 메모리 할당이 적절하다는 설명입니다.
일단 대충 다 점검하긴 함
file = filesys_open(only_file_name);
load에서 file이 fail을 띄웠어야 했음.
struct file *
filesys_open(const char *name)
{
struct dir *dir = dir_open_root();
struct inode *inode = NULL;
if (dir != NULL)
dir_lookup(dir, name, &inode);
dir_close(dir);
if (inode == NULL)
return -1;
return file_open(inode);
}
그런 파일 없을 때 -1을 반환하는데,
if (file == NULL)
{
printf("load: %s: open failed\n", only_file_name);
goto done;
}
file == NULL로만 돼있어서 page fault나던 거였음.
file == -1 조건 추가.
load: no-such-file: open failed
Page fault at 0xb: not present error reading page in kernel context.
Interrupt 0x0e (#PF Page-Fault Exception) at rip=800421f17d
cr2=000000000000000b error= 0
rax ffffffffffffffff rbx 0000000000000000 rcx 00000080040b8000 rdx 0000000000000001
open failed가 뜨긴 하는데 여전히 page-fault 남.
if ((file != NULL) && (file != -1))
file_close(file);
load 맨 마지막에 있는 file_close에도 조건문 추가.
int result = process_exec(fn_copy);
// palloc_free_page(fn_copy);
중복으로 free를 하고 있어서 오류가 또 나고 있길래 exec()에 있는 free 하나 주석처리
이렇게 침착하게 하나하나 찾아가면 된다
자꾸 자기 자신을 못 믿고 안된다안된다 하면 더 안되고 아무것도 안된다
(exec-read) begin
(exec-read) open "sample.txt"
(exec-read) read "sample.txt" first 20 bytes
(child-read) begin
(child-read) open "sample.txt"
(child-read) open "sample.txt": FAILED
child-read: exit(1)
부모는 되는데 자식은 안됨
fdt 제대로 복사 안된듯?
열려고 했던 파일 이름: sample.txt
연 파일: 69455928
(exec-read) read "sample.txt" first 20 bytes
(child-read) begin
(child-read) open "sample.txt"
열려고 했던 파일 이름: sample.txt
연 파일: 69455960
찍어봤더니 파일이 복사되긴 한 거 같음.
그렇다면 읽기 위치도 초기화해줘야?
생각해보니 read를 아직 만져주지 않음
read 보니까 file의 pos 초기화 시켜줘야할듯
fork할 때 복사하는 과정에서 pos 초기화시켜주려다
struct file의 ->pos가 원하는대로 안되길래
헤더 파일에도 추가해봤는데 오류 생기길래 포기하고
file_duplicate에서부터 pos->0으로 초기화해줬는데
또 안됨
이거 문제는 아닐지도
snprintf(cmd_line, sizeof cmd_line, "%s %d", "child-read", handle);
exec(cmd_line);
아 이거 child-read 테케로 가버리는거구나
그래서 디버깅이 안 찍혔던거
ㅇㅋ 이놈이 문제였구나
exec 하고 나면 file descriptor가 뭔가 바뀌는 건 맞는듯.
??? 왜 fdt idx 0하고 1에 들어있는 건 1씩 증가하는데
2에 있는 건 똑같지?
그것은 파일을 추가하는 fd_idx가 process_cleanup();
에 의해서 2로 초기화가 되었기 때문이죠~
이걸 저장하고 있다가 다시 돌려주면 될듯. fdt는 어차피 포인터로 갖고 있던 거라 상관이 없,나? 일단 혹시 모르니 전달시켜주자. 이론상 전달시켜주는게 맞긴 한듯.
아니 분명 exec 하기 전의 fd_idx인데 왜 갑자기 2로 리셋돼있는거임;
fd가 아니라 확실하게 전달되는 fdt로 하는게 맞을듯.
파일 디스크립터 추가할 때 파일이 해당 fd에 이미 있으면 일단 위로 올린다.
아무리 생각해도 비효율적이지만 쩔수.
완료.
부모가 sema_down해버리고 자버리는 현상
내일 해결하려고 했는데 같이 가려던 동기가 이상하다고 짚어줘서 해결됨
list_remove(&child->child_elem);
elem을 child_elem으로 바꿈
오늘은 여기까지
pass tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
FAIL tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
FAIL tests/userprog/bad-read
FAIL tests/userprog/bad-write
FAIL tests/userprog/bad-read2
FAIL tests/userprog/bad-write2
FAIL tests/userprog/bad-jump
FAIL tests/userprog/bad-jump2
pass tests/filesys/base/lg-create
pass tests/filesys/base/lg-full
FAIL tests/filesys/base/lg-random
FAIL tests/filesys/base/lg-seq-block
FAIL tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
pass tests/filesys/base/sm-full
FAIL tests/filesys/base/sm-random
FAIL tests/filesys/base/sm-seq-block
FAIL tests/filesys/base/sm-seq-random
pass tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
FAIL tests/userprog/no-vm/multi-oom