오늘 월요일인데 벌써 거의 다 한 동기(이 친구 답지도 안 봄)가 있길래 어떻게 했는지 복기해달라고 함
복기 좀 해달라고 해서 다 들어봤는데 요약하면 단서를 잡고 해결책을 찾아가는 과정의 반복.
내가 했던 쓸모없는 짓
기록의 의미가 있긴 함. 내가 나중에 보고 복습할 수 있음.
근데 "의미있는" 기록을 알차게 남겨야 함.
딱히 의미 없는 것 같으면 기록 전부 다 거르기.
텍스트를 정독하는 능력 : 대충대충 읽지 말아야 함. 하나하나 꼼꼼히. 마음 속에 조급함이 있어서?
해당 문제에 집중하는 능력 : 내가 이것을 왜 해야되는지에 대한 동기부여?
어떤 것을 읽고 대충 말고 완벽히 이해하는 연습을 꾸준히 하면 됨. 지금까지의 인생은 어떻게든 미뤄왔으니 어쩔 수 없고, 지금부터라도 뇌의 구조를 바꾸려고 노력하면 됨.
흐리멍텅하게 생각하면 안된다는 이미지 각인
느리보이고 답답하더라도 필요한 것을 천천히 정독해야 오히려 빠르고 멀리 갈 수 있다.
파일 생성:
파일 접근:
파일 삭제:
스택: 함수 내에서 선언된 로컬 배열
힙: malloc() 같은 동적 할당 함수로 생성된 배열
데이터 영역: 전역 또는 정적 변수로 선언된 배열
페이지 테이블 엔트리(Page Table Entry, PTE)는 컴퓨터 운영 체제에서 가상 메모리를 물리 메모리로 매핑하는 중요한 역할을 합니다. 운영 체제는 페이지 테이블을 통해 가상 주소 공간과 실제 물리 메모리 주소를 연결하는데, 페이지 테이블 엔트리는 그 중에서 개별 페이지(일정 크기의 메모리 블록)의 매핑 정보를 담고 있는 항목입니다.
각 페이지 테이블 엔트리는 다음과 같은 정보를 포함할 수 있습니다:
filesys.c에 있는 함수 중 file과 직접적으로 관련된 함수는 filesys_open 하나
그것은 파일의 이름이 인자로 주어지면 directory.c를 이용해서 inode를 찾은 뒤
그 inode를 인자로 넘겨주며 file.c에 있는 file_open()을 호출한다.
그러면 file 구조체가 동적 할당되며 초기화된다.
해당 파일 구조체 자체가 반환됨.
지금 내가 해야될거: 파일을 열 때 파일 디스크립터 테이블에 파일 구조체를 할당한다
int find_next_fd(struct thread *t)
{
struct file **fdt = t->fdt;
for (int i = 2; i < 64; i++)
{
if (fdt[i] == NULL)
{
t->next_fd = i;
return i;
}
}
return -1; // 64개나 채울 일이? 일단 처리는 해둠.
}
/* Opens a file for the given INODE, of which it takes ownership,
* and returns the new file. Returns a null pointer if an
* allocation fails or if INODE is null. */
struct file *
file_open(struct inode *inode)
{
struct file *file = calloc(1, sizeof *file);
if (inode != NULL && file != NULL)
{
file->inode = inode;
file->pos = 0;
file->deny_write = false;
// 파일 디스크립터 테이블에 해당 파일을 추가해야 함
struct thread *t = thread_current();
if (t->fdt != NULL)
{
struct file **fdt = t->fdt;
fdt[t->next_fd] = file;
find_next_fd(t);
}
return file;
}
else
{
inode_close(inode);
free(file);
return NULL;
}
}
이론상 되어야 하긴 함
되는듯?
이제 해야될 거 : 파일을 닫을 때 파일 디스크립터에서 해당 파일 구조체를 지운다.
그래서 close 구현하려고 봤더니 애초에 open에서부터 연 파일의 fd를 반환해야함.
그래야 close의 인자로 open의 반환값 fd가 전해짐.
그런데 file_open과 filesys_open은 int가 아니라 struct *file을 반환함.
이렇게 되면 내가 어떤 fd에 file을 할당했는지 알 수 없음.
즉, file_open과 filesys_open에서 fd를 할당하는 작업을 처리하는게 아니라
그 두 함수를 호출하는 open() 시스템 콜에서 fd를 할당한 뒤
할당한 fd를 반환해야 함.
int find_next_fd(struct thread *t)
{
struct file **fdt = t->fdt;
for (int i = 2; i < 64; i++)
{
if (fdt[i] == NULL)
{
t->next_fd = i;
printf("할당된 fd: %d", i);
return i;
}
}
return -1; // 64개나 채울 일이? 일단 처리는 해둠.
}
/* file(첫 번째 인자)이라는 이름을 가진 파일을 엽니다. */
int open(const char *file)
{
if (file == NULL || pml4_get_page(thread_current()->pml4, file) == NULL || !is_user_vaddr(file))
exit(-1);
if (strlen(file) == 0)
return -1;
struct file *real_file = filesys_open(file);
// 파일 디스크립터 테이블에 해당 파일을 추가해야 함
struct thread *t = thread_current();
if (t->fdt != NULL)
{
struct file **fdt = t->fdt;
fdt[t->next_fd] = real_file;
find_next_fd(t);
}
return;
}
위치 옮김. 역시 잘 됨.
일단 close 구현하러 감.
fdt 완성하려면 close도 건드려야 됨.
/* 파일 식별자 fd를 닫습니다. */
void close(int fd)
{
// 파일 디스크립터 테이블에서 fd에 할당된 file 구조체를 찾은 뒤에 일단 저장한 뒤
// 해당 file 구조체를 테이블에서 삭제하고
// file 구조체로 file_close 호출
struct thread *t = thread_current();
struct file **fdt = t->fdt;
struct file *file = fdt[fd]; // fd에 할당된 file 구조체
fdt[fd] = NULL; // 테이블에서 삭제
file_close(file);
}
이렇게 짜고 테스트 돌려봄
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-normal:close-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run close-normal < /dev/null 2> tests/userprog/close-normal.errors > tests/userprog/close-normal.output
perl -I../.. ../../tests/userprog/close-normal.ck tests/userprog/close-normal tests/userprog/close-normal.result
FAIL tests/userprog/close-normal
Run started 'close-normal' but it never finished
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-twice:close-twice -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run close-twice < /dev/null 2> tests/userprog/close-twice.errors > tests/userprog/close-twice.output
perl -I../.. ../../tests/userprog/close-twice.ck tests/userprog/close-twice tests/userprog/close-twice.result
FAIL tests/userprog/close-twice
Run started 'close-twice' but it never finished
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-bad-fd:close-bad-fd -- -q -f run close-bad-fd < /dev/null 2> tests/userprog/close-bad-fd.errors > tests/userprog/close-bad-fd.output
perl -I../.. ../../tests/userprog/close-bad-fd.ck tests/userprog/close-bad-fd tests/userprog/close-bad-fd.result
FAIL tests/userprog/close-bad-fd
Run started 'close-bad-fd' but it never finished
통과를 아무것도 못하는 것까지는 그렇다 치는데
perl -I../.. ../../tests/userprog/open-missing.ck tests/userprog/open-missing tests/userprog/open-missing.result
FAIL tests/userprog/open-missing
run: open() returned 2: FAILED
이것도 망가져버림.
예외처리해서 open-missing 다시 통과.
좀 ad-hoc인가 싶긴 하지만 방법이 없음.
Executing 'close-normal':
(close-normal) begin
(close-normal) open "sample.txt"
(close-normal) close "sample.txt"
Timer: 314 ticks
그나저나 Run started 'close-normal' but it never finished
이건 왜일까?
결과를 봐도 확실히 exit()가 없다.
할당 해제가 안돼서 그런가? 어제 했는데?
case SYS_CLOSE:
halt();
break;
ㅋㅋㅋㅋㅋㅋㅋ 시스템 콜 핸들러에 close()를 안 써놔서 그랬던 거였음.
고쳤더니 정상 작동. twice까지도 통과하는데 왜 그런건진 모르겠.
어딘가에서 예외 처리에 걸리나봄.
/* 파일 식별자 fd를 닫습니다. */
void close(int fd)
{
if (fd < 0 || 64 <= fd)
return;
간단한 보호 구문 추가
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-normal:close-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run close-normal < /dev/null 2> tests/userprog/close-normal.errors > tests/userprog/close-normal.output
perl -I../.. ../../tests/userprog/close-normal.ck tests/userprog/close-normal tests/userprog/close-normal.result
pass tests/userprog/close-normal
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-twice:close-twice -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run close-twice < /dev/null 2> tests/userprog/close-twice.errors > tests/userprog/close-twice.output
perl -I../.. ../../tests/userprog/close-twice.ck tests/userprog/close-twice tests/userprog/close-twice.result
pass tests/userprog/close-twice
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/close-bad-fd:close-bad-fd -- -q -f run close-bad-fd < /dev/null 2> tests/userprog/close-bad-fd.errors > tests/userprog/close-bad-fd.output
perl -I../.. ../../tests/userprog/close-bad-fd.ck tests/userprog/close-bad-fd tests/userprog/close-bad-fd.result
pass tests/userprog/close-bad-fd
close 3종 전부 통과
void thread_exit(void)
{
ASSERT(!intr_context());
#ifdef USERPROG
// 모든 파일 close 해줘야됨
struct file **fdt = thread_current()->fdt;
for (int i = 2; i < 64; i++)
{
if (fdt[i] != NULL)
{
file_close(fdt[i]);
}
}
free(thread_current()->fdt);
process_exit();
#endif
저번에 안해줬던거 해줌.
터지진 않는데, 이거 한다고 뭐가 달라지진 않음.
그냥 requirement라서 해준거.
Reads size bytes from the file open as fd into buffer. Returns the number of bytes actually read (0 at end of file), or -1 if the file could not be read (due to a condition other than end of file). fd 0 reads from the keyboard using input_getc().
깃북 번역본에 fd 0일땐 input_getc() 써야된다는 거 빠져있었다.
https://nullbyte.tistory.com/49
여기 보고서야 다시 확인함.
그와 별개로
/* Reads SIZE bytes from FILE into BUFFER,
* starting at the file's current position.
* Returns the number of bytes actually read,
* which may be less than SIZE if end of file is reached.
* Advances FILE's position by the number of bytes read. */
off_t file_read(struct file *file, void *buffer, off_t size)
{
off_t bytes_read = inode_read_at(file->inode, buffer, size, file->pos);
file->pos += bytes_read;
return bytes_read;
}
read 자체는 file_read 쓰면 끝인듯.
/* fd로 열린 파일에서 size 바이트만큼 읽어서 buffer에 넣는다 */
int read(int fd, void *buffer, unsigned length)
{
// fd에서 file 꺼내와서 file_read에 넣어준다
struct file *file;
file = thread_current()->fdt[fd];
file_read(file, buffer, length);
}
일단 대충 만들어봤는데
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-normal:read-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run read-normal < /dev/null 2> tests/userprog/read-normal.errors > tests/userprog/read-normal.output
perl -I../.. ../../tests/userprog/read-normal.ck tests/userprog/read-normal tests/userprog/read-normal.result
FAIL tests/userprog/read-normal
Run started 'read-normal' but it never finished
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-bad-ptr:read-bad-ptr -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run read-bad-ptr < /dev/null 2> tests/userprog/read-bad-ptr.errors > tests/userprog/read-bad-ptr.output
perl -I../.. ../../tests/userprog/read-bad-ptr.ck tests/userprog/read-bad-ptr tests/userprog/read-bad-ptr.result
FAIL tests/userprog/read-bad-ptr
Kernel panic in run: PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x800421847f 0x800421cd91 0x800421cf10 0x8004208fbf 0x80042093dd 0x800421fb59 0x800421eade 0x800421d522 0x800421d185 0x800421cf85 0x400160 0x4001d6 0x400c77
Translation of call stack:
0x000000800421847f: debug_panic (lib/kernel/debug.c:32)
0x000000800421cd91: kill (userprog/exception.c:103)
0x000000800421cf10: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208fbf: intr_handler (threads/interrupt.c:352)
0x00000080042093dd: intr_entry (threads/intr-stubs.o:?)
0x000000800421fb59: inode_read_at (filesys/inode.c:219)
0x000000800421eade: file_read (filesys/file.c:86)
0x000000800421d522: read (userprog/syscall.c:240)
0x000000800421d185: syscall_handler (userprog/syscall.c:120)
0x000000800421cf85: no_sti (userprog/syscall-entry.o:?)
0x0000000000400160: (unknown)
0x00000000004001d6: (unknown)
0x0000000000400c77: (unknown)
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-boundary:read-boundary -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run read-boundary < /dev/null 2> tests/userprog/read-boundary.errors > tests/userprog/read-boundary.output
perl -I../.. ../../tests/userprog/read-boundary.ck tests/userprog/read-boundary tests/userprog/read-boundary.result
pass tests/userprog/read-boundary
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-zero:read-zero -p ../../tests/userprog/sample.txt:sample.txt -- -q -f run read-zero < /dev/null 2> tests/userprog/read-zero.errors > tests/userprog/read-zero.output
perl -I../.. ../../tests/userprog/read-zero.ck tests/userprog/read-zero tests/userprog/read-zero.result
pass tests/userprog/read-zero
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-stdout:read-stdout -- -q -f run read-stdout < /dev/null 2> tests/userprog/read-stdout.errors > tests/userprog/read-stdout.output
perl -I../.. ../../tests/userprog/read-stdout.ck tests/userprog/read-stdout tests/userprog/read-stdout.result
FAIL tests/userprog/read-stdout
Kernel panic in run: PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x800421847f 0x800421cd91 0x800421cf10 0x8004208fbf 0x80042093dd 0x800421d522 0x800421d185 0x800421cf85 0x40010d 0x40016b 0x400c0c
Translation of call stack:
0x000000800421847f: debug_panic (lib/kernel/debug.c:32)
0x000000800421cd91: kill (userprog/exception.c:103)
0x000000800421cf10: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208fbf: intr_handler (threads/interrupt.c:352)
0x00000080042093dd: intr_entry (threads/intr-stubs.o:?)
0x000000800421d522: read (userprog/syscall.c:240)
0x000000800421d185: syscall_handler (userprog/syscall.c:120)
0x000000800421cf85: no_sti (userprog/syscall-entry.o:?)
0x000000000040010d: (unknown)
0x000000000040016b: (unknown)
0x0000000000400c0c: (unknown)
pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/read-bad-fd:read-bad-fd -- -q -f run read-bad-fd < /dev/null 2> tests/userprog/read-bad-fd.errors > tests/userprog/read-bad-fd.output
perl -I../.. ../../tests/userprog/read-bad-fd.ck tests/userprog/read-bad-fd tests/userprog/read-bad-fd.result
FAIL tests/userprog/read-bad-fd
Kernel panic in run: PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x800421847f 0x800421cd91 0x800421cf10 0x8004208fbf 0x80042093dd 0x800421d185 0x800421cf85 0x40010d 0x400219 0x400cba
Translation of call stack:
0x000000800421847f: debug_panic (lib/kernel/debug.c:32)
0x000000800421cd91: kill (userprog/exception.c:103)
0x000000800421cf10: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208fbf: intr_handler (threads/interrupt.c:352)
0x00000080042093dd: intr_entry (threads/intr-stubs.o:?)
0x000000800421d185: syscall_handler (userprog/syscall.c:120)
0x000000800421cf85: no_sti (userprog/syscall-entry.o:?)
0x000000000040010d: (unknown)
0x0000000000400219: (unknown)
0x0000000000400cba: (unknown)
살짝 난해한 결과가 나왔다.
read-normal: 끝나지를 않는다고 함. 확인해보니 다른 테케들은 read를 직접 호출하는데 이것만 check_file을 호출함. 그 안에서 read가 호출되는 느낌.
read-boundary, read-zero: 이건 또 왜 통과함? normal은 안되면서?
read-bad-ptr, read-stdout, read-bad-fd: 페이지 폴트 나면서 터짐. 이건 보호구문만 처리하면 되겠지?
int read(int fd, void *buffer, unsigned length)
{
if (fd < 0 || 64 <= fd || !is_user_vaddr(buffer))
exit(-1);
// fd에서 file 꺼내와서 file_read에 넣어준다
struct file *file;
file = thread_current()->fdt[fd];
return file_read(file, buffer, length);
}
bad-fd는 통과
/* fd로 열린 파일에서 size 바이트만큼 읽어서 buffer에 넣는다 */
int read(int fd, void *buffer, unsigned length)
{
if (fd < 0 || 64 <= fd || pml4_get_page(thread_current()->pml4, buffer) == NULL || !is_user_vaddr(buffer))
exit(-1);
// fd에서 file 꺼내와서 file_read에 넣어준다
struct file *file;
file = thread_current()->fdt[fd];
return file_read(file, buffer, length);
}
앞에서 했던 것처럼
page 유효성 검사도 해줘야 함
그랬더니 통과
/* Try reading from fd 1 (stdout),
which may just fail or terminate the process with -1 exit
code. */
어떻게 해야되지? stdout이면 1인데 왜 깃북에선 stdin의 fd인 0을 얘기하고 있는거지? 이러다가
테스트 파일 보니까 주석에 "이거 터트려야돼요" 라고 함.
if (fd < 0 || 64 <= fd || fd == 1 || pml4_get_page(thread_current()->pml4, buffer) == NULL || !is_user_vaddr(buffer))
exit(-1);
fd==1 조건 추가해서 통과
normal은 lib.c에 있는 check_file()을 사용함. 디버깅으로 문제 좁혀봤더니 여기가 문제.
ctrl 좌클릭해도 안 들어가지길래 검색해봤더니 내가 그냥 구현을 안해서 그런거였음
/* fd(첫 번째 인자)로서 열려 있는 파일의 크기가 몇 바이트인지 반환합니다. */
int filesize(int fd)
{
struct file *file = thread_current()->fdt[fd];
return file_length(file);
}
구현했더니 통과
/* buffer로부터 open file fd로 size 바이트를 적어줍니다. */
int write(int fd, const void *buffer, unsigned length)
{
if (fd < 0 || 64 <= fd || fd == 0 || pml4_get_page(thread_current()->pml4, buffer) == NULL || !is_user_vaddr(buffer))
exit(-1);
// strlcpy(fd, buffer, length);
if (fd == 1)
{
putbuf(buffer, length);
return length;
}
struct file *file = thread_current()->fdt[fd];
return file_write_at(file, buffer, length, 0);
}
가볍게 다 통과
자식 프로세스는 fork()를 호출한 부분에서부터 시작
fprintf : 파일에 데이터 쓰기
자식 프로세스는 자신의 주소 공간, 레지스터, PC 값을 가짐
프로그램 카운터(Program counter, PC) : 프로세서 내부에 있는 레지스터 중의 하나, 다음에 실행될 명령어의 주소를 가지고 있음
fork로부터 부모는 자식의 PID 반환받고, 자식은 0 반환 받음.
단일 cpu 시스템에선 부모 자식 둘 중 하나 실행됨
exec()는 현재 실행 중 프로세스의 코드 세그멘트와 정적 데이터 부분을 덮어 씀. 힙과 스택들도 초기화됨. 그런 다음 argv 같은 인자를 전달해서 프로그램 실행시킴.
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->user_tf;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy(&if_, parent_if, sizeof(struct intr_frame));
/* 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.*/
// 파일 식별자 복사
for (int i = 2; i < 64; i++)
current->fdt[i] = file_duplicate(parent->fdt[i]);
process_init();
// 부모 리스트에 자식 추가
list_push_back(&parent->child_list, ¤t->child_elem);
/* Finally, switch to the newly created process. */
if (succ)
do_iret(&if_);
error:
thread_exit();
}
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 = false;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
if (is_kern_pte(pte))
return false;
/* 2. Resolve VA from the parent's page map level 4. */
parent_page = pml4_get_page(parent->pml4, va);
/* 3. TODO: Allocate new PAL_USER page for the child and set result to
* TODO: NEWPAGE. */
newpage = palloc_get_page(PAL_USER);
/* 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). */
if ((*pte & PTE_W) == PTE_W)
writable = true;
if (writable)
memcpy(newpage, parent_page, PGSIZE);
/* 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. */
pml4_destroy(current->pml4);
return false;
}
return true;
}
일단 하긴 했는데 wait 구현 전엔 통과 못한다고 함.