[SW사관학교 정글]72일차 TIL - project 2 all pass 도전기

김승덕·2022년 11월 29일
0

SW사관학교 정글 5기

목록 보기
113/150
post-thumbnail

project 2에서 All pass를 보고싶었다. 너무나... 🫠 이를 위한 고군분투의 과정을 아래 적어보았다.

지금 13개가 실패한다.

실패한 테스트 케이스가 어떤 케이스이고 어떻게 해결하면 되는지 확인해보자

bad-* 테스트 케이스들

이 문제는 page-fault를 해결해줘야하는 문제이다.

userprog/execption.c를 건드려주면 해결이 된다.

static void
page_fault (struct intr_frame *f) {
	(...)

	/* Determine cause. */
	not_present = (f->error_code & PF_P) == 0;
	write = (f->error_code & PF_W) != 0;
	user = (f->error_code & PF_U) != 0;

	if(user || write || not_present){
		exit(-1);
	}
	(...)

}

user, write, not_present는 모두 bool형의 값들이고 이게 true라면 page_fault가 발생한다. 따라서 page_fault가 발생할때 exit(-1)을 해줌으로써 오류를 해결한다.

그러면 7개가 남게된다.

rox-child, rox-multichild, lg-random, sm-random, syn-remove, syn-write

아래는 rox-child 테스트 케이스 코드이다.

/* -*- c -*- */

#include <syscall.h>
#include "tests/lib.h"
#include "tests/main.h"

void
test_main (void) 
{
  const char *child_cmd = "child-rox " CHILD_CNT;
  int handle;
  pid_t child;
  char buffer[16];

  /* Open child-rox, read from it, write back same data. */
  CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\"");
  CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer,
         "read \"child-rox\"");
  seek (handle, 0);
  CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer,
         "write \"child-rox\"");

  /* Execute child-rox and wait for it. */
  msg ("exec \"%s\"", child_cmd);
  if (!(child = fork ("child-rox"))){
    exec (child_cmd);
  }

  if (child < 0)
    fail ("fork() returned %d", child);
  quiet = true;
  CHECK (wait (child) == 12, "wait for child");
  quiet = false;

  /* Write to child-rox again. */
  seek (handle, 0);
  CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer,
         "write \"child-rox\"");
}

여기서 디버깅을 해보니 seek의 문제라는것을 알 수 있었다.

void seek(int fd, unsigned position)
{
	if (fd < 2)
		return;
	struct file *f = fd_to_file(fd);
	if (f == NULL)
		return;
	check_address(f);

	file_seek(f, position);
}

기존의 seek함수에서 check_address(f)를 제거해주니 고쳐졌다.

이것을 고치니 rox-child, rox-multichild, lg-random, sm-random, syn-remove, syn-write가 해결되었다.

multi-oom

/* Recursively forks until the child fails to fork.
   We expect that at least 28 copies can run.
   
   We count how many children your kernel was able to execute
   before it fails to start a new process.  We require that,
   if a process doesn't actually get to start, exec() must
   return -1, not a valid PID.

   We repeat this process 10 times, checking that your kernel
   allows for the same level of depth every time.

   In addition, some processes will spawn children that terminate
   abnormally after allocating some resources.

   We set EXPECTED_DEPTH_TO_PASS heuristically by
   giving *large* margin on the value from our implementation.
   If you seriously think there is no memory leak in your code
   but it fails with EXPECTED_DEPTH_TO_PASS,
   please manipulate it and report us the actual output.
   
   Orignally written by Godmar Back <godmar@gmail.com>
   Modified by Minkyu Jung, Jinyoung Oh <cs330_ta@casys.kaist.ac.kr>
*/

#include <debug.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <syscall.h>
#include <random.h>
#include "tests/lib.h"

static const int EXPECTED_DEPTH_TO_PASS = 10;
static const int EXPECTED_REPETITIONS = 10;

const char *test_name = "multi-oom";

int make_children (void);

/* Open a number of files (and fail to close them).
   The kernel must free any kernel resources associated
   with these file descriptors. */
static void
consume_some_resources (void)
{
  int fd, fdmax = 126;

  /* Open as many files as we can, up to fdmax.
	 Depending on how file descriptors are allocated inside
	 the kernel, open() may fail if the kernel is low on memory.
	 A low-memory condition in open() should not lead to the
	 termination of the process.  */
  for (fd = 0; fd < fdmax; fd++) {
#ifdef EXTRA2
	  if (fd != 0 && (random_ulong () & 1)) {
		if (dup2(random_ulong () % fd, fd+fdmax) == -1)
			break;
		else
			if (open (test_name) == -1)
			  break;
	  }
#else
		if (open (test_name) == -1)
		  break;
#endif
  }
}

/* Consume some resources, then terminate this process
   in some abnormal way.  */
static int NO_INLINE
consume_some_resources_and_die (void)
{
  consume_some_resources ();
  int *KERN_BASE = (int *)0x8004000000;

  switch (random_ulong () % 5) {
	case 0:
	  *(int *) NULL = 42;
    break;

	case 1:
	  return *(int *) NULL;

	case 2:
	  return *KERN_BASE;

	case 3:
	  *KERN_BASE = 42;
    break;

	case 4:
	  open ((char *)KERN_BASE);
	  exit (-1);
    break;

	default:
	  NOT_REACHED ();
  }
  return 0;
}

int
make_children (void) {
  int i = 0;
  int pid;
  char child_name[128];
  for (; ; random_init (i), i++) {
    if (i > EXPECTED_DEPTH_TO_PASS/2) {
      snprintf (child_name, sizeof child_name, "%s_%d_%s", "child", i, "X");
      pid = fork(child_name);
      if (pid > 0 && wait (pid) != -1) {
        fail ("crashed child should return -1.");
      } else if (pid == 0) {
        consume_some_resources_and_die();
        fail ("Unreachable");
      }
    }

    snprintf (child_name, sizeof child_name, "%s_%d_%s", "child", i, "O");
    pid = fork(child_name);
    if (pid < 0) {
      exit (i);
    } else if (pid == 0) {
      consume_some_resources();
    } else {
      break;
    }
  }

  int depth = wait (pid);
  if (depth < 0)
	  fail ("Should return > 0.");

  if (i == 0)
	  return depth;
  else
	  exit (depth);
}

int
main (int argc UNUSED, char *argv[] UNUSED) {
  msg ("begin");

  int first_run_depth = make_children ();
  CHECK (first_run_depth >= EXPECTED_DEPTH_TO_PASS, "Spawned at least %d children.", EXPECTED_DEPTH_TO_PASS);

  for (int i = 0; i < EXPECTED_REPETITIONS; i++) {
    int current_run_depth = make_children();
    if (current_run_depth < first_run_depth) {
      fail ("should have forked at least %d times, but %d times forked", 
              first_run_depth, current_run_depth);
    }
  }

  msg ("success. Program forked %d iterations.", EXPECTED_REPETITIONS);
  msg ("end");
}

위는 multi-oom의 testcase 코드이다. 여기서도 여러번의 디버깅 끝에 여러 문제가 있다는것을 알았다…

아래에 나열을 해보겠다.

  1. process_fork()에서 sema_down()의 위치가 잘못되었다.

  2. __do_fork()에서 부모의 파일 디스크립터 인덱스가 LIMIT보다 크면 goto error; 를 해주어야한다.

  3. process_fork()에서 아래와 같은 에러 처리를 해주어야한다.

    if(child->exit_status == -1)
    	return TID_ERROR; 
  4. process_wait에서 에러 처리를 해주어야한다.

    if(child_tid < 0)
    	return -1;
  5. process_exit에서 palloc_free_multiple() 함수를 통해 free 시켜주어야하고, process_cleanup 의 위치를 수정해준다.

    palloc_free_multiple(curr->file_descriptor_table, FDT_PAGES);

좀 많았다… 이렇게 수정을 하니 all-pass가 떴다.

하지만 지금 우리 팀의 디버깅이 지금 이글을 보는 여러분과 같을 가능성은 정말 낮다.

디버깅 팁

이럴때는 디버깅을 어떻게 하는지가 정말 중요하다. 정말 별건 아니지만 우리팀이 사용한 디버깅 팁을 알려주자면 다음과 같다.

  1. 테스트 파일을 잘 활용해라

    다행히도 pintos는 대부분의 함수가 잘 구현되었는지 확인해볼수있다. 그리고 테스트를 하는 코드도 우리가 볼수있다. 그렇다면 이 코드들을 보면서 어떤 함수가 잘못되었는지 확인할 수 있다.

    그래서 우리팀은 테스트 파일에 함수와 함수 사이에 printf() 를 적어보면서 어떤 함수가 잘못되었는지 유추를 하였다. 우리의 코드가 어디에서 틀렸는지 안다는건 정말정말 중요하고 시간절약이 된다. 그래서 이 방법이 정말 유용했다.

  2. 의심이 되는 코드부분을 주석처리하면서 계속 테스트 케이스를 돌려봐라

    위에서 어떤 함수가 잘못 구현되었는지 찾았다면 여기서 이제 주석 처리를 해보면 된다. 물론 여기서도 printf() 를 통해서 잘못된 부분을 찾는것도 나쁘지 않다.

    그리고 의심이 되는 부분을 주석처리하면서 잘못되었는지 안되었는지 확인해보는것도 정말 좋은 방법이다.

  3. 최후의 방법이다. 정답 코드를 어떻게든 찾아서 잘못된 부분을 찾아보아라

    이 방법… 우리팀도 좀 썼다… 정말 막막할때는 이 방법이 해결책이 된다. 구글링하거나 주변의 성공한 팀에게 얻어오는 등 어떻게든 정답 코드를 찾아보아라. 그런다음 함수 단위로 내 코드를 지우고 정답코드를 붙여본다.(반대로 해도 된다.) 그래서 잘못된 부분을 찾고 잘못된 이유를 고민해본다. 이유를 알아내는것이 중요하다!!

이번 포스팅은 여기서 끝!

profile
오히려 좋아 😎

0개의 댓글