ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2월 중순 되돌아보기
    Life 2021. 2. 17. 21:17

    되돌아보기

     

    2월 중순 되돌아보기

     

    시간이 점점 빠르게 흐른다. 이번 2주는 설이 껴있어서 그런지 조금은 어수선했다.

    Pintos Project 1주차에 Thread scheduling을 진행하고 최근 2주동안은 System Call들을 구현해 보았다. Fork, wait, read, write, exit, exec 등등 평소에 당연하게 여겼던 system call들을 구현하면서 OS가 어떻게 작동하는지 엿볼 수 있었다. 우리가 System Call을 잘 구현했는지 확인하기 위한 94개의 test case들이 존재하는데, 방금 마지막 test case까지 완료했다. Test case를 통과했다고 해서 완벽하게 구현했음을 보장받을 수는 없지만, 호독한 마지막 test case를 통과한게 너무 뿌듯하다.

     

    마지막 Test Case

    더보기
    /* 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");
    }
    

     

    너무 뿌듯해서 Test Case코드까지 첨부했다.(내 코드를 검사하는 코드이고, 내 코드는 아니다.) 

    마지막 Test Case에서는 부모 프로세스가 두개의 자식 프로세스를 fork한다. 첫 프로세스는 일정 resource를 소비한후 종료시키고, 나머지 프로세스는 일정 resource를 소비한 후 자식 프로세스를 fork하고 이 프로세스를 wait한다. 새로 생긴 자식 프로세스가 다시 부모가 되어 두개의 자식 프로세스를 fork한다. 이 과정을 메모리가 부족해서 자식 프로세스를 더이상 fork할 수 없을 때 까지(fork에 실패할 때 까지)반복한다. 실패하면, wait중이던 프로세스들이 wait을 끝내고, exit하게 된다.

     

     

     

    위의 과정을 10번 반복하면서 프로세스를 fork하고, resource를 소비하고, exit하는 과정에서 메모리누수가 일어나는지 검사한다. 메모리 누수가 일어난다면, fork에 실패하는 시점의 깊이가 얕아질 것임으로, 이 깊이가 일정하게 유지되어야 테스트 케이스를 통과할 수 있다. 

     

    팀원과 함께 이 Test Case를 통과한 후에, 진심으로 이해하고 통과한 것이 아니라 이것저것 시도해보다가 겨우 통과한 느낌이 들었다. 이게 어제다. 내 코드로 다시 한번 통과해 보기 위해서 오늘 다시 디버깅을 시작했는데 저녁 늦도록 오류의 원인을 찾지 못했었다. 문제는 child process의 exit_status로 특정 값을 넘겨줬는데, 부모가 child의 exit_status를 참조해 읽으면 이상한 값이 들어있었다는 것이다. 오류가 나는 부분에서 분명히 135가 exit_status에 입력되는데  process_wait함수에서 부모가 자식의 exit_status를 참조하면 계속 -121이 튀어나왔다. 이 문제의 원인을 하루종일 찾아봐도 해결하지 못했었는데, 한 시간 전에 결국 찾을 수 있었다.

     

    함께 공부하는 친구들에게 내 상황을 알리고 코드를 같이 봤는데, 원인은 135라는 숫자에 비해서 너무 작은 자료형을 사용한 거이었다. Thread 구조체에 exit_status를 저장할 변수를 선언했는데, 초기에는 exit_status가 100을 넘어서까지 커질 것이라고 생각하지 못해서 int8_t 자료형으로 선언을 했다. int8_t는 1byte크기의 자료형으로 2^8 = 256개의 숫자, -127부터 128까지의 정수만 표현할 수 있다. 그래서 135를 넣으면 계속 128을 초과해 -121이 출력되었던 것이다.

     

    thread struct의 크기를 최대한 줄이고 싶어서 일부러 작은 자료형을 선언했었는데, 이런 경우를 고려하지 못한게 큰 실수였다. 오늘의 실수로 다시한번 자료형과 자료구조의 중요성을 느낄 수 있는 계기가 되었다. 원인을 발견했을 때, 2주동안 했던 디버깅 성공때 느꼈던 감정들 보다 더 신났다. 행복했다...

     

    중간에 팀원들과 같이 진행했던 코드로 돌아가고 싶었지만 끝까지 원인을 찾아본 나 자신과 함께해준 친구들에게 너무 감사하다. 모든 경우들을 다 찾아보고 있다고 생각했는데, 이렇게 내 논리에 구멍이 있었다는 것을 발견했고 이후로는 자료형 까지 더욱 꼼꼼하게 체크해야겠다. 다들 너무 고맙다. 앞으로도 더욱 열심히 하겠습니다.

     

     

    2주 간의 목표 달성 check

    • 1일 1알고리즘 O
    • 일주일 동안 배운 내용 목요일에 포스팅하기 X
    • 일주일에 적어도 3번 운동 X
    • 중간중간 휴식 취하고 스트레칭하기 X

     

    반성

    눈과 몸을 너무 혹사시킨 것 같다. 바쁘긴 한데 운동도 중요하긴 하다. 딜레마다... 점심 저녁먹고 하루에 한 번 정도는 턱걸이 두세트씩 했는데, 부족한 듯 하다. 팔굽혀펴기라도 열심히 해야겠다. 

     

    코딩을 열심히 하는 것은 좋은데, 구현에 집중하다 보니 큰그림을 조금 놓치고 있다는 생각이 든다. 디버깅 하다가 보면, 큰그림은 잊어버린 채 작동하게만 하려는 자신을 보곤 한다. 구현과 이해 사이의 균형을 찾아야 하는데, 다음 2주 동안은 컴퓨터 시스템 책과 gitbook을 조금 더 정독하려는 노력을 해야겠다.

     

    앞으로 2주간의 목표

    이제 지키지 못할 것 같은 목표는 세우지 말아야겠다.

     

    • 하루에 팔굽혀펴기 50개 턱걸이 10개
    • 2일 1알고리즘
    • 2주동안 배운 내용 블로그에 포스팅하기

     

    다음 2주는 더욱 힘든 시간이 될 것 같다. 하지만 그만큼 성취감도 배가 될 것이라 믿는다. 2주 후에도 더 성장해 있는 나를 만나야겠다.

    'Life' 카테고리의 다른 글

    3월 말 되돌아보기  (1) 2021.04.07
    2월 말 되돌아보기  (0) 2021.03.02
    1월 말 되돌아보기  (0) 2021.02.04
    1월 중순 되돌아보기  (0) 2021.01.15
    12월 말 되돌아보기 2020년  (1) 2020.12.31
Designed by Tistory.