Pintos Projects: User Programs - System Calls

차호성·2024년 8월 8일
0

Pintos

목록 보기
4/7

개요

시스템 콜(system call)은 사용자 프로그램에서 운영체제가 관리 중인 자원이나 서비스를 사용하기 위해 요청하는 것을 말합니다. 이번 프로젝트에서는 프로세스와 파일 입출력에 대한 시스템 콜을 구현합니다.

구현

pintos에서 시스템 콜(system call)은 인터럽트(interrupt)로 등록되어 있습니다. 따라서 사용자 프로그램에서 시스템 콜을 호출하면 인터럽트가 발생하고, 등록된 인터럽트 처리기(handler)가 실행됩니다.

시스템 콜 구분하기

/* syscall.c */

static void
syscall_handler (struct intr_frame *f)
{
  int number = 0;
  pop_stack (&f->esp, &number, sizeof number);
  
  switch (number)
    {
      case SYS_HALT:
        break;
      case SYS_EXIT:
        {
          int status;
          pop_stack (&f->esp, &status, sizeof status);
          
          exit (status);
        }
        break;
        ...
    }
}

사용자 프로그램이 시스템 콜을 호출할 때, 넘긴 파라미터는 사용자 스택에 저장됩니다. 그리고 intr_frame 구조체에서 esp 레지스터에 사용자 스택 주소가 저장되어 있습니다. 이것을 통해 사용자 프로그램이 넘긴 파라미터를 읽어올 수 있습니다.

/* syscall.c */

static void
pop_stack (void **esp, void *value, size_t size)
{
  memcpy (value, *esp, size);
  *esp += size;
  if (!is_user_vaddr (*esp))
    exit (-1);
}

사용자 스택에서 시스템 콜 파라미터를 읽어올 때 사용합니다. 만약 스택에서 데이터를 읽다가 잘못된 주소를 읽을 경우, 프로그램을 종료시킵니다.

프로세스 시스템 콜

exec

/* syscall.c */

static tid_t
exec (const char *cmd_line)
{
  if (is_user_vaddr (cmd_line))
    return process_execute (cmd_line);
  else
    return TID_ERROR;
}

프로세스를 실행합니다. 파라미터로 프로그램 이름과 실행인자(argument)가 한 문자열로 오는데, 이때 주소가 올바른 주소인지 확인합니다.

wait

/* syscall.c */

static int
wait (tid_t tid)
{
  return process_wait (tid);
}

자식 프로세스가 종료될 때까지 기다리는 시스템 콜입니다. 따라서 process_wait() 함수에 자식 프로세스 아이디를 파라미터로 보내서 실행합니다.

/* process.c */

int
process_wait (tid_t child_tid)
{
  struct thread *parent = thread_current ();
  struct thread *child = NULL;
  int exit_status = -1;
  
  /* find child thread. */
  for (struct list_elem *cursor = list_begin (&parent->child_list);
       cursor != list_end (&parent->child_list);
       cursor = list_next (cursor)
  )
    {
      struct thread *t = list_entry (cursor, struct thread, child_elem);
      if (t->tid == child_tid)
        {
          child = t;
          break;
        }
    }
    
  if (child != NULL && child->wait.value == 0)
    {
      sema_down (&child->running);
      exit_status = child->exit_status;
      sema_up (&child->wait);
    }
  
  return exit_status;
}

process_wait() 함수에서는 우선 파라미터로 온 프로세스 아이디가 현재 프로스세의 자식 프로세스인지 확인합니다. pintos에서 wait 시스템 콜은 반드시 자식 프로스세만 기다릴 수 있기 때문에 그렇지 않은 경우 -1을 반환해줍니다.

자식 프로세스 리스트에서 해당 프로세스를 찾았다면, 해당 프로세스가 종료되기를 기다립니다. 그리고 자식 프로세스의 종료 상태(exit status)를 가져와서 반환합니다. 이때 예외 상황이 있습니다. 바로 자식 프로세스가 부모 프로세스보다 먼저 실행이 완료 될 경우입니다. 이럴 경우에는 저는 자식 프로세스가 바로 종료되지 않도록 했습니다. 그리고 wait 세마포어를 만들고 초기값을 0으로 설정한 다음, sema_down() 함수를 호출해서 해당 프로세스를 대기 상태로 만들었습니다. 그리고 부모 프로세스가 wait 시스템 콜을 사용해서 해당 프로세스의 종료 상태를 읽어올 때, sema_up() 함수를 호출하여 자식 프로세스가 종료되도록 만들었습니다.

exit

/* syscall.c */

void
exit (int status)
{
  struct thread *t = thread_current ();
  t->exit_status = status;
  printf ("%s: exit(%d)\n", t->name, status);
  thread_exit ();
}

프로세스가 종료될 때, 호출되는 시스템 콜입니다. 프로젝트 요구사항 중에 프로세스의 이름과 종료 상태를 화면에 출력하라는 요구사항이 있으므로 printf() 함수로 프로세스의 이름과 종료 상태를 출력합니다.

파일 시스템 콜

thread

/* thread.h */

...

#define NOFILE 128

struct thread
{
  ...
  
  struct file *open_files[NOFILE];
  
  ...
};

...

extern struct lock file_lock

...

thread 구조체에 open_files 배열을 정의하여 해당 프로세스가 사용 중인 파일 핸들(handle)을 관리합니다. 이때, open_files 배열의 첫 번째와 두 번째 원소는 표준 입력과 표준 출력으로 예약되어 있습니다. 따라서 여기에 파일 입출력 요청이 오면 표준 입출력으로 전환합니다. 그리고 파일 시스템을 사용할 때 발생할 수 있는 경쟁 조건을 방지하기 위해 file_lock을 전역변수로 정의합니다.

create

/* syscall.c */

static bool
create (const char *file, unsigned initial_size)
{
  if (file == NULL)
    exit (-1);
  if (!is_user_vaddr (file))
    return false;
    
  lock_acquire (&file_lock);
  bool result = filesys_create (file, initial_size);
  lock_release (&file_lock);
  return result;
}

파라미터가 유효한 지 검사한 다음에 파일을 생성합니다. 이때 락을 통해서 경쟁 조건을 방지합니다.

remove

/* syscall.c /*

static bool
remove (const char *file)
{
  if (!is_user_vaddr (file))
    return false;
    
  lock_acquire (&file_lock);
  bool result = filesys_remove (file);
  lock_release (&file_lock);
  return result;
}

파라미터가 유효한 지 검사한 다음에 파일을 삭제합니다. 이때 락을 통해서 경쟁 조건을 방지합니다.

open

static int
open (const char *file)
{
  if (file != NULL && is_user_vaddr (file))
    {
      struct thread *t = thread_current ();
      int fd = STDOUT_FILENO + 1;
    
      /* find unused file descriptor. */
      while (fd < NOFILE)
        {
          if (t->open_files[fd] == NULL)
            break;
          else
            fd++;
        }
      
      if (fd < NOFILE)
        {
          lock_acquire (&file_lock);
          t->open_files[fd] == filesys_open (file);
          lock_release (&file_lock);
          if (t->open_files[fd] != NULL)
            return fd;
        }
    }
    
  return -1;
}

우선 파라미터가 유효한 지 검사합니다. 그리고 현재 프로세스의 open_files 배열에서 사용하지 않는 요소의 인덱스를 찾습니다. 찾았다면 이제 파일을 열고 열린 파일 데이터를 open_files 배열에 저장합니다. 그리고 해당 인덱스를 파일 디스크립터로 반환합니다. 만약 데이터를 넣을 공간이 없다면 해당 요청은 실패로 처리하고 -1을 반환합니다. 여기서도 파일을 열 때 경쟁 조건이 발생할 수 있으므로 락을 사용합니다.

filesize

/* syscall.c */

static int
filesize (int fd)
{
  if (fd < 0 || fd >= NOFILE)
    goto error;
  if (fd == STDIN_FILENO || fd == STDOUT_FILENO)
    goto error;
  
  struct thread *t = thread_current ();
  
  if (t->open_files[fd] != NULL)
    return file_length (t->open_files[fd]);
    
error:
  return -1;
}

우선 파라미터의 유효성을 검사하고, 유효하다면 file_length() 함수를 통해 파일 크기를 얻어와서 반환합니다. 유효하지 않을 경우 goto 문을 통해 바로 -1을 반환합니다.

read

/* syscall.c */

static int
read (int fd, void *buffer, unsigned size)
{
  int (fd < 0 || fd >= NOFILE)
    goto error;
  if (!(is_user_vaddr (buffer) && is_user_vaddr (buffer + size)))
    exit (-1);
    
  struct thread *t = thread_current();
  
  if (fd == STDIN_FILENO)
    {
      unsigned read_bytes = 0;
      while (read_bytes < size)
        {
          uint8_t data = input_getc ();
          memcpy ((uint8_t*)buffer + read_bytes, &data, sizeof data);
          read_bytes += sizeof (data);
        }
      return size;
    }
  else if (t->open_files[fd] != NULL)
    return file_read (t->open_files[fd], buffer, size);

error:
  return -1;
}

우선 파라미터의 유효성을 검사합니다. 그 다음에, 읽기를 요청한 fd를 검사합니다. fd가 stdin이라면 pintos에서 제공하는 input_getc() 함수를 사용하여 표준입력에서 데이터를 읽습니다. 그렇지 않은 경우 file_read() 함수를 사용하여 파일에서 데이터를 읽습니다.

write

/* syscall.c */

#define STDOUT_BUFFER_SIZE 512

...

static int
write (int fd, const void *buffer, unsigned size)
{
  if (fd < 0 || fd >= NOFILE)
    goto error;
  if (!(is_user_vaddr (buffer) && is_user_vaddr (buffer + size)))
    goto error;
  
  struct thread *t = thread_current ();
  
  if (fd == STDOUT_FILENO)
    {
      int remaining = size;
      int offset = 0;
      
      while (remaining > STDOUT_BUFFER_SIZE)
        {
          putbuf (buffer + offset, STDOUT_BUFFER_SIZE);
          remaining -= STDOUT_BUFFER_SIZE;
          offset += STDOUT_BUFFER_SIZE;
        }
        
      if (remaining > 0)
        putbuf (buffer + offset, remaining);
        
      return size;
    }
  else if (t->open_files[fd] != NULL)
    return file_write (t->open_files[fd], buffer, size);
    
error:
  return -1;
}

우선 파라미터의 유효성을 검사합니다. 그 다음에, 쓰기를 요청한 fd를 검사합니다. fd가 stdout이라면, pintos에서 제공하는 putbuf() 함수를 사용하여 표준 출력에 데이터를 씁니다. 이때 한번에 너무 많은 데이터를 보내면 오류가 발생할 수도 있기 때문에, STDOUT_BUFFER_SIZE 크기씩 전송합니다. fd가 표준 출력이 아니라면, file_write() 함수를 사용하여 파일에 씁니다.

seek

/* syscall.c */

static void
seek (int fd, unsigned position)
{
  if (fd >= 0 && fd < NOFILE)
    file_seek (thread_current ()->open_files[fd], position);
}

요청된 fd가 유효한지 검사한 다음, 유효하다면 file_seek() 함수를 사용하여 파일의 커서 위치를 요청된 position으로 설정합니다.

tell

/* syscall.c */

static unsigned
tell (int fd)
{
  if (fd >= 0 && fd < NOFILE)
    return file_tell (thread_current ()->open_files[fd]);
  else
    return -1;
}

요청된 fd가 유효한 지 검사한 다음, 유효하다면 file_tell() 함수를 사용하여 파일의 현재 커서의 위치를 읽어옵니다. 그렇지 않다면 -1을 반환합니다.

close

static void
close (int fd)
{
  if (fd >= 0 && fd < NOFILE)
    {
      struct thread *t = thread_current ();
      lock_acquire (&file_lock);
      file_close (t->open_files[fd]);
      lock_release (&file_lock);
      t->open_files[fd] = NULL;
    }
}

요청된 fd가 유효한 지 검사한 다음, 유효하다면 file_close() 함수를 사용하여 파일을 닫습니다. 이때, 락을 사용하여 경쟁 조건을 방지합니다.

Github

link: https://github.com/chahoseong/pintos
branch: project/user-programs

Reference

https://thierrysans.me/CSCC69/projects/WWW/pintos_3.html#SEC32

profile
프로그래머

0개의 댓글