Operating System

[Pintos] project2-2 syscall 나머지

star.candy 2023. 6. 9. 17:40

틀린 내용 있을 수 있습니다 지적 환영

 

TODO

System call implementation - read, write, exec, wait, remove, filesize, seek, tell

^^..

 

pintos/src/userprog/syscall.c의 스위치문에 마저 케이스를 완성해주고, 아래에 함수 뼈대도 만들어준다. 당연히 헤더파일에도 적어줌.

사실 저번 프로젝트때 여기까지는 해두고 안쓰는건 주석처리해두고 내서 주석만 없애주면 됐다.. 아주 칭찬해 정말

그럼 이제 필요한 파일들을 열어둬야한다(편의를 위해) ppt를 읽어보자

filesys/filesys.h, filesys/file.h, lib/syscall-nr.h을 참조할것, 그렇지만 modify는 필요없다(아직까진-^^;;;)솔직히 그렇게 도움이 된instruction은 아니었다 어차피userprog과 thread에 있는 파일 다 열어봐야되던데..허

 

pid_t exec (const char *file)

먼저 (늘 그렇듯..) 매뉴얼을 읽어보면 해야 할 일이 나와있다. 대충 크게 두가지로 나눌 수 있을 것 같은데

1. Runs the executable whose name is given in cmd line passing any given arguments > returns the new process’s program id (pid). Must return pid -1, which otherwise should not be a valid pid, if the program cannot load or run for any reason

2. The parent process cannot return from the exec until it knows whether the child process successfully loaded its executable > You must use appropriate synchronization to ensure this.

먼저 새로운 프로세스 아이디를 리턴하고, 두번째로 child process와 싱크로나이즈 할 방법을 찾는다(으악.....)

1번은 process.c 파일에서 tid_t process_execute (const char *file_name) 함수로부터 해결 할 수 있다. 새로운 process id를 return하고, 스레드를 생성하지 못하TID_ERROR를 반환한다(TID_ERROR는 ./threads/thread.h에서 #define TID_ERROR ((tid_t) -1)          /* Error value for tid_t. */ 으로 정의되어 있다.).

pid_t
exec (const char *file)
{
  return process_execute(file);
}

1번은 마쳤다. 이제 지옥에서 온 싱크로나이즈 구현. 문제는 어떻게 child process가 successfully loaded 됐는지 알고 기다리냐는 건데.. 메뉴얼의 exec 설명 바로 아래 wait에 대한 설명이 나와있다.

 

int wait (pid_t pid)

Waits for a child process pid and retrieves the child’s exit status. If pid is still alive, waits until it terminates.

우선 process_wait을 부른다. 지난 2-1에서 무한루프로 돌려놓았던 것을 여기서 고쳐야하나보다.

int
wait (pid_t pid)
{
  return process_wait(pid);
}

우선 이렇게 해 두고.. process_wait의 설명을 읽어보면

Waits for thread TID to die and returns its exit status.
If it was terminated by the kernel (i.e. killed due to an exception), returns -1.
If TID is invalid or if it was not a child of the calling process, or if process_wait() has already been successfully called for the given TID, returns -1 immediately, without waiting.

TID가 죽을 때 까지 기다리고 exit status를 리턴한다. exception으로 kill되면 -1을 리턴하고, TID가 invalid하거나 calling process의 child가 아니면 waiting 없이 바로 -1을 리턴한다.

스레드에 먼저 child에 대한 정보와 exit status에 대한 정보를 알 수 있도록 thread.h에 추가 선언해주고 thread.c에서 초기화시켜준다.

// thread.h
#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
    struct file *files[128];            /* Files. */
    int fd;                             /* File descriptor. */
    int exit_status;                    /* Exit status of the thread. */
    struct list child_list;             /* Child list. */
    struct list_elem child;             /* List element for all child threads list. */
#endif
// thread.c
#ifdef USERPROG
    /* Owned by userprog/process.c. */
    int i = 0;
    for (i; i < 128; i++) t->files[i] = NULL;
    t->fd = 0;
    list_init (&(t->child_list));
    list_push_back (&(running_thread()->child_list), &(t->child));
 #endif

이제 child에 대한 정보를 가질 수 있는 장치는 만들어뒀다. 싱크로나이즈 문제를 해결하기 위해 관련 페이지를 찾아 읽어본다. 찾아보니 lock과 semaphore가 정의되어 있는 파일은 threads/synch.h와 threads/synch.c이다. 파일을 열어준다..

(이 티스토리에서 감사하게도 잘 설명을 해 주셔서..)

https://yoongrammer.tistory.com/63

 

스핀락(Spin lock), 뮤텍스(Mutex), 세마포어(Semaphore) 알아보기

목차 스핀락(Spin lock), 뮤텍스(Mutex), 세마포어(Semaphore) 알아보기 스핀 락(Spin lock) 스핀 락(Spin lock)은 임계 구역에 진입이 불가능할 때 진입이 가능할 때까지 루프를 돌면서 재시도하는 방식으로

yoongrammer.tistory.com

synch.c에 구현되어있는 lock은 mutex라고 봐도 되는데, mutex는 lock을 걸은 스레드만이 critical section을 통과하고 lock을 풀 수 있지만 semaphore는 lock을 걸지 않은 스레드도 signal을 사용해 lock을 풀 수 있기 때문에, semaphore는 mutex로 사용될 수 있지만 그 반대는 안된다. 즉, 여기서 요구하는 syncronization을 구현하려면 lock이 아닌 semaphore를 사용해야 한다고 이해해도 ..될?것?같?다?

threads/synch.h에서 정의된 semaphore 구조체를 본다

struct semaphore 
  {
    unsigned value;             /* Current value. */
    struct list waiters;        /* List of waiting threads. */
  };

메뉴얼에서 semaphore와 관련된 페이지를 찾아서 설명을 읽어보자

  • “Down” or “P”: wait for the value to become positive, then decrement it.
  • “Up” or “V”: increment the value (and wake up one waiting thread, if any).

A semaphore initialized to 0 may be used to wait for an event that will happen exactly once. For example, suppose thread A starts another thread B and wants to wait for B to signal that some activity is complete. A can create a semaphore initialized to 0, pass it to B as it starts it, and then “down” the semaphore. When B finishes its activity, it “ups” the semaphore. This works regardless of whether A “downs” the semaphore or B “ups” it first.

A semaphore initialized to 1 is typically used for controlling access to a resource. Before a block of code starts using the resource, it “downs” the semaphore, then after it is done with the resource it “ups” the resource. In such a case a lock, described below, may be more appropriate.

첫번째 문단에 구현 가닥이 대충 써 있는거 같은데..? semaphore를 0으로 초기화 했을 때의 사용 용례는 대충.. thread A가 다른 thread B를 start하는데(여기서 thread A는 parent, thread B는 child라고 보면 될 것 같다?) thread B가 잘 끝났다는 signal을 기다리고 있다. 그럼 A는 0으로 초기화된 semaphore을 B가 시작할 때 같이 전달하고, down한다. B가 잘 exit하면 다시 up 한다. 순서는 상관없다.

두번째 방법은 lock(mutex?)를 이야기하는 것 같다.

그럼 thread.h와 thread.c에 semaphore을 정의해준다.

//thread.h, struct thread
#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
    struct file *files[128];            /* Files. */
    int fd;                             /* File descriptor. */
    int exit_status;                    /* Exit status of the thread. */
    struct list child_list;             /* Child list. */
    struct list_elem child;             /* List element for child threads in list. */
    struct semaphore wait_child;        /* Semaphore for waiting child process. */
#endif
  //thread.c, init_thread
  #ifdef USERPROG
    /* Owned by userprog/process.c. */
    int i = 0;
    for (i; i < 128; i++) t->files[i] = NULL;
    t->fd = 0;
    list_init (&(t->child_list));
    list_push_back (&(running_thread()->child_list), &(t->child));
    sema_init (&(t->wait_child), 0);
  #endif

process_wait에 시키는 내용들을 구현해준다

int
process_wait (tid_t child_tid) 
{
  struct thread *child_thread = get_child_tid (child_tid);

  if (child_thread == NULL) return -1;

  sema_down (&(child_thread->wait_child));
  int exit_status = child_thread->exit_status;
  list_remove (&(child_thread->child));
  
  return exit_status;
}

대충 설명을 하자면.. child_tid를 가진 child_thread를 찾고, 만약 해당하는 child_thread가 없으면 -1을 리턴한다. child_thread->wait_child를 sema_down하고, child_thread의 exit_status를 int exit_status에 저장한 뒤, child를 remove해준다. 마지막으로 exit_status를 remove한다.

child_tid에 해당하는 child_thread를 찾는 함수 get_child_tid는 thread.c에 만들어줬다(이유는 없고 그냥 통일성을 위해..)

//thread.c
struct thread *
get_child_tid (tid_t child_tid)
{
  struct list *cur_child = &(thread_current()->child_list);
  struct list_elem *e;
  for (e = list_begin (&cur_child); e != list_end (&cur_child); e = list_next (e))
  {
    struct thread *t = list_entry (e, struct thread, child);
    if (child_tid == t->tid)  return t;
  }
  return NULL;
}

이제 sema_up을 해줘야한다. 이건 child thread가 finish activity를 하는 지점인 process_exit에 추가해주면 될 것 같다

void
process_exit (void)
{
  struct thread *cur = thread_current ();
  uint32_t *pd;

  /* Destroy the current process's page directory and switch back
     to the kernel-only page directory. */
  pd = cur->pagedir;
  if (pd != NULL) 
    {
      /* Correct ordering here is crucial.  We must set
         cur->pagedir to NULL before switching page directories,
         so that a timer interrupt can't switch back to the
         process page directory.  We must activate the base page
         directory before destroying the process's page
         directory, or our active page directory will be one
         that's been freed (and cleared). */
      cur->pagedir = NULL;
      pagedir_activate (NULL);
      pagedir_destroy (pd);
    }
  sema_up (&(cur->wait_child));
}

syscall.c에 exit 함수에서 current thread의 exit_status를 저장할 수 있도록 한다.

void
exit (int status) 
{
  struct thread *cur = thread_current();
  cur->exit_status = status;
  printf("%s: exit(%d)\n", cur->name, status);
  int i;
  for (i = 3; i < 128; i++) {
    if (cur->files[i] != NULL) close(i);
  }
  thread_exit();
}

가장 기본이었던 args-none을 돌려본다

pintos -v -k -T 60 --bochs  --filesys-size=2 -p tests/userprog/args-none -a args-none -- -q  -f run args-none < /dev/null 2> tests/userprog/args-none.errors |tee tests/userprog/args-none.output
Executing 'args-none':
main: exit(-1)
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)

TIMEOUT after 60 seconds of host CPU time

..? ㅋ..

args-none이 어떻게 돌아가는가.. 저 출력을 뽑아내는 파일을 찾기위해 thread.c를 뒤지다가 저 출력들은 threads/init.c에서 나온다는 걸 알게되었다(핀토스는 노가다다 진심;;)

/* Runs the task specified in ARGV[1]. */
static void
run_task (char **argv)
{
  const char *task = argv[1];
  
  printf ("Executing '%s':\n", task);
#ifdef USERPROG
  process_wait (process_execute (task));
#else
  run_test (task);
#endif
  printf ("Execution of '%s' complete.\n", task);
}

args-none을 process_execute에 전달하고, 이걸 process_wait에 전달한다..

지금까지 만든 process_execute와 process_wait함수를 기준으로 무슨 일이 일어나는지 생각을 해 보자. args-none이라는 task가 process_execute에 전달되면 args-none이라는 이름의 thread가 create되고, 해당 tid가 return된다. 그럼 main thread가 왜 -1로 먼저 끝나는지. printf문 두줄을 추가해서 어떻게 찍히는지 본다(printf를 사용하는건 여하튼 위험하지만 일단 머리가 안돌아간다ㅜ.ㅜ)

//process.c, process_execute()
printf("current thread %s tid: %d\ncreated thread %s tid: %d\n", thread_current()->name, thread_current()->tid, t_name, tid);
printf("thread %s running\n", thread_current() ->name);
Executing 'args-none':
current thread main tid: 1
created thread args-none tid: 3
thread main running
main: exit(-1)
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)
^C

현재 돌아가는 thread은 예상했던대로 main이고 tid 1을 가진다. args-none thread도 잘 생성되었고... 이 친구의 tid는 3이다. 문제는 main이 args-none보다 먼저 튀어나온다. 여기다가 semaphore를 걸어볼까?

Executing 'args-none':
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)
^C

args-none은 잘 돌아가지만 main이 끝나지 않는다. 하.. 당연하지 애초에 execute에서 막으면....

자 다시 차근차근 생각을 해보면.. process_wait (process_execute (task)) -여기서 task는 args-none이다-에서 process_execute는 args-none thread에 대한 tid를 process_wait의 argument로 준다; 여기서 process_wait는 argument를 tid_t child_tid로 받는다는 점이다. 즉 디폴트로 돌아가는 main thread의 child는 args-none이 된다는 듯인데 왜 니 자식을 니 자식으로 인식을 못하니

 

...

 

아주 바보같은 실수를 두가지 했다. 이걸로 6시간을 썼는데 굉장히 열받는다..

우선 get_child_tid 함수를 지우고 직접 적어줬다.. 바로 아 child 찾았다 리턴!! 하는게 아니라 찾고 sema를 걸어줘야했다. 이유는 아래에

int
process_wait (tid_t child_tid) 
{
  struct list *cur_child = &(thread_current()->child_list);
  struct list_elem *e;
  struct thread *child_thread = NULL;
  int exit_status = -1;
  printf("%s tid %d\n", thread_current()->name, thread_current()->tid);
  for (e = list_begin (&cur_child); e != list_end (&cur_child); e = list_next (e))
  {
    struct thread *t = list_entry (e, struct thread, child);
    printf("child tid %d, thread tid %d\n", child_tid, t->tid);
    if (child_tid == t->tid) {
      struct thread *child_thread = t;
      sema_down (&child_thread->wait_child);
      exit_status = child_thread->exit_status;
      list_remove (&(child_thread->child));
      break;
    }
  }
  return exit_status;
}

child_list가 자꾸 엉뚱한 값을 내놓는 것 같아 printf를 찍으면서 확인해 본 결과

Executing 'args-none':
main tid 1
child tid 3, thread tid 0
main: exit(-1)
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)

어쩐지 이상하다했어

for 문에 들어가는 list를 조금 고쳐준다

int
process_wait (tid_t child_tid) 
{
  struct thread *cur = thread_current();
  struct list_elem *e;
  int exit_status = -1;
  printf("%s tid %d\n", thread_current()->name, thread_current()->tid);
  for (e = list_begin (&cur->child_list); e != list_end (&cur->child_list); e = list_next (e))
  {
    struct thread *t = list_entry (e, struct thread, child);
    printf("child tid %d, thread tid %d\n", child_tid, t->tid);
    if (child_tid == t->tid) {
      struct thread *child_thread = t;
      sema_down (&child_thread->wait_child);
      exit_status = child_thread->exit_status;
      list_remove (&(child_thread->child));
      break;
    }
  }
  return exit_status;
}
Executing 'args-none':
main tid 1
child tid 3, thread tid 1
child tid 3, thread tid 2
child tid 3, thread tid 3
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)
main: exit(-1)

그래..이제야 알아보는구나

main이 먼저 실행되는 것은 고쳐졌는데 여전히 exit(-1)로 비정상적인 종료를 한다. 느낌이 좋다! 이 원인만 찾으면 wait와 exec모두 해결될 것 같다!

arge-none은 정상적으로 exit_status 0을 반환했다. 그러니까 그 이후에 일어난 문제라는 건데. 디버깅을 위해 sema_down 이후의 thread의 값들을 찍어보자

int
process_wait (tid_t child_tid) 
{
  struct thread *cur = thread_current();
  struct list_elem *e;
  int exit_status = -1;
  for (e = list_begin (&cur->child_list); e != list_end (&cur->child_list); e = list_next (e))
  {
    struct thread *t = list_entry (e, struct thread, child);
    if (child_tid == t->tid) {
      struct thread *child_thread = t;
      sema_down (&child_thread->wait_child);
      printf("exit_status %d\n", child_thread->exit_status);
      exit_status = child_thread->exit_status;
      printf("child tid %d, thread tid %d\n", child_tid, child_thread->tid);
      list_remove (&(child_thread->child));
      break;
    }
  }
  return exit_status;
}
Executing 'args-none':
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)
exit_status -858993460
child tid 3, thread tid -858993460
main: exit(-1)

제정신이 아니군. process_exit 설명을 보면 Free the current process's resources라고 한다. 그 과정에서 기본의 child thread의 값들이 free 되면서 저렇게 이상한 값이 찍히나보다. 그럼 값들이 삭제되기 전에 sema_up을 해주면 되지 않..을까?

void
process_exit (void)
{
  struct thread *cur = thread_current ();
  uint32_t *pd;

  sema_up (&cur->wait_child);
  /* Destroy the current process's page directory and switch back
     to the kernel-only page directory. */
  pd = cur->pagedir;
  if (pd != NULL) 
    {
      /* Correct ordering here is crucial.  We must set
         cur->pagedir to NULL before switching page directories,
         so that a timer interrupt can't switch back to the
         process page directory.  We must activate the base page
         directory before destroying the process's page
         directory, or our active page directory will be one
         that's been freed (and cleared). */
      cur->pagedir = NULL;
      pagedir_activate (NULL);
      pagedir_destroy (pd);
    }
}

결과:

Executing 'args-none':
(args) begin
(args) argc = 1
(args) argv[0] = 'args-none'
(args) argv[1] = null
(args) end
args-none: exit(0)
exit_status 0
child tid 3, thread tid 3
Execution of 'args-none' complete.

의도한대로 제대로 값들이 들어가있다. 이쯤에서 make check

pass tests/userprog/exec-once
FAIL tests/userprog/exec-arg
pass tests/userprog/exec-multiple
FAIL tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid

디버깅을 위한 출력물 안지우고 했다가 눈물 날 뻔했다

좀 자고 일어나서 생각해보니 저렇게 clean하기 전에 sema_up만 해주는 것이 안전한가... 그니까 뭔가 운이 안좋아서 sema_up을 해도 child의 값들을 가져오기 전에 홀라당 지워버리는 케이스를 막을 수 있나? 일단 테스트들은 통과를 하긴 했는데 영 찝찝하다.... clean하는 부분에서 sema를 하나 더 만들어주는게 안전할 것 같다

// thread.h
#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
    struct file *files[128];            /* Files. */
    int fd;                             /* File descriptor. */
    int exit_status;                    /* Exit status of the thread. */
    struct list child_list;             /* Child list. */
    struct list_elem child;             /* List element for child threads in list. */
    struct semaphore wait_child;        /* Semaphore for waiting child process. */
    struct semaphore wait_clean;        /* Semaphore for process_exit(). */
#endif

thread.h와 thread.c에 추가/초기화 해 주고

int
process_wait (tid_t child_tid) 
{
  struct thread *cur = thread_current();
  struct list_elem *e;
  int exit_status = -1;
  for (e = list_begin (&cur->child_list); e != list_end (&cur->child_list); e = list_next (e))
  {
    struct thread *t = list_entry (e, struct thread, child);
    if (child_tid == t->tid) {
      struct thread *child_thread = t;
      sema_down (&child_thread->wait_child);
      sema_up (&child_thread->wait_clean);
      exit_status = child_thread->exit_status;
      list_remove (&(child_thread->child));
      break;
    }
  }
  return exit_status;
}
void
process_exit (void)
{
  struct thread *cur = thread_current ();
  uint32_t *pd;

  sema_up (&cur->wait_child);
  sema_down (&cur->wait_clean);
  /* Destroy the current process's page directory and switch back
     to the kernel-only page directory. */
  pd = cur->pagedir;
  if (pd != NULL) 
    {
      /* Correct ordering here is crucial.  We must set
         cur->pagedir to NULL before switching page directories,
         so that a timer interrupt can't switch back to the
         process page directory.  We must activate the base page
         directory before destroying the process's page
         directory, or our active page directory will be one
         that's been freed (and cleared). */
      cur->pagedir = NULL;
      pagedir_activate (NULL);
      pagedir_destroy (pd);
    }
}

각각 clean되기 전에 추가해준다

make check하면 역시나 추가하기 전과 같은 결과를 보이나.. 마음이 편하다

wait 테스트케이스들은 모두 통과하지만 exec는 몇개 걸렸다. 하나씩 열어보고 다음 걸로 넘어가야겠다.

  1. exec-arg

코드를 열어보자

/* Tests argument passing to child processes. */

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

void
test_main (void) 
{
  wait (exec ("child-args childarg"));
}
Executing 'exec-arg':
(exec-arg) begin
exec-arg: exit(-1)
Execution of 'exec-arg' complete.
Timer: 274 ticks
Thread: 30 idle ticks, 221 kernel ticks, 25 user ticks
hda2 (filesys): 88 reads, 404 writes
hda3 (scratch): 198 reads, 2 writes
Console: 910 characters output
Keyboard: 0 keys pressed
Exception: 1 page faults

page fault다. 왜 났는지 좀 더 자세하게 알아야 할 것 같아서 이전에 만들었던 커널 침범 여부 확인 부분을 주석처리했다

  //if (!user || is_kernel_vaddr (fault_addr))  exit(-1);

  /* To implement virtual memory, delete the rest of the function
     body, and replace it with code that brings in the page to
     which fault_addr refers. */
  printf ("Page fault at %p: %s error %s page in %s context.\n",
          fault_addr,
          not_present ? "not present" : "rights violation",
          write ? "writing" : "reading",
          user ? "user" : "kernel");
  kill (f);
Page fault at 0x804a6e2: rights violation error writing page in kernel context.
Interrupt 0x0e (#PF Page-Fault Exception) at eip=0xc00277f1
 cr2=0804a6e2 error=00000003
 eax=00000020 ebx=0804a6d8 ecx=00000020 edx=00000020
 esi=c002fede edi=0804a6e2 esp=c002fede ebp=00000020
 cs=0008 ds=0010 es=0010 ss=0020
Kernel PANIC at ../../userprog/exception.c:101 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0xc00283be 0xc00277f1.

ㅜㅜ...load에서 막힌건가...뭐가 문제니

 

...

 

또 이걸로 6시간은 족히 쓴 듯.. 핀토스는 스스로의 멍청함을 가감없이 마주하는 과정인 듯하다

process_execute에서 새로운 thread를 create하는 과정에서 file_name을 그대로 strtok_r에 넘겨줘 버렸다

위에 친절하게 Make a copy of FILE_NAME. Otherwise there's a race between the caller and load(). 라고 경고하고 있음에도 불구하고...

ㅋㅋ

에라이

tid_t
process_execute (const char *file_name) 
{
  char *fn_copy, *fn_copy_1;
  char *t_name, *save_ptr;
  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);

  /* Create a new thread to execute FILE_NAME. */
  fn_copy_1 = palloc_get_page (0);
  strlcpy (fn_copy_1, file_name, PGSIZE);
  t_name = strtok_r (fn_copy_1, " ", &save_ptr);
  if (filesys_open (t_name) == NULL) return -1;
  tid = thread_create (t_name, PRI_DEFAULT, start_process, fn_copy);
  if (tid == TID_ERROR) {
    palloc_free_page (fn_copy);
    return TID_ERROR;
  }

exec-missing도 같이 해결되었다. exec-missing test에 달려있는 주석은

Tries to execute a nonexistent process.
The exec system call must return -1.

if (filesys_open (t_name) == NULL) return -1; 한 줄을 추가해주면 된다.

이제 exec과 wait 테케는 모두 돌아간다

 

bool remove (const char *file)

Deletes the file called file. Returns true if successful, false otherwise. A file may be removed regardless of whether it is open or closed, and removing an open file does not close it. See [Removing an Open File], page 35, for details.

35페이지에 없는데 뭐 어디서 찾으라는거지; 아무튼 filesys_remove가 구현되어있으니 가져다 쓰랜다

감사하다

bool
remove (const char *file)
{
  return filesys_remove(file);
}

 

int filesize (int fd)

Returns the size, in bytes, of the file open as fd.

이전 프젝에서 thread에 정의해두었던 struct file *files[128]을 사용할 때다

당연히 파일이 NULL이면 exit(-1)한다솔직히 이쯤되니 눈치껏 넣어주게 된다

int
filesize (int fd) 
{
	if (thread_current()->files[fd] == NULL) exit(-1); 
	return file_length (thread_current()->files[fd]);
}

 

int read (int fd, void *buffer, unsigned length)

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().

return값은 number of bytes actually read, 파일을 읽을 수 없으면 -1. fd=0은 키보드로부터 온다..

일단 filesys.c에서 read와 관련된 함수를 찾아보자. 어 여기없다. file.c에 있다.

// filesys/file.c
/* 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;
}

저번 프로젝트에서 open을 참고하여 fd를 기준으로 조건문을 만든다. fd는 3부터 들어가니까 저렇게 써주고.. 근데 fd 0 reads from the keyboard using input_getc()란다. 그럼 0에 대한 케이스도 따로 빼줘야하나보다. 그리고 나머지 파일을 열 수 없는 모든 경우에는 -1을 반환한다. 지금 당장 생각나는건 fild이 NULL일 때 밖에 없으므로 일단 그렇게 써준다.

int
read (int fd, void *buffer, unsigned length)
{
  if (thread_current()->files[fd] == NULL)  return -1;
  if (fd == 0) {

  } else if (fd > 2) {
    return file_read (thread_current()->files[fd], buffer, size);
  }
}

자 fd는 0일 때 어떻게 읽어들어야하는지 생각하자. grep input_getc -R . 을 통해서 input_getc를 찾는다.

// devices/input.c
/* Retrieves a key from the input buffer.
   If the buffer is empty, waits for a key to be pressed. */
uint8_t
input_getc (void) 
{
  enum intr_level old_level;
  uint8_t key;

  old_level = intr_disable ();
  key = intq_getc (&buffer);
  serial_notify ();
  intr_set_level (old_level);
  
  return key;
}

뭐지. 별로 도움은 안되는것같다.

일단 쉬운것부터 하자.

read과 관련된 테케들을 하나씩 돌려본다

read-normal은 통과하는 것 같고.. read-bad-ptr이 걸린다

pintos -v -k -T 60 --bochs  --filesys-size=2 -p tests/userprog/read-bad-ptr -a read-bad-ptr -p ../../tests/userprog/sample.txt -a sample.txt -- -q  -f run read-bad-ptr < /dev/null 2> tests/userprog/read-bad-ptr.errors |tee tests/userprog/read-bad-ptr.output
Executing 'read-bad-ptr':
(read-bad-ptr) begin
(read-bad-ptr) open "sample.txt"
(read-bad-ptr) should not have survived read(): FAILED
read-bad-ptr: exit(1)
read-bad-ptr: exit(-1)
Execution of 'read-bad-ptr' complete.
read-bad-ptr: exit(-1)
^C

살아남으면 안됐었대

이전에 만든 check함수로 buffer와 length를 검사해주는 코드를 추가한다

잘 통과한다...

fd == 0인 테스트케이스가 뭔지 좀 보려고 따로 프린트문을 추가해봐도 도저히 찾을 수가 없다.. 우선 재껴두고 다른것부터 만들고 마지막에 테스트케이스를 하나씩 열어보면서 하는걸로 노선을 변경한다.

 

int write (int fd, const void *buffer, unsigned size)

int
write (int fd, const void *buffer, unsigned size)
{
  check (buffer);
  check (size);
  if (fd == 1) {
    //writes to console: call putbuf(buffer, size) and return size
    putbuf(buffer, size);
    return size;
  } else if (fd > 2) {
    struct file *target_file = thread_current()->files[fd];
    if (target_file == NULL) return -1;
    return file_write(target_file, buffer, size);
  } else {
    return -1;
  }
}

앞서 fd가 1인 케이스에 대해서만 구현하였다. 다른 케이스에 대해선

Writes size bytes from buffer to the open file fd. Returns the number of bytes actually written, which may be less than size if some bytes could not be written.

라고 하는데.. 앞서 open 구현 시 fd는 3 이상일 때에 대해서만 다루었었다. 따라서 해당 케이스에 대해서만 file_write를 사용하여 써주고, 나머지는 일단 -1일 return하도록 만들어준다. 참고로 fild_write는 file.c에 있는 함수로, file.c 코드를 읽다보면 대충 syscall의 함수들에 뭐를 가져다가 써야하는지 운이 좋게 얻어 걸리는 경우도 있었다. 이것도 일단 이쯤 해두고 넘어가자(당장 특별한 뭔가는 잘 모르겠으니 테케의 논리를 직접 따라가 보는게 더 나을 것 같아)

 

void seek (int fd, unsigned position)

Changes the next byte to be read or written in open file fd to position. ~~ These semantics are implemented in the file system and do not require any special effort in system call implementation.<< 감사하다..

file.c에 seek를 찾아보면 file_seek 함수를 찾을 수 있는데,

/* Sets the current position in FILE to NEW_POS bytes from the
   start of the file. */
void
file_seek (struct file *file, off_t new_pos)
{
  ASSERT (file != NULL);
  ASSERT (new_pos >= 0);
  file->pos = new_pos;
}

이를 일단은 그냥 가져다가 써준다.

void
seek (int fd, unsigned position)
{
  check (position);
  if (thread_current()->files[fd] == NULL) exit(-1);
  file_seek (thread_current()->files[fd], position);
}

 

unsigned tell (int fd)

Returns the position of the next byte to be read or written in open file fd, expressed in bytes from the beginning of the file.

역시나 file.c에 tell을 찾아보면 file_tell 함수를 쉽게 찾을 수 있다.

/* Returns the current position in FILE as a byte offset from the
   start of the file. */
off_t
file_tell (struct file *file) 
{
  ASSERT (file != NULL);
  return file->pos;
}

이를 가져다가 써준다.

unsigned
tell (int fd)
{
  if (thread_current()->files[fd] == NULL) exit(-1);
  return file_tell (thread_current()->files[fd]);
}

 

우선 make check를 하고 output log를 확인해보자.

 

9 of 76 tests failed.

생각보다.. 나쁘지 않은데?사실 지금 정말 너무 피곤해서 그냥 이대로 버리고싶다...

그렇지만 일단 fd==0일때 구현을 얼레벌레 넘겼고 몇가지는 그냥 대충 메뉴얼에서 시키는대로만 대충 만들었고.. 여기저기 구멍이 많다. 이걸 언제 다 메꾸나.. 아무튼 실패한 테케들을 쭉 적어보자면

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-jump

FAIL tests/userprog/no-vm/multi-oom

FAIL tests/filesys/base/syn-read

FAIL tests/filesys/base/syn-write

일단 근데 저 syn-read와 syn-write가 눈에 띈다. 뭔가.. read와 write에도 싱크로나이즈가 필요한건지. 메뉴얼에 관련 키워드를 찾으니 이런 이야기가 써져 있었네..

In particular, it is not safe to call into the file system code provided in the ‘filesys’ directory from multiple threads at once. Your system call implementation must treat the file system code as a critical section. Don’t forget that process_execute() also accesses files. For now, we recommend against modifying code in the ‘filesys’ directory.

아하.. file system code들을 critical section으로 취급하고, 일단 지금은 filesys 디렉토리의 코드들은 그대로 두기를 권장한다

시키는대로 file system과 관여하는 코드들에 semaphore을 추가해주었다

// semaphore 정의와 초기화
struct semaphore sync_file;

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
  sema_init (&sync_file, 1);
}
pid_t
exec (const char *file)
{
  sema_down (&sync_file);
  pid_t p = process_execute(file);
  sema_up (&sync_file);
  return p;
}

bool
create (const char *file, unsigned initial_size)
{
  if (file == NULL) exit(-1);
  sema_down (&sync_file);
  bool c = filesys_create(file, initial_size);
  sema_up (&sync_file);
  return c;
}

bool
remove (const char *file)
{
  if (file == NULL) exit(-1);
  sema_down (&sync_file);
  bool r = filesys_remove(file);
  sema_up (&sync_file);
  return r;
}

int
open (const char *file)
{
  if (file == NULL) return -1;
  sema_down (&sync_file);
  struct file *f = filesys_open(file);
  sema_up (&sync_file);
  struct file **files = thread_current()->files;
  if (f == NULL) {
    return -1;
  } else {
    int i;
    for (i = 3; i < 128; i++){
      if (files[i] == NULL) {
        thread_current()->fd = i;
        files[i] = f;
        break;
      }
    }
  }
  //printf("return fd value %d\n", thread_current()->fd);
  return thread_current()->fd;
}

int
filesize (int fd)
{
  if (thread_current()->files[fd] == NULL)  exit(-1);
  //printf("file size is %d, from syscall.c\n", file_length (thread_current()->files[fd]));
  sema_down (&sync_file);
  int length = file_length (thread_current()->files[fd]);
  sema_up (&sync_file);
  return length;
}

int
read (int fd, void *buffer, unsigned length)
{
  check (buffer);
  check (length);
  if (thread_current()->files[fd] == NULL)  return -1;
  if (fd == 0) {
    *(char *) buffer = input_getc ();
    //int i = 0;
    //for (i; i < length; i++) buffer[i] = input_getc ();
    printf("case of fd == 0\n");
    return length;
  } else if (fd > 2) {
    //printf("fd: %d\n", fd);
    sema_down (&sync_file);
    int r = file_read (thread_current()->files[fd], buffer, length);
    sema_up (&sync_file);
    return r;
  }
  return -1;
}

int
write (int fd, const void *buffer, unsigned size)
{
  check (buffer);
  check (size);
  if (fd == 1) {
    //writes to console: call putbuf(buffer, size) and return size
    putbuf(buffer, size);
    return size;
  } else if (fd > 2) {
    struct file *target_file = thread_current()->files[fd];
    if (target_file == NULL) return -1;
    sema_down (&sync_file);
    int w = file_write(target_file, buffer, size);
    sema_up (&sync_file);
    return w;
  } else {
    return -1;
  }
}

void
seek (int fd, unsigned position)
{
  check (position);
  if (thread_current()->files[fd] == NULL) exit(-1);
  sema_down (&sync_file);
  file_seek (thread_current()->files[fd], position);
  sema_up (&sync_file);
}

unsigned
tell (int fd)
{
  if (thread_current()->files[fd] == NULL) exit(-1);
  sema_down (&sync_file);
  unsigned t = file_tell (thread_current()->files[fd]);
  sema_up (&sync_file);
  return t;
}

void
close (int fd)
{
  if (thread_current()->files[fd] == NULL) exit(-1);
  sema_down (&sync_file);
  file_close(thread_current()->files[fd]);
  thread_current()->files[fd] = NULL;
  sema_up (&sync_file);
}

아 뭐지 글이 너무 길어지는 것 같은데..

여하튼 다시 make check의 로그를 확인해보자

여전히 9개의 테케가 fail이지만.. 이전과 fail이 뜬 테케가 조금 달라졌다

우선 새롭게 실패한 tests/userprog/write-bad-ptr.. 뭐가 문제니

따로 돌려본다

pintos -v -k -T 60 --bochs  --filesys-size=2 -p tests/userprog/write-bad-ptr -a write-bad-ptr -p ../../tests/userprog/sample.txt -a sample.txt -- -q  -f run write-bad-ptr < /dev/null 2> tests/userprog/write-bad-ptr.errors |tee tests/userprog/write-bad-ptr.output

타임아웃이 뜨네. 어디서 sema가 잘못 걸리고있나보다

Erasing ustar archive...
Executing 'write-bad-ptr':
(write-bad-ptr) begin
(write-bad-ptr) open "sample.txt"
line 222
line 224
write-bad-ptr: exit(-1)
^C

왜 끝나지 못하고 cpu timeout이 나는가 했더니 exit을 못하고 있었다. 그렇다. exit에는 sync_file을 추가해주지 않았다. 고쳐준다.

void
exit (int status) 
{
  sema_up (&sync_file);
  struct thread *cur = thread_current();
  cur->exit_status = status;
  printf("%s: exit(%d)\n", cur->name, status);
  int i;
  for (i = 3; i < 128; i++) {
    if (cur->files[i] != NULL) close(i);
  }
  thread_exit();
}

여담인데 어디서 문제가 났는지 여기저기 확인하다보니 dst에 들어있는 문자는 "Amazing Electronic Fact: If you scuffed your feet long enough without touching anything, you would build up so many electrons that your finger would explode!  But this is nothing to worry about unless you have carpeting." --Dave Barry 인가보다? bom lab 할 때 생각나고 재밌다ㅋㅋㅋ

 

그 다음 고쳐야 할 것은 bad-read와 bad-write. 갑자기 얘들이 왜 fail이 떴는지 당최 모르겠다.. bad-read가 해야 할 일을 보니 This program attempts to read memory at an address that is not mapped. This should terminate the process with a -1 exit code.

로그의 에러 코드를 보면 페이지 폴트가 났는데, 원인이 not present error writing page in user context.이다. exception.c의 page_fault에 not present하면 exit(-1)하도록 조건을 추가해준다(일단은)

if (!user || is_kernel_vaddr (fault_addr) || not_present)  exit(-1);

해결이 된다. 뭔가 프젝3에서 뜯어고쳐야 할 것 같은 불길한 예감이 들지만..

사실 처음부터 저렇게 page_fault 함수에 넣은 건 아니고.. pagedir의 여러 함수를 뜯어봤지만 load가 안되는 문제여서 그런가 다 page_fault를 피할 수 없었다. 그래서 불가피하게. 아무튼.

 

그다음 눈에 띄는건 syn-write다. syn-read는 해결되었는데 얜 뭐가 문제지. 로그를 열어본다.

Executing 'syn-write':
(syn-write) begin
(syn-write) create "stuff"
(syn-write) exec child 1 of 10: "child-syn-wrt 0"
(syn-write) exec child 2 of 10: "child-syn-wrt 1"
(syn-write) exec child 3 of 10: "child-syn-wrt 2"
(syn-write) exec child 4 of 10: "child-syn-wrt 3"
load: child-syn-wrt: open failed
child-syn-wrt: exit(1)
child-syn-wrt: exit(0)
(syn-write) exec child 5 of 10: "child-syn-wrt 4"
(syn-write) exec child 6 of 10: "child-syn-wrt 5"
child-syn-wrt: exit(2)
(syn-write) exec child 7 of 10: "child-syn-wrt 6"
(syn-write) exec child 7 of 10: "child-syn-wrt 6": FAILED
syn-write: exit(1)
Execution of 'syn-write' complete.

syn-write.c를 열어본다. Spawns several child processes to write out different parts of the contents of a file and waits for them to finish.  Then reads back the file and verifies its contents. 여러 자식 프로세스를 생성하여 파일 내용의 다른 부분을 작성하고 완료될 때까지 기다린 후, 파일을 다시 읽고 내용을 확인한다. 그러니까 open failed가.. 싱크가 안돼서 일어난 일이라는 건가? filesys_open에서 에러가 났을 부분 load에 for 루프를 넣고 결과를 확인한다.

//in load
  /* Open executable file. */
  file = filesys_open (file_name);
  if (file == NULL) 
    {
      int i = 0;
      for (i; i<10000000; i++) ;
      printf ("load: %s: open failed\n", file_name);
      goto done; 
    }
Executing 'syn-write':
(syn-write) begin
(syn-write) create "stuff"
(syn-write) exec child 1 of 10: "child-syn-wrt 0"
(syn-write) exec child 2 of 10: "child-syn-wrt 1"
(syn-write) exec child 3 of 10: "child-syn-wrt 2"
(syn-write) exec child 4 of 10: "child-syn-wrt 3"
(syn-write) exec child 5 of 10: "child-syn-wrt 4"
child-syn-wrt: exit(1)
child-syn-wrt: exit(0)
(syn-write) exec child 6 of 10: "child-syn-wrt 5"
child-syn-wrt: exit(2)
(syn-write) exec child 7 of 10: "child-syn-wrt 6"
(syn-write) exec child 8 of 10: "child-syn-wrt 7"
child-syn-wrt: exit(4)
(syn-write) exec child 9 of 10: "child-syn-wrt 8"
(syn-write) exec child 10 of 10: "child-syn-wrt 9"
child-syn-wrt: exit(5)
(syn-write) wait for child 1 of 10 returned 0 (expected 0)
(syn-write) wait for child 2 of 10 returned 1 (expected 1)
child-syn-wrt: exit(6)
(syn-write) wait for child 3 of 10 returned 2 (expected 2)
child-syn-wrt: exit(7)
child-syn-wrt: exit(8)
child-syn-wrt: exit(9)
load: child-syn-wrt: open failed
(syn-write) wait for child 4 of 10 returned 0 (expected 3)
(syn-write) wait for child 4 of 10 returned 0 (expected 3): FAILED
syn-write: exit(1)
Execution of 'syn-write' complete.

load fail 문제가 일어나지 않는다.. 결국 싱크가 전혀 되고 있지 않다는 뜻이다. syscall.c를 쭉 보니 sync_file semaphore도 (아마) 빠진 부분 없이 다 넣어준 것 같은데 뭐가 문제지. 아무래도 "여러 자식 프로세스를 생성하여 파일 내용의 다른 부분을 작성하고 완료될 때까지 기다린 후" 이 부분이 문제가 되고 있는 것 같다. 메뉴얼을 열어 읽어본다..

You can use file_deny_write() to prevent writes to an open file.

이걸 파보라는 소리같다. 일단 찾아보니 file_deny_write는 file.c에 있다.

// file.c
/* Prevents write operations on FILE's underlying inode
   until file_allow_write() is called or FILE is closed. */
void
file_deny_write (struct file *file) 
{
  ASSERT (file != NULL);
  if (!file->deny_write) 
    {
      file->deny_write = true;
      inode_deny_write (file->inode);
    }
}

/* Re-enables write operations on FILE's underlying inode.
   (Writes might still be denied by some other file that has the
   same inode open.) */
void
file_allow_write (struct file *file) 
{
  ASSERT (file != NULL);
  if (file->deny_write) 
    {
      file->deny_write = false;
      inode_allow_write (file->inode);
    }
}

 

Putting 'syn-read' into the file system...
Putting 'child-syn-read' into the file system...
Erasing ustar archive...
Executing 'syn-read':
line 137 in process.c(in start_process), thread syn-read sema val 0
line 139 in process.c(in start_process), thread syn-read sema val 0
(syn-read) begin
(syn-read) create "data"
(syn-read) open "data"
(syn-read) write "data"
(syn-read) close "data"
(syn-read) exec child 1 of 10: "child-syn-read 0"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
(syn-read) exec child 2 of 10: "child-syn-read 1"
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 3 of 10: "child-syn-read 2"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 4 of 10: "child-syn-read 3"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 5 of 10: "child-syn-read 4"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 6 of 10: "child-syn-read 5"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
(syn-read) exec child 7 of 10: "child-syn-read 6"
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 8 of 10: "child-syn-read 7"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 9 of 10: "child-syn-read 8"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
(syn-read) exec child 10 of 10: "child-syn-read 9"
line 131 in syscall.c(in exex fun), thread syn-read sema val 0
line 133 in syscall.c(in exex fun), thread syn-read sema val 0
line 137 in process.c(in start_process), thread child-syn-read sema val 0
line 139 in process.c(in start_process), thread child-syn-read sema val 0
child-syn-read: exit(3)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
child-syn-read: exit(8)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
child-syn-read: exit(0)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
child-syn-read: exit(2)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
(syn-read) wait for child 1 of 10 returned 0 (expected 0)
child-syn-read: exit(6)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
child-syn-read: exit(5)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
child-syn-read: exit(1)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
(syn-read) wait for child 2 of 10 returned 1 (expected 1)
(syn-read) wait for child 3 of 10 returned 2 (expected 2)
(syn-read) wait for child 4 of 10 returned 3 (expected 3)
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
child-syn-read: exit(9)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
child-syn-read: exit(4)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
(syn-read) wait for child 5 of 10 returned 4 (expected 4)
(syn-read) wait for child 6 of 10 returned 5 (expected 5)
(syn-read) wait for child 7 of 10 returned 6 (expected 6)
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
child-syn-read: exit(7)
line 119 in syscall.c(before thread_exit), thread child-syn-read sema val 0
line 193 in process.c(in process_exit), thread child-syn-read wait_clean val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
(syn-read) wait for child 8 of 10 returned 7 (expected 7)
(syn-read) wait for child 9 of 10 returned 8 (expected 8)
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
(syn-read) wait for child 10 of 10 returned 9 (expected 9)
(syn-read) end
syn-read: exit(0)
line 119 in syscall.c(before thread_exit), thread syn-read sema val 0
line 193 in process.c(in process_exit), thread syn-read wait_clean val 0
line 210 in process.c(after destroy page), thread child-syn-read sema val 0
line 212 in process.c(after destroy page), thread child-syn-read sema val 0
Execution of 'syn-read' complete.
line 210 in process.c(after destroy page), thread syn-read sema val 0
line 212 in process.c(after destroy page), thread syn-read sema val 0

 

// pintos/src/tests/lib.c
void
exec_children (const char *child_name, pid_t pids[], size_t child_cnt) 
{
  size_t i;

  for (i = 0; i < child_cnt; i++) 
    {
      char cmd_line[128];
      snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i);
      CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR,
             "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line);
    }
}

void
wait_children (pid_t pids[], size_t child_cnt) 
{
  size_t i;
  
  for (i = 0; i < child_cnt; i++) 
    {
      int status = wait (pids[i]);
      CHECK (status == (int) i,
             "wait for child %zu of %zu returned %d (expected %zu)",
             i + 1, child_cnt, status, i);
    }
}

'Operating System' 카테고리의 다른 글

[Pintos] project2-1 syscall 일부  (0) 2023.05.01