크래프톤 정글 TIL : 0907

lazyArtisan·2024년 9월 7일
0

정글 TIL

목록 보기
69/147

📚 Journal


그래도 테스트를 돌릴 수 있게 된 후부터는 속도가 붙었다. 어디에서 문제가 되는지 테스트의 출력으로부터 거슬러 올라가는 것으로 확실히 알 수가 있어서 fail이 뜨는 테스트로부터 문제가 되는 부분을 하나하나 고치는 것으로 빠르게 구현을 할 수 있었던 것 같다.

https://uneducatedjungler.tistory.com/148

이 말대로 테스트 돌릴 수 있게 되니까 숨통이 트임.
이제야 과제다운 과제가 된 느낌임.



📝 배운 것들


🏷️ 인터럽트 프레임 관련 구조체 (pintos)

interrupt.h에 있는 struct gp_registersstruct intr_frame이 뭔지 몰라서 gpt에 물어봄. 구글링도 해보려고 했는데 검색 결과가 안 나옴. 레지스터 + 핀토스 개념 섞인듯.

일단 이 둘은 인터럽트 스택 프레임과 관련된 구조체들임.
인터럽트가 발생하면 CPU는 현재 작업 상태를 저장해야 함.
이를 위해 스택에 레지스터 값과 기타 정보를 저장해야 함.
인터럽트 후, 해당 정보는 복구되어 원래 상태로 돌아감.

struct gp_registers

struct gp_registers는 CPU의 범용 레지스터를 저장함.

  • r15, r14, r13, r12, r11, r10, r9, r8: 범용 레지스터로, 다양한 연산에 사용됩니다. 프로그램이 연산을 수행하는 동안 임시 데이터를 저장합니다.
  • rsi (Source Index): 메모리에서 데이터를 읽을 때 주로 사용되는 레지스터.
  • rdi (Destination Index): 메모리에서 데이터를 쓸 때 사용되는 레지스터.
  • rbp (Base Pointer): 스택의 기준 포인터. 스택 프레임의 기준 주소를 저장합니다.
  • rdx (Data Register): 보통 곱셈, 나눗셈 연산에서 사용되며 함수 호출 시 인자를 전달할 때도 쓰입니다.
  • rcx (Count Register): 반복 연산의 카운터로 주로 사용됩니다.
  • rbx (Base Register): 함수 호출 간 값이 유지되는 레지스터.
  • rax (Accumulator Register): 연산 결과나 시스템 콜에서 리턴 값을 저장하는 레지스터.

이런 것들을 저장하고 있음.

struct intr_frame

struct intr_frame인터럽트 처리 시 스택에 저장되는 상태 정보를 담고 있음.

struct gp_registers R

  • R: 앞서 설명한 범용 레지스터들의 모음입니다. 이들은 인터럽트가 발생할 때 저장되어야 하며, 이후 다시 복구됩니다.

세그먼트 레지스터 및 패딩

  • es, ds: 이들은 세그먼트 레지스터로, CPU가 메모리에 접근할 때 세그먼트를 관리합니다. 현대의 64비트 시스템에서는 세그먼트 레지스터가 많이 사용되지 않지만, 여전히 존재합니다.
  • __pad1, __pad2, __pad3: 구조체의 필드를 64비트 경계로 정렬하기 위한 패딩입니다.

인터럽트 벡터 및 에러 코드

  • vec_no: 인터럽트 벡터 번호로, 어떤 인터럽트가 발생했는지를 나타냅니다. 예를 들어, 타이머 인터럽트, 키보드 인터럽트, 시스템 콜 등이 각각 고유한 벡터 번호를 가집니다.
  • error_code: 일부 예외(예: 페이지 폴트)에서는 에러 코드가 발생하며, 인터럽트 처리기에게 더 자세한 정보를 제공합니다.

프로그램 카운터 및 코드 세그먼트

  • rip (Instruction Pointer): 인터럽트가 발생했을 때 실행 중이던 명령어의 주소를 저장합니다. 인터럽트 후 복구될 때, 이 주소로 다시 점프하여 중단된 명령어부터 실행을 이어나갑니다.
  • cs (Code Segment): 코드 세그먼트 레지스터로, 현재 실행 중인 코드가 속한 세그먼트를 가리킵니다. 이 레지스터는 현재 코드의 위치를 확인하는 데 사용됩니다.

플래그 및 스택 포인터

  • eflags: 플래그 레지스터로, CPU의 상태를 나타내는 여러 비트를 포함합니다. 예를 들어, 산술 연산의 결과에 따른 플래그(Zero Flag, Carry Flag)나 인터럽트 허용 여부를 나타내는 플래그가 있습니다.
  • rsp (Stack Pointer): 스택 포인터로, 현재 스택의 위치를 가리킵니다. 인터럽트가 발생하면 현재 스택 포인터가 저장됩니다.
  • ss (Stack Segment): 스택 세그먼트 레지스터로, 스택이 속한 세그먼트를 가리킵니다. 64비트 시스템에서는 거의 사용되지 않지만 여전히 존재합니다.

__attribute__((packed))

이 속성은 구조체의 메모리 정렬 방식을 지정합니다. packed를 사용하면 컴파일러가 패딩 없이 구조체의 필드를 메모리에 연속적으로 배치하도록 합니다. 이는 메모리 효율성을 높이기 위해 사용되지만, 성능 저하가 있을 수 있습니다. 구조체 필드 간에 여분의 패딩을 넣지 않도록 보장하여 스택의 정확한 형식을 유지하는 데 중요합니다.



🖥️ PintOS


🔷 Argument passing

어제 도움 받아서 완성한 코드

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;

	char *save_ptr;
	char *only_file_name;

	only_file_name = strtok_r(file_name, " ", &save_ptr);
	printf("filename : %s\n", only_file_name);

	/* 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(only_file_name);
	// file = filesys_open(file_name);
	if (file == NULL)
	{
		printf("load: %s: open failed\n", only_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", only_file_name);
		goto done;
	}

	/* Read program headers. */
	file_ofs = ehdr.e_phoff;
	for (i = 0; i < ehdr.e_phnum; i++)
	{
		(생략)
	}

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

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

	/* TODO: Your code goes here.
	 * TODO: Implement argument passing (see project2/argument_passing.html). */

	// rsp가 setup_stack에서 페이지까지 할당됨 ㅇㅋ 이제 쓸 수 있음
	// 유저 스택에 넣으려면 rsp에 넣어야 됨. string.c 보셈
	// 넣어야될 거 strlen()만큼 내려가서 strlcpy()를 하셈

	// 규약 더 남음. 패딩 추가. 했음. 0 추가. 했음.
	// 주소값들 추가.
	// fake return 추가

	char *token;
	// char *if_->rsp = if_->rsp; // 유저 스택 맨 꼭대기
	char *address[50];
	int ptr_cnt = 0; // 포인터 배열의 포인터 올릴 때 사용하는거

	if_->rsp -= strlen(only_file_name) + 1;
	// only_file_name += '\0';
	strlcpy(if_->rsp, only_file_name, strlen(only_file_name) + 1);
	// memcpy();
	address[ptr_cnt] = if_->rsp;
	ptr_cnt++;

	for (token = strtok_r(NULL, " ", &save_ptr); token != NULL;
		 token = strtok_r(NULL, " ", &save_ptr)) // 시작을 NULL로 바꿔줘야 하는 이유
	{
		if_->rsp -= strlen(token) + 1;				 // 내리고
		strlcpy(if_->rsp, token, strlen(token) + 1); // 채워주고
		// strlcat(address + ptr_cnt, if_->rsp, 8); // 어디에 넣었는지 포인터 배열에 저장해두기
		// ptr_cnt += 8;						// 포인터 배열 다음 곳으로
		address[ptr_cnt] = if_->rsp; // 포인터 배열에 주소 저장
		ptr_cnt++;
	}

	// dst = (char *)((uintptr_t)dst - (8 - ((uintptr_t)dst % 8))); // dst -= 8 - ((int)(dst % 8)); 이거 던졌더니 gpt가 짜줌

	// 패딩 추가
	while ((uintptr_t)if_->rsp % 8 != 0)
	{
		if_->rsp--;
		*(uint8_t *)if_->rsp = 0; // 패딩으로 0 추가
	}

	// 0 추가
	if_->rsp -= 8;
	*(char **)if_->rsp = NULL;
	// memset()
	// strlcpy(dst, 0, 8);

	// 유저 스택에 주소값들 추가
	// for (token = strtok_r(address, " ", &save_ptr); token != NULL;
	// 	 token = strtok_r(NULL, " ", &save_ptr))
	// {
	// 	dst -= strlen(token);
	// 	strlcpy(dst, token, strlen(token));
	// }
	for (int i = ptr_cnt - 1; i >= 0; i--)
	{
		if_->rsp -= 8;
		// memcpy / strlcpy
		strlcpy(if_->rsp, &address[i], strlen(address[i])); // 주소값을 유저 스택에 추가
															// *dst = address[i]; // 주소값을 유저 스택에 추가
	}

	// %rsi = argc , %rdi = argv
	if_->R.rdi = if_->rsp;
	if_->R.rsi = ptr_cnt;

	// fake return 추가
	if_->rsp -= 8;
	*(char **)if_->rsp = NULL;
	// 다른 방식으로 null pointer 넣기

	// strlcpy(dst, 0, 8);

	success = true;

done:
	/* We arrive here whether the load is successful or not. */
	file_close(file);
	return success;
}

어떤 느낌인지 전부 설명받고 대충 짜보긴 했는데
당연히 버그 생기고 안 돌아가서

동기 두 명이 달라붙어서 도와줌.

메인에서 프로세스 실행이 안돼
그래서 명령줄 파싱해서 유저 스택에 넣어줘야돼
그건 호출 규약에 따라 넣어야돼

를 구현한 것

int process_wait(tid_t child_tid UNUSED)
{
	/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
	 * XXX:       to add infinite loop here before
	 * XXX:       implementing the process_wait. */
	int i = 0;
	while (i <= 1 << 29)
	{
		i++;
	}
	// while (1)
	// {
	// }

	return -1;
}

process_exec에 hex_dump로 정답 확인

Executing 'args-single onearg':
000000004747ffc0                          00 00 00 00 00 00 00 00 |        ........|
000000004747ffd0  f4 ff 47 47 00 00 00 00-ed ff 47 47 00 00 00 00 |..GG......GG....|
000000004747ffe0  00 00 00 00 00 00 00 00-00 00 00 00 00 6f 6e 65 |.............one|
000000004747fff0  61 72 67 00 61 72 67 73-2d 73 69 6e 67 6c 65 00 |arg.args-single.|

복기 하고는 싶은데 시스템 콜이 급함

(근데 이거 틀린 코드였음. 이유는 아래에 적음.)


🔷 System Calls

시스템 콜 프로토타입 복붙하고 깃북에서 설명 복붙해서 주석 담

syscall_nr.h 찾음 (이걸로 switch문)

이거 계속 필요함.

인자
1 : rdi
2 : rsi
3 : rdx
4 : r10
5 : r8
6 : r9

exit

강의 영상 정리본 보니까 void thread_exit(void) NO_RETURN; 를 쓰라고 해서
들어가봤더니

#ifdef USERPROG
	process_exit();
#endif

이렇게 정리돼있음

/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
	struct thread *curr = thread_current();
	/* TODO: Your code goes here.
	 * TODO: Implement process termination message (see
	 * TODO: project2/process_termination.html).
	 * TODO: We recommend you to implement process resource cleanup here. */

	process_cleanup();
}

여기까진 왔는데 이제 또 도저히 모르겠어서 물어보러 감

커널이 쓰는 시스템 콜과 유저가 쓰는 시스템 콜이 따로 있다
lib/user/syscall.c에는 유저가 쓰는 시스템 콜 있음

void
exit (int status) {
	syscall1 (SYS_EXIT, status);
	NOT_REACHED ();
}
static __inline int64_t syscall (uint64_t num_, uint64_t a1_, uint64_t a2_,
		uint64_t a3_, uint64_t a4_, uint64_t a5_, uint64_t a6_) {
	int64_t ret;
	register uint64_t *num asm ("rax") = (uint64_t *) num_;
	register uint64_t *a1 asm ("rdi") = (uint64_t *) a1_;
	register uint64_t *a2 asm ("rsi") = (uint64_t *) a2_;
	register uint64_t *a3 asm ("rdx") = (uint64_t *) a3_;
	register uint64_t *a4 asm ("r10") = (uint64_t *) a4_;
	register uint64_t *a5 asm ("r8") = (uint64_t *) a5_;
	register uint64_t *a6 asm ("r9") = (uint64_t *) a6_;

status 가져올 때 rdi를 이용해야 하는 이유

// 현재 동작중인 유저 프로그램을 종료합니다.
void exit(int status)
{
	thread_current()->status = THREAD_DYING;
	// thread_current()->tf.R.rdi = status;
	printf("exit(%d)", status);
	thread_exit();
}

일단 하긴 했음

perl -I../.. ../../tests/userprog/exit.ck tests/userprog/exit tests/userprog/exit.result
FAIL tests/userprog/exit
Test output failed to match any acceptable form.

Acceptable output:
  (exit) begin
  exit: exit(57)
Differences in `diff -u' format:
- (exit) begin
- exit: exit(57)
+ filename : exit
+ Page fault at 0x1: not present error reading page in user context.
+ exit: dying due to interrupt 0x0e (#PF Page-Fault Exception).
+ Interrupt 0x0e (#PF Page-Fault Exception) at rip=400110
+  cr2=0000000000000001 error=               4
+ rax 0000000000000001 rbx 0000000000000000 rcx 0000000000000000 rdx 0000000000000001
+ rsp 000000004747ffa8 rbp 000000004747ffb8 rsi 0000000000000001 rdi 000000004747ffe8
+ rip 0000000000400110 r8 0000000000000000  r9 0000000000000000 r10 0000000000000000
+ r11 0000000000000000 r12 0000000000000000 r13 0000000000000000 r14 0000000000000000
+ r15 0000000000000000 rflags 00000202
+ es: 001b ds: 001b cs: 0023 ss: 001b

작동 안되는 것 같긴 함
근데 일단 다음 진행

// 현재 동작중인 유저 프로그램을 종료합니다.
void exit(int status)
{
	thread_current()->dying_status = status;
	// printf("exit(%d)", status);
	thread_exit();
}
/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
	struct thread *curr = thread_current();

	/* TODO: Your code goes here.
	 * TODO: Implement process termination message (see
	 * TODO: project2/process_termination.html).
	 * TODO: We recommend you to implement process resource cleanup here. */
	printf("exit(%d)", curr->dying_status);

	process_cleanup();
}

하려다가 다시 물어보고 고침
status는 안 고치고 쓰레드에 dying_status 추가해서 저장시킨 뒤에
process_exit에서 출력

이걸로 다시 돌려봤는데 make check가 하나도 안되는거 이상

/* buffer로부터 open file fd로 size 바이트를 적어줍니다. */
int write(int fd, const void *buffer, unsigned length)
{
	// strlcpy(fd, buffer, length);
	if (fd == 1)
		putbuf(buffer, length);
}

write를 먼저 구현해줘야 한다고 함
물어보면서 봤던 코드 그대로 베낌

어케 알았는가? : 호출하는 시스템 콜 번호를 디버깅 찍어봄
putbuf 쓰는 건 어케 알았는가? : 깃북에 있음

write 했더니 드디어 make check에 뭔가가 뜨기 시작함.

FAIL tests/userprog/exit
Test output failed to match any acceptable form.

Acceptable output:
  (exit) begin
  exit: exit(57)
Differences in `diff -u' format:
- (exit) begin
- exit: exit(57)
+ Page fault at 0x1: not present error reading page in user context.
+ exit: dying due to interrupt 0x0e (#PF Page-Fault Exception).
+ Interrupt 0x0e (#PF Page-Fault Exception) at rip=400110
+  cr2=0000000000000001 error=               4
+ rax 0000000000000001 rbx 0000000000000000 rcx 0000000000000000 rdx 0000000000000001
+ rsp 000000004747ffa8 rbp 000000004747ffb8 rsi 0000000000000001 rdi 000000004747ffe8
+ rip 0000000000400110 r8 0000000000000000  r9 0000000000000000 r10 0000000000000000
+ r11 0000000000000000 r12 0000000000000000 r13 0000000000000000 r14 0000000000000000
+ r15 0000000000000000 rflags 00000202
+ es: 001b ds: 001b cs: 0023 ss: 001b

아니 근데 이거 아까도 뜬 거잖아
아까 떴던 것들은 디버깅 문구 + hex_dump고
이번 건 hex_dump만 있긴 한데

아무 생각 없이 코드 멍하니 보면서 어떡하지 어떡하지 하지 말고
생각이라는 걸 해보자

내가 원하는 결과물: exit(57)
내가 했던 거: printf("%s: exit(%d)\n", thread_name(), status);
단서:

FAIL tests/userprog/args-single
Test output failed to match any acceptable form.

Acceptable output:
  (args) begin
  (args) argc = 2
  (args) argv[0] = 'args-single'
  (args) argv[1] = 'onearg'
  (args) argv[2] = null
  (args) end
  args-single: exit(0)
Differences in `diff -u' format:
- (args) begin
- (args) argc = 2
- (args) argv[0] = 'args-single'
- (args) argv[1] = 'onearg'
- (args) argv[2] = null
- (args) end
- args-single: exit(0)
+ (args) argv and stack must be word-aligned, actually 0x2

word-aligned가 안됐다고 함

	// for (int i = ptr_cnt - 1; i >= 0; i--)
	for (int i = 0; i < ptr_cnt; i++)

일단 for문 거꾸로 해봤는데 안됨

	if_->R.rdi = ptr_cnt;
	if_->R.rsi = if_->rsp;

도움 요청해서 한참 찾아줌.
그냥 이거 두 개 순서를 다르게 했던 거였음.

분석 복기: arg-none 테케에서 aligned 어쩌구가 actually 0x1이라고 뜸. 보니까 argv 출력하는거. argv 가보니까 잘못됐다고 함.

	case SYS_TELL:
		halt();
		break;
	case SYS_CLOSE:
		halt();
		break;

	default:
		break;
	}

	// thread_exit();
}

syscall.c에서 thread_exit()도 지워줬어야 함

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);

	char *save_ptr;

	strtok_r(file_name, " ", &save_ptr);
	/* 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;
}

명령줄이 한 개 더 출력됨.

process_create_initd에서 initd로 thread_create하기 전에 strtok_r(file_name, " ", &save_ptr); 해줘야 한다고 함.

pass tests/threads/priority-donate-chain
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
FAIL tests/userprog/create-normal
FAIL tests/userprog/create-empty
FAIL tests/userprog/create-null
FAIL tests/userprog/create-bad-ptr
FAIL tests/userprog/create-long
FAIL tests/userprog/create-exists
FAIL tests/userprog/create-bound
FAIL tests/userprog/open-normal
FAIL tests/userprog/open-missing
FAIL tests/userprog/open-boundary
FAIL tests/userprog/open-empty
FAIL tests/userprog/open-null
FAIL tests/userprog/open-bad-ptr
FAIL tests/userprog/open-twice
FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice
FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-normal
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-boundary
FAIL tests/userprog/read-zero
FAIL tests/userprog/read-stdout
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
FAIL tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
FAIL tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
FAIL tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
FAIL tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
FAIL tests/userprog/wait-bad-pid
FAIL tests/userprog/multi-recurse
FAIL 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
FAIL tests/filesys/base/lg-create
FAIL 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
FAIL tests/filesys/base/sm-create
FAIL 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
FAIL tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
FAIL tests/userprog/no-vm/multi-oom
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
68 of 95 tests failed.

나이스

fork

새 프로세스를 만드는데
부모 프로세스의 정보를 갖고 있어야 함

프로세스를 만드는 방법을 알아야 함

/* Clones the current process as `name`. Returns the new process's thread id, or
 * TID_ERROR if the thread cannot be created. */
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)
{
	/* Clone current thread to new thread.*/
	return thread_create(name,
						 PRI_DEFAULT, __do_fork, thread_current());
}

process.c에 process_fork()가 있었음

이런 흐름.

/* A thread function that copies parent's execution context.
 * Hint) parent->tf does not hold the userland context of the process.
 *       That is, you are required to pass second argument of process_fork to
 *       this function. */
  1. parent->tf가 저장하지 않는 것
    주석에서 언급된 첫 번째 중요한 포인트는 parent->tf가 부모 프로세스의 사용자 영역(userland) 컨텍스트를 저장하지 않는다는 것입니다.
  • parent->tf는 부모 스레드의 커널 모드에서의 인터럽트 프레임을 저장합니다. 즉, 커널 모드에서의 상태 정보만 담고 있고, 부모 스레드가 사용자 모드에서 실행 중이던 상태는 포함되어 있지 않습니다.

  • 사용자 영역(userland)은 프로그램이 직접 실행되는 공간이며, 커널에서만 실행되는 코드와는 다른 컨텍스트를 가집니다. 따라서 자식 스레드를 만들 때는 부모의 사용자 모드에서 실행 중이던 상태를 제대로 복사해줘야 합니다.

  1. 두 번째 인자를 전달해야 하는 이유
  • 주석에서 제공하는 힌트는 process_fork 함수의 두 번째 인자를 이 함수에 전달해야 한다는 것입니다. 이 인자는 부모의 사용자 영역 상태를 포함한 실행 컨텍스트를 전달할 수 있는 핵심적인 정보를 담고 있습니다.

  • 이 인자는 부모 프로세스의 사용자 모드 실행 컨텍스트를 나타냅니다. 따라서 자식 스레드가 올바르게 부모의 실행 상태를 복사하고, 이어서 실행할 수 있도록 이 정보를 넘겨받아야 합니다.

  1. 주요 작업
    이 함수의 주 목적은 부모 프로세스의 실행 상태를 복사하여 자식 프로세스가 부모 프로세스와 동일한 상태에서 실행될 수 있도록 하는 것입니다.

process_fork의 두번째 인자를 __do_fork에 넘겨줘야 한다고 함.

tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED)

여기 있는 intr_frame을

return thread_create(name, PRI_DEFAULT, __do_fork, thread_current());

여기 있는 __do_fork로 넘겨줘야 한다고??

...어떻게?

#ifndef VM
/* Duplicate the parent's address space by passing this function to the
 * pml4_for_each. This is only for the project 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. */

	/* 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. */

	/* 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). */

	/* 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 true;
}
#endif

아래 보니까 뭔가 관련있어 보이는게 있긴 한데
깃북에도 pml4 뭐시기 함수 쓰라는 얘기 듣긴 했는데

ifndef VM은 또 왜 붙어있고

첫번째 TODO의 parent_page가 kernel인지는 또 어떻게 알아야되는거지?
그냥 프로세스(쓰레드) 이름이 main인지 확인하면 끝? tid가 0이면 끝?
아니 프로세스나 쓰레드가 아니라 "페이지"네?
parent_page 초기화도 내가 해줘야되는거네? 어디서 받아오는거지?

구현할 것도 많고
테스트 순서도 create가 먼저인 것 같아서 패스

create

filesys.c 보니까 filesys_create() 있음.
이거 그대로 쓰면 될듯?

#include "filesys/filesys.h" 이거 추가하고

	case SYS_CREATE:
		create(f->R.rdi, f->R.rsi);
		break;

이거 추가하고

/*  file(첫 번째 인자)를 이름으로 하고 크기가 initial_size(두 번째 인자)인 새로운 파일을 생성합니다 */
bool create(const char *file, unsigned initial_size)
{
	filesys_create(file, initial_size);
}

이거 추가했는데 컴파일이 안됨.
로그 확인했더니 전방 선언때문에 안됨.
아니 그럼 이전 건 왜 됐던거임? 그건 알 수가 없음.

일단 전방선언해줬더니 컴파일되고 테스트도 됨.

create_normal은 통과하는데 create_empty나 create_null은 안됨.

어우 안되는거 싹 다 결과 뜯어보고 예외 처리 해줘야됨
사이버 노가다 on

create-empty, create-null

Acceptable output:
  (create-empty) begin
  create-empty: exit(-1)
Differences in `diff -u' format:
  (create-empty) begin
- create-empty: exit(-1)
Acceptable output:
  (create-empty) begin
  (create-empty) create(""): 0
  (create-empty) end
  create-empty: exit(0)
Differences in `diff -u' format:
  (create-empty) begin
- (create-empty) create(""): 0
- (create-empty) end
- create-empty: exit(0)

결과가 2개? 어째서?

Acceptable output:
  (create-null) begin
  create-null: exit(-1)
Differences in `diff -u' format:
  (create-null) begin
- create-null: exit(-1)

이건 exit(-1)을 시켜줘야함.

/*  file(첫 번째 인자)를 이름으로 하고 크기가 initial_size(두 번째 인자)인 새로운 파일을 생성합니다 */
bool create(const char *file, unsigned initial_size)
{
	if (file == NULL || strlen(file) == 0)
		exit(-1);

	filesys_create(file, initial_size);
}

일단 이렇게 수정했더니 empty랑 null통과함.

create-bad-ptr

FAIL tests/userprog/create-bad-ptr
Kernel panic in run: PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x800421837d 0x800421cc8f 0x800421ce0e 0x8004208ec5 0x80042092e3 0x800421cffb 0x800421ce83 0x400102 0x400180 0x400c21
Translation of call stack:
0x000000800421837d: debug_panic (lib/kernel/debug.c:32)
0x000000800421cc8f: kill (userprog/exception.c:103)
0x000000800421ce0e: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208ec5: intr_handler (threads/interrupt.c:352)
0x00000080042092e3: intr_entry (threads/intr-stubs.o:?)
0x000000800421cffb: syscall_handler (userprog/syscall.c:106)
0x000000800421ce83: no_sti (userprog/syscall-entry.o:?)
0x0000000000400102: (unknown)
0x0000000000400180: (unknown)
0x0000000000400c21: (unknown)

이상한 포인터 주면 꺼지라고 해야 함

https://velog.io/@biomatrix117/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A0%95%EA%B8%80-TIL-0903#accessing-user-memory

정리해놨었죠?

아니 아무리 찾아도 안 보이길래 깃허브 검색 기능 써봤더니 애초에 vaddr.c가 없다

vaddr.h는 있음

mmu.c에는 포인터가 아니라 pte만 있었음

아 다시보니까 vaddr.c가 아니라 vaddr.h였음
근데 헤더파일만 있어도 됨??

/* Returns true if VADDR is a user virtual address. */
#define is_user_vaddr(vaddr) (!is_kernel_vaddr((vaddr)))

/* Returns true if VADDR is a kernel virtual address. */
#define is_kernel_vaddr(vaddr) ((uint64_t)(vaddr) >= KERN_BASE)

아 그냥 전처리문만 선언돼있었음 ㅇㅋㅇㅋ 알겠
한 번만 읽어볼 걸 그랬네

/*  file(첫 번째 인자)를 이름으로 하고 크기가 initial_size(두 번째 인자)인 새로운 파일을 생성합니다 */
bool create(const char *file, unsigned initial_size)
{
	if (file == NULL || is_kernel_vaddr(file) || strlen(file) == 0)
		exit(-1);

	return filesys_create(file, initial_size);
}

이렇게 바꿔도 bad-ptr 통과를 못함

/* Looks up the physical address that corresponds to user virtual
 * address UADDR in pml4.  Returns the kernel virtual address
 * corresponding to that physical address, or a null pointer if
 * UADDR is unmapped. */
void *
pml4_get_page (uint64_t *pml4, const void *uaddr) {
	ASSERT (is_user_vaddr (uaddr));

	uint64_t *pte = pml4e_walk (pml4, (uint64_t) uaddr, 0);

	if (pte && (*pte & PTE_P))
		return ptov (PTE_ADDR (*pte)) + pg_ofs (uaddr);
	return NULL;
}

동기가 와서 힌트 주고 감.
mmu.c에 있는 pml4_get_page를 쓰면 된다고 함.

포인터니까 포인터로만 생각한 내 고정관념이 나빴다.
유연한 사고를 해야함.

동기꺼 보니까 bool check_ptr(void *ptr);로 함수 따로 빼서 하길래
해봤는데 뭔가 계속 오류 남.

/*  file(첫 번째 인자)를 이름으로 하고 크기가 initial_size(두 번째 인자)인 새로운 파일을 생성합니다 */
bool create(const char *file, unsigned initial_size)
{
	if (file == NULL || pml4_get_page(thread_current()->pml4, file) == NULL || !is_user_vaddr(file) || strlen(file) == 0)
		exit(-1);

	return filesys_create(file, initial_size);
}

원래대로 함수 따로 안 빼고 했더니 bad-ptr 통과 완료
(동기는 함수 따로 뺐지만 while문으로 한 번 더 돌렸더니 됐다고 함)

create-long

Acceptable output:
  (create-long) begin
  (create-long) create("x..."): 0
  (create-long) end
  create-long: exit(0)
Differences in `diff -u' format:
  (create-long) begin
- (create-long) create("x..."): 0
+ (create-long) create("x..."): 1
  (create-long) end
  create-long: exit(0)

파일명에 ... 붙이면 status가 0이 아니라 1?
애초에 create() 뭐시기는 왜 뜨는거지?
파일 보니까 msg로 그냥 뜨는거인듯.

	if (strchr(file, ".") != NULL && strchr(strchr(file, "."), ".") != NULL)
		exit(-1);

처음엔 이렇게 접근했는데,
x...가 진짜 "x..."라는게 아니라 축약의 의미로 ...이라는거. xxxxxxxxxxx... 이런 느낌이라고 함.
테스트 파일을 진작 봤어야했음. 동기가 안 말해줬으면 왜 안되지? 이러고 있을뻔.

문제를 직접 볼 수 있는데 현상만 보고 문제를 알고 있다고 착각하면 안됨

/*  file(첫 번째 인자)를 이름으로 하고 크기가 initial_size(두 번째 인자)인 새로운 파일을 생성합니다 */
bool create(const char *file, unsigned initial_size)
{
	if (file == NULL || pml4_get_page(thread_current()->pml4, file) == NULL || !is_user_vaddr(file) || strlen(file) == 0)
		exit(-1);
	else if (strlen(file) > 50)
		exit(0);

	return filesys_create(file, initial_size);
}

예외 처리 해줬는데

Acceptable output:
  (create-long) begin
  (create-long) create("x..."): 0
  (create-long) end
  create-long: exit(0)
Differences in `diff -u' format:
  (create-long) begin
- (create-long) create("x..."): 0
- (create-long) end
  create-long: exit(0)

이렇게 뜨길래
생각해봤더니
create까지 가서 이거 너무 긴데요? 해야되는거인듯.

"이거 너무 긴데요?" 했는데도 안돼서 디버깅 찍어봤더니 뭔가... 뭔가 이상한 일이 벌어지고 있음

	case SYS_CREATE:
		f->R.rax = create(f->R.rdi, f->R.rsi);
		break;

return이 안된다고 판단, 깃북에서도 봤었고 동기들한테도 들었던
rax 값에 return 값 넣어주기 해줌.
앞에 것들은 exit()가 알아서 return 해준듯?

그랬더니 통과함. 나이스.

create-exists

!! 다음 날 알아냈음. 수정 필요 없었음. !!

Acceptable output:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
  (create-exists) try to re-create quux.dat
  (create-exists) create baffle.dat
  (create-exists) try to re-create quux.dat
  (create-exists) end
  create-exists: exit(0)
Differences in `diff -u' format:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
- (create-exists) try to re-create quux.dat
- (create-exists) create baffle.dat
- (create-exists) try to re-create quux.dat
- (create-exists) end
- create-exists: exit(0)

이미 있는 거 만들면 안 만들어줌
try to 뭐시기 뜨게 해야되는데 이건 원래 있는건가?

dir_lookup 써보려고 이리저리 옮겨봤는데,
이대로 쓰면 page fault 일으킨다는 사실 알아냄.

아니 기존에 쓰던 곳 있길래 그대로 가져다 쓴건데 왜 안되지?
하지말고 무지성으로 갖다쓴 자신을 탓하자.

bool filesys_create(const char *name, off_t initial_size)
{
	if (strlen(name) > 50)
		return 0;

	struct dir *dir_test = dir_open_root();
	struct inode *inode = NULL;

	if (dir_lookup(dir_test, name, &inode) != NULL)
	{
		dir_close(dir_test);
		return false;
	}

	disk_sector_t inode_sector = 0;
	struct dir *dir = dir_open_root();

	bool success = (dir != NULL && free_map_allocate(1, &inode_sector) && inode_create(inode_sector, initial_size) && dir_add(dir, name, inode_sector));

	if (!success && inode_sector != 0)
		free_map_release(inode_sector, 1);
	dir_close(dir);

	return success;
}

아니 이건 진짜 될텐데 왜 안되지?

dir_lookup에서 바로 터져버리는 이유가 뭔지 모르겠다.

비교해보니까 바로 찾아냄.

조건문에 dir_test != NULL 이거 빼먹어서 그럼.
테스트 파일 실행 전에 여러 가지 세팅할 때 이런 경우가 있는듯.

물론 이제 터지진 않는데 답이 틀림.

Executing 'create-exists':
(create-exists) begin
(create-exists) create quux.dat
(create-exists) create warble.dat
Execution of 'create-exists' complete.

여전히 이렇게 나옴.

false가 입력돼서 그런가 했는데 약간 결과가 다름.

디버깅 문구 넣어서 다시 확인해봤더니

믿었던 warble이 실행이 안되던 거였음;
어째서??

이건 도저히 모르겠어서 답지 봤는데

여긴 이걸 수정하지도 않음.

쓰레드.c 보니까 제대로 구현된 레포 맞는거 같은데 왜...?

아니 애초에

	if (strlen(name) > 50)
		return 0;

이건 무조건 여기에 넣어야 되는건데 왜 안 넣은거임;

ㅇㅋ 이 사람 건 버리자

다른 답지 봤는데 여기도 이거 구현 안 했는데 적은거임
filesys 잔뜩 수정해놓고 그건 생략했거나

다시 이전 사람 답지 가서 마저 확인해봤더니 이렇게 돼있음.

아... get_page해서 할당돼있으면 뭔가가 있는거다? ㅇㅋ

아니 근데 그건 나도 했잖음?

Acceptable output:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
  (create-exists) try to re-create quux.dat
  (create-exists) create baffle.dat
  (create-exists) try to re-create quux.dat
  (create-exists) end
  create-exists: exit(0)
Differences in `diff -u' format:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
  (create-exists) try to re-create quux.dat
- (create-exists) create baffle.dat
- (create-exists) try to re-create quux.dat
- (create-exists) end
- create-exists: exit(0)

깃 저장하기 전에 한 번 돌려봤는데, try to까지 가는거 맞음.

스샷에서 드래그한 부분 다시 살려서 돌려보니까

try to re-create quux.dat가 출력이 안됨.

아... 그냥 dir를 마저 안 닫아줘서 그런거였음.
dir_close를 if문 안에서만 하면 어떡하나 이 사람아.

??? 똑같이 했는데 왜 이번엔 안됨

논리는 맞다고 판단,
아무래도 여기가 문제라고 생각함.

동기가 while문 썼더니 다 통과했다며 즐거워했었는데
그 이유가 process_wait라고 했었던 걸 들었음.

그래서 << 29에서 << 30으로 바꿔봤는데 뭔가 더 나옴.
그렇지 내가 틀릴리가 없었음

Acceptable output:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
  (create-exists) try to re-create quux.dat
  (create-exists) create baffle.dat
  (create-exists) try to re-create quux.dat
  (create-exists) end
  create-exists: exit(0)
Differences in `diff -u' format:
  (create-exists) begin
  (create-exists) create quux.dat
  (create-exists) create warble.dat
  (create-exists) try to re-create quux.dat
  (create-exists) create baffle.dat
- (create-exists) try to re-create quux.dat
- (create-exists) end
- create-exists: exit(0)

결과 : 틀림 ^^

일단 exit(0)가 안 나온다? 그리고 앞에서는 잘만 되던 create가 안된다?
이건 그냥 wait 문제.

동기한테 그 while문 테크닉이 뭐냐고 물어봐야할듯.
아니면 wait 하고 와서 다시 보던가.

근데 로직은 맞을거임.
틀릴 수가 없음.

마침 slack에 while문 그거 뭐 어떻게 하는건지 올라왔길래 시도해봤는데

통과함. 휴~

근데 이렇게 하니까 테스트 시간이 디지게 오래 걸려져서

내일은 wait 먼저 구현을 시도해봐야
하는 건 아니고 쉬운 것부터 차근차근 하자

pass tests/threads/priority-donate-chain
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
FAIL tests/userprog/open-normal
FAIL tests/userprog/open-missing
FAIL tests/userprog/open-boundary
FAIL tests/userprog/open-empty
FAIL tests/userprog/open-null
FAIL tests/userprog/open-bad-ptr
FAIL tests/userprog/open-twice
FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice
FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-normal
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-boundary
FAIL tests/userprog/read-zero
FAIL tests/userprog/read-stdout
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
FAIL tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
FAIL tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
FAIL tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
FAIL tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
FAIL tests/userprog/wait-bad-pid
FAIL tests/userprog/multi-recurse
FAIL 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
FAIL tests/filesys/base/lg-create
FAIL 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
FAIL tests/filesys/base/sm-create
FAIL 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
FAIL tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
FAIL tests/userprog/no-vm/multi-oom
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
61 of 95 tests failed.

갈 길이 멀다

0개의 댓글