Operating System

[Pintos] project2-1 syscall 일부

star.candy 2023. 5. 1. 00:52

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

 

2-1에서 구현해야 할 syscall는 halt, exit, create, open, close, write

src/lib/user/syscall.c를 참고하여 각 함수들로 보내는 값들을 먼저 적어준다

argument의 개수에 따라 syscall0, syscall1 등으로 나눠져있는걸 확인할 수 있는데, 프로젝트 ppt에 따르면,

1: argument is located in esp+1
2: arguments are located in esp+4 (arg0), esp+5 (arg1)
3: arguments are located in esp+5 (arg0), esp+6 (arg1), esp+7 (arg2)

이를 참고하여 적어준다

예를 들어 halt의 경우 `passing no arguments`이므로 그냥 halt()만 불러주고, create의 경우 두개의 argument를 가지므로 create((const char *)* (uint32_t*)(f->esp+16), (unsigned *)*(uint32_t *)(f->esp+20));으로...

return value가 있을 경우 f->eax에 저장해준다

static void
syscall_handler (struct intr_frame *f) 
{
  switch(*(uint32_t *)(f->esp)) {
    // halt(), exit(), create(), open(), close(), write()
    case SYS_HALT:
      halt();
      break;
    case SYS_EXIT:
      exit(*(uint32_t *)(f->esp+4));
      break;
    case SYS_EXEC:
      break;
    case SYS_WAIT:
      break;
    case SYS_CREATE:
      f->eax = create((const char *)* (uint32_t*)(f->esp+16), (unsigned *)*(uint32_t *)(f->esp+20));
      break;
    case SYS_REMOVE:
      break;
    case SYS_OPEN:
      f->eax = open((const char*)*(uint32_t *)(f->esp + 4));
      break;
    case SYS_FILESIZE:
      break;
    case SYS_READ:
      break;
    case SYS_WRITE:
      f->eax = write((int)*(uint32_t *)(f->esp+20), (void *)*(uint32_t *)(f->esp + 24), (unsigned)*((uint32_t *)(f->esp + 28)));
      break;
    case SYS_SEEK:
      break;
    case SYS_TELL:
      break;
    case SYS_CLOSE:
      close((const char*)*(uint32_t *)(f->esp + 4));
      break;
  }
}

설명서를 보면서 함수를 하나씩 작성

void halt (void)

shutdown_power_off()를 부르며 terminate

void
halt (void)
{
  shutdown_power_off();
}

void exit (int status)

status를 반환하며 current user program terminate

make check를 해서 output파일을 확인해보면 알 수 있지만, args-many: exit(0) 같은 걸 출력해줘야 한다(diff 어쩌구 해서 안하면 FAIL이 뜸)

저기서 args-many는 thread structure에 name으로 초기화되는 걸 알 수 있는데, 따라서 출력문을 적어준다

void
exit (int status) 
{
  struct thread *cur = thread_current();
  printf("%s: exit(%d)\n", thread_name(), status);
  thread_exit();
}

bool create (const char *file, unsigned initial_size)

Creates a new file called file initially initial size bytes in size. Returns true if successful, false otherwise. 단, 생성하기만하고 open과는 다른 얘기다. 즉 새로운 파일을 생성한다는 것이 open한다는 것과는 다른 이야기

file관련 함수들이 어떻게 돌아가는지 알기 위해 src/filesys/filesys.c를 살펴본다

/* Creates a file named NAME with the given INITIAL_SIZE.
   Returns true if successful, false otherwise.
   Fails if a file named NAME already exists,
   or if internal memory allocation fails. */
bool
filesys_create (const char *name, off_t initial_size) 
{
  block_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;
}

여기서 써야하는 함수는 아마 이녀석이지 싶다

initial_size의 file을 만들고, success 여부를 return한다

bool
create (const char *file, unsigned initial_size)
{
  return filesys_create(file, initial_size);
}

가져와서 써준다.  그 외에 특별한 설명은 없으니 일단 넘어가고...

int open (const char *file)

Opens the file called file. Returns a nonnegative integer handle called a “file descriptor” (fd), or -1 if the file could not be opened.

fd라는 nonnegative integer을 다룬다. fd 0 (STDIN_FILENO) 은 standard input, fd 1 (STDOUT_FILENO)은 standard output이며 open 함수는 두 값은 return하지는 않는다.. 라고 나와있지만 어떻게 하라는 것인지... pdf는 솔직히 설명이 좀 불친절할 때도 많은 것 같다 우선 file descriptor가 무엇인지 찾아보았다.

https://twofootdog.tistory.com/51

 

파일 디스크립터(File Descriptor) 란 무엇인가?

1. 개념 파일 디스크립터(File Descriptor)란 리눅스 혹은 유닉스 계열의 시스템에서 프로세스(process)가 파일(file)을 다룰 때 사용하는 개념으로, 프로세스에서 특정 파일에 접근할 때 사용하는 추상

twofootdog.tistory.com

file descriptor란 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값으로, 0이 아닌 정수값을 갖는다고 한다. 프로세스가 파일을 open하면 해당 프로세스의 file descriptor 중 사용되지 않은 가장 작은 값을 할당한다. 그 다음 프로세스가 열려있는 파일에 접근할 때, file descriptor 값을 이용해서 파일을 지칭한다.
기본적으로 할당되는 fd 값은 표준입력(Standard Input), 표준 출력(Standard Output), 표준에러(Standard Error)에 각각 0, 1, 2로 할당된다. 아래 FAQ에보면 You may impose a limit of 128 open files per process, if necessary 라고 하니 3부터 128까지(당연한 소리지만, index로는 127까지겠죠)로 생각한다.

외에 pdf에 나와있는 내용을 읽어보자면.. 각각의 process 는 independent set of file descriptors를 가지고있으며, 이는 child processes에 inherited 되지 않는다.

Single file이 한번 이상 열렸을 때, single process인지 different processes인지와 관련 없이 open은 새로운 file descriptor를 return한다. 그리고 이것들은 independently하게 닫힌다(?)

우선 fd를 정의해야 할 것 같다. 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.*/
#endif
// init_thread in thread.c
  #ifdef USERPROG
    /* Owned by userprog/process.c. */
    int i = 0;
    for (i; i < 128; i++) t->files[i] = NULL;
    t->fd = 0;
  #endif

src/filesys/filesys.c로 가서 file open과 관련된 함수가 있는지 찾아본다

/* Opens the file with the given NAME.
   Returns the new file if successful or a null pointer
   otherwise.
   Fails if no file named NAME exists,
   or if an internal memory allocation fails. */
struct file *
filesys_open (const char *name)
{
  struct dir *dir = dir_open_root ();
  struct inode *inode = NULL;

  if (dir != NULL)
    dir_lookup (dir, name, &inode);
  dir_close (dir);

  return file_open (inode);
}

file_open (inode)를 return한다. file_open이 뭘 하는지 한번 봐야할 것 같다... src/filesys/filesys.c내에는 없다. 그러므로 grep file_open -R .

src/filesys/file.c에 있다고 한다.

file_open에 대한 설명을 보면,

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

allocation이 fail하거나 INODE가 null일때는 null pointer을 반환, 아닐 경우 new file을 반환

그니까 file을 못열었다 > file_open은 null pointer을 return > filesys_open도 null pointer을 return

따라서 이를 반영해서 만들어준다

int
open (const char *file)
{
  if (file == NULL) return -1;
  struct file *f = filesys_open(file);
  struct file **files = thread_current()->files;
  if (f == NULL) {
    // file을 열지 못했으면 return -1
    return -1;
  } else {
    int i;
    for (i = 3; i < 128; i++){
      if (files[i] == NULL) {
        thread_current()->fd = i;
        files[i] = f;
        break;
      }
    }
  }
  return thread_current()->fd;
}

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

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.

project 2-1에서는 If fd == 1, writes to console: call putbuf(buffer, size) and return size 만 구현하라고한다

int
write (int fd, const void *buffer, unsigned size)
{
  if (fd == 1) {
    //writes to console: call putbuf(buffer, size) and return size
    putbuf(buffer, size);
  }
  return size;
}

void close (int fd)

Closes file descriptor fd. Exiting or terminating a process implicitly closes all its open file descriptors, as if by calling this function for each one.

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

받은 fd의 file을 닫는다.

Exiting or terminating a process implicitly closes all its open file descriptors, as if by calling this function for each one.

아하... exit에서 하나하나 다 닫아주어야 하나보다. 반영하여 exit도 고쳐준다

void
exit (int status) 
{
  struct thread *cur = thread_current();
  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();
}

 

 

 

make check로 현재까지 상황 확인

가장 먼저 FAIL이 뜬

FAIL tests/userprog/sc-bad-sp
FAIL tests/userprog/sc-bad-arg

에 대해서 살펴보자

먼저 src/tests/userprog/sc-bad-arg.c를 읽어본다

/* Sticks a system call number (SYS_EXIT) at the very top of the
stack, then invokes a system call with the stack pointer
(%esp) set to its address. The process must be terminated
with -1 exit code because the argument to the system call
would be above the top of the user address space. */

user address space을 침범하면 exit(-1)해야한다. src/threads/vaddr.h에서 관련 함수를 찾을 수 있는데.. 

/* Returns true if VADDR is a user virtual address. */
static inline bool
is_user_vaddr (const void *vaddr) 
{
  return vaddr < PHYS_BASE;
}

/* Returns true if VADDR is a kernel virtual address. */
static inline bool
is_kernel_vaddr (const void *vaddr) 
{
  return vaddr >= PHYS_BASE;
}

따라서 esp와 PHYS_BASE를 비교하는 함수를 만들어준다

void
check(const void *address) {
  if (!is_user_vaddr (address))
    exit(-1);
}

syscall_handler로 돌아가서 체크해준다..

sc-bad-arg는 통과했다! 이제 sc-bad-sp이 문제인데

/* Invokes a system call with the stack pointer (%esp) set to a
bad address. The process must be terminated with -1 exit
code.

For Project 3: The bad address lies approximately 64MB below
the code segment, so there is no ambiguity that this attempt
must be rejected even after stack growth is implemented.
Moreover, a good stack growth heuristics should probably not
grow the stack for the purpose of reading the system call
number and arguments. */

솔직히 뭔소리인지....수면시간부족으로 한국어도 못읽겠는데

에러 부분을 살펴보면

FAIL
Kernel panic in run: PANIC at ../../userprog/exception.c:100 in kill(): Kernel bug - unexpected interrupt in kernel

음.. exception.c:100의 unexpected interrupt in kernel이 문제라고 한다. 해당 부분을 살펴본다.

case SEL_KCSEG:
      /* Kernel's code segment, which indicates a kernel bug.
         Kernel code shouldn't throw exceptions.  (Page faults
         may cause kernel exceptions--but they shouldn't arrive
         here.)  Panic the kernel to make the point.  */
      intr_dump_frame (f);
      PANIC ("Kernel bug - unexpected interrupt in kernel");

여기서 걸렸군... kill이라는 함수에서 f->cs의 스위치문에서 빠져나왔다(여기서 f->cs란

uint16_t cs, :16; /* Code segment for eip. */

를 이야기한다..) SEL_KCSEG가 무엇인가 하니 #define SEL_KCSEG       0x08    /* Kernel code selector. */ 라고 정의되어있다. 그럼 이 함수로 넘어오는 어딘가에서 처리를 해주면 될 것 같다는 감이 온다.. kill 함수가 쓰이는 곳을 grep으로 찾아보니 같은 파일 내의 page_fault에서 사용하는데.. page_fault가 어떻게 돌아가는지 봐야할 거 같다

static void
page_fault (struct intr_frame *f) 
{
  bool not_present;  /* True: not-present page, false: writing r/o page. */
  bool write;        /* True: access was write, false: access was read. */
  bool user;         /* True: access by user, false: access by kernel. */
  void *fault_addr;  /* Fault address. */

  /* Obtain faulting address, the virtual address that was
     accessed to cause the fault.  It may point to code or to
     data.  It is not necessarily the address of the instruction
     that caused the fault (that's f->eip).
     See [IA32-v2a] "MOV--Move to/from Control Registers" and
     [IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception
     (#PF)". */
  asm ("movl %%cr2, %0" : "=r" (fault_addr));

  /* Turn interrupts back on (they were only off so that we could
     be assured of reading CR2 before it changed). */
  intr_enable ();

  /* Count page faults. */
  page_fault_cnt++;

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

  /* 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");
  
  if (!user || is_kernel_vaddr (fault_addr))  exit(-1);

  kill (f);
}

kill 이전에 fault_addr이 kernel virtual address거나 access by kernel일 때 올바르게 exit(-1)하도록 만들어준다

다시 make check

아직도 FAIL이다; result 파일을 보자

FAIL
Test output failed to match any acceptable form.

Acceptable output:
  (sc-bad-sp) begin
  sc-bad-sp: exit(-1)
Differences in `diff -u' format:
  (sc-bad-sp) begin
+ Page fault at 0x40480a3: not present error reading page in kernel context.
  sc-bad-sp: exit(-1)

아하... 저 printf가 출력이 되면 안되는구나

printf 이전에 exit(-1)하게 다시 써준다

잘 통과한다

 

다음 fail

FAIL tests/userprog/create-null

create-null을 열어본다

// create_null.c
/* Tries to create a file with the null pointer as its name.
   The process must be terminated with exit code -1. */

#include "tests/lib.h"
#include "tests/main.h"

void
test_main (void) 
{
  msg ("create(NULL): %d", create (NULL, 0));
}

NULL인 file을 create하려고 하면 exit(-1)한다

반영하여 고쳐준다

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

잘 통과한다

 

다음 fail

FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice

왜지.. close normal result를 열어보니 커널패닉이 났다

Kernel PANIC at ../../filesys/inode.c:336 in inode_allow_write(): assertion `inode->deny_write_cnt <= inode->open_cnt' failed.

해당 위치로 가본다...

/* Re-enables writes to INODE.
   Must be called once by each inode opener who has called
   inode_deny_write() on the inode, before closing the inode. */
void
inode_allow_write (struct inode *inode) 
{
  ASSERT (inode->deny_write_cnt > 0);
  ASSERT (inode->deny_write_cnt <= inode->open_cnt);
  inode->deny_write_cnt--;
}

deny_write_cnt와 open_cnt의 쓰임새를 알아야겠다. inode의 구조를 본다

/* In-memory inode. */
struct inode 
  {
    struct list_elem elem;              /* Element in inode list. */
    block_sector_t sector;              /* Sector number of disk location. */
    int open_cnt;                       /* Number of openers. */
    bool removed;                       /* True if deleted, false otherwise. */
    int deny_write_cnt;                 /* 0: writes ok, >0: deny writes. */
    struct inode_disk data;             /* Inode content. */
  };

open_cnt는 number of openers고 deny_write_cnt는 값이 0일 때는 write하고 0보다 클 시에는 deny한다. 각각 1과 0으로 초기화 되어 있다. 대체 어디서 걸리는건지 알기 위해 함수들을 보면

ASSERT (inode->deny_write_cnt <= inode->open_cnt);에서 걸렸다 > inode.c의 inode_allow_write로 넘어왔다 > file.c의 file_allow_write에서 넘어왔다 > file.c의 file_close에서 넘어왔다 > syscall.c에서 구현한 void close (int fd)에서 넘어왔다

설명이 좀 이상한데 아무튼 저런 과정으로 커널 패닉이 만들어졌다

뭐가 문제인지 알기 위해서 각 함수들에 printf를 넣어서 확인한다 (더 현명한 방법과 사고 흐름이 분명 있을텐데 지금 머리가 영 안돌아가서 무식한 방법으로 한다..)

void
file_close (struct file *file) 
{
  printf("file close called\n");
  if (file != NULL)
    {
      file_allow_write (file);
      inode_close (file->inode);
      free (file); 
    }
}
void
file_allow_write (struct file *file) 
{
  printf("file allow write called\n%d\n", file->deny_write);
  ASSERT (file != NULL);
  if (file->deny_write) 
    {
      file->deny_write = false;
      inode_allow_write (file->inode);
    }
}
void
inode_allow_write (struct inode *inode) 
{
  printf("inode allow write\n");
  ASSERT (inode->deny_write_cnt > 0);
  ASSERT (inode->deny_write_cnt <= inode->open_cnt);
  inode->deny_write_cnt--;
}

커널 패닉 전후의 출력을 살펴보면

(close-normal) end
close-normal: exit(0)
file close called
file allow write called
204
inode allow write
Kernel PANIC at ../../filesys/inode.c:338 in inode_allow_write(): assertion `inode->deny_write_cnt <= inode->open_cnt' failed.

어라... 저기서 왜 file close가 호출되고 file->deny_write값은 또 204..

아마 close에서 직접 thread_current()->files[fd]을 NULL로 설정해줘야 하나보다

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

 

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/sc-bad-sp
pass tests/userprog/sc-bad-arg
pass tests/userprog/sc-boundary
pass tests/userprog/sc-boundary-2
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
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
pass tests/userprog/close-twice
pass tests/userprog/close-stdin
pass tests/userprog/close-stdout
pass tests/userprog/close-bad-fd

어우 힘들어

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

[Pintos] project2-2 syscall 나머지  (0) 2023.06.09