크래프톤 정글/TIL

PintOS 프로젝트 2주차 [User Programs / System Call] (1)

양선규 2024. 5. 31. 21:08
728x90
반응형

95개의 테스트 케이스 중 15개를 실패한 코드입니다. 주의해서 참고하세요.

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/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-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
pass tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/filesys/base/lg-create
FAIL tests/filesys/base/lg-full
FAIL tests/filesys/base/lg-random
FAIL tests/filesys/base/lg-seq-block
FAIL tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
FAIL tests/filesys/base/sm-full
FAIL tests/filesys/base/sm-random
FAIL tests/filesys/base/sm-seq-block
FAIL tests/filesys/base/sm-seq-random
FAIL tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
pass tests/userprog/no-vm/multi-oom
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
15 of 95 tests failed.

 

 

시스템 콜(System call)

- 운영 체제가 제공하는 서비스에 대한 프로그래밍 인터페이스

- 사용자 모드의 프로그램이 커널 기능을 사용할 수 있도록 한다

- 시스템 콜은 커널 모드에서 처리된 후, 사용자 모드로 복귀한다

 

시스템 콜

 

 

유저 프로그램이 함부로 OS에 접근한다면, 보안부터 시작해서 여러가지 문제를 야기할 수 있다. 또한, 유저 프로그램이 하드웨어 단까지 내려가서 작업하는 것은 매우 복잡하다. 이를 위해서 운영체제를 제공했지만 운영체제 내부도 매우 복잡했다.

 

따라서 OS의 중요한 부분은 숨기고 추상화해 인터페이스만 제공한 것이 시스템 콜이다. 시스템 콜을 통해 유저 프로그램은 커널 기능을 쉽게 이용할 수 있게 된다. 유저 프로그램은 평상시엔 커널 영역에 접근하지 못 하다가 커널 영역에 접근해야 할 때 시스템 콜을 호출한다. 그러면 커널 영역에서 작업을 수행하고, 반환값을 유저 프로그램에 전달한다.

 

아래는 유저 프로그램에서 운영체제에 write() 시스템 콜을 요청한 상황이다. 작성한 값을 출력 장치(모니터)에 띄워야 할 수도 있고, 디스크에 저장해야 할 수도 있다. 두 과정 모두 커널의 도움이 필요하다. 따라서 write() 자체가 시스템 콜이며, write()에 넣은 인자가 syscall로 넘어간다.

 

 

시스템 콜 호출 과정

 

1. 유저 프로그램에서 write() 시스템 콜을 호출한다.

2. write() 함수는 받은 인자를 유저 스택에 넣은 후 커널로 진입한다. 이 때 스택 포인터는 인자가 들어간 주소를 가리킨다.

3. Interrupt Vector table에 가면, 각 주소별로 어떤 인터럽트를 실행해야 하는지 매핑되어 있다. 0x30은 시스템 콜 핸들러를 호출하는 주소이다. 따라서 syscall_handler를 호출한다.

4. 이제 도달한 syscall_handler는 우리가 구현해야 할 함수이다. 유저 스택에 number가 있는데, 이 number는 system call number이다. 각 system call은 매핑되는 system call number를 가지고 있으며, number에 맞는 system call이 호출된다.

 

우리는 이 작업을 수행할 수 있도록 system call handler를 구현해야 한다.

 

syscall 명령은 x86-64에서 system call을 호출하는 가장 일반적인 수단이다. ( 이 때, syscall은 어셈블리어 명령어다 )

PintOS에서도 사용자 프로그램은 system call을 호출하기 위해 syscall을 수행한다.

이 때, %rax 레지스터에는 system call number가 있으며 인자는 각각 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 전달된다.

%rdi, %rsi 등등은 어떠한 형식의 값을 항상 의미하는 건 아니며, system call 함수를 호출할 때 들어오는 순서일 뿐이다. 즉, 첫번째 인자는 반드시 %rdi이지만 이 안에 어떤 인자가 있을지는 각 system call마다 다르다.

 

syscall_handler를 구현할 때, 인자로써 인터럽트 프레임이 들어온다. 인터럽트 프레임 안에 레지스터 정보 등 우리가 원하는 값이 모두 들어있기 때문에 인터럽트 프레임은 매우 중요하다. 인터럽트 프레임은 "커널 스택"에 존재하며, caller 함수는 인터럽트 프레임에 callee에게 줄 인자를 전달하기 위해 액세스할 수 있다.

 

 

 

syscall-nr.h

system call 매핑정보

 

syscall-nr.h 파일이다. 이 함수에는 system call과 system call number의 매핑정보가 저장되어 있다. 

halt부터 0, exit는 1, fork는 2 이런 식으로 증가한다. 이번 project2 에서는 13번 CLOSE까지만 진행하면 된다.

 

 

syscall.c

void check_address(void *addr) {
    struct thread *t = thread_current();


    if (!is_user_vaddr(addr) || addr == NULL || pml4_get_page(t->pml4, addr) == NULL)
    {
        exit(-1);
    }
}

 

입력받은 주소가 유저 영역인지 체크하는 함수이다.

 

유저 영역이 아니거나(커널 영역이거나), 주소 자체가 NULL이거나, 현재 스레드의 페이지 테이블에 해당 주소가 매핑되어 있지 않다면 프로그램을 종료한다.

 

사용자 프로그램은 커널 영역에 접근할 수 없다. system call을 통해서도 말이다.

 

 

syscall.c

/* The main system call interface */
void syscall_handler (struct intr_frame *f UNUSED) {
 
    /* rax = 시스템 콜 넘버 */
    int syscall_n = f->R.rax; /* 시스템 콜 넘버 */
    switch (syscall_n)
    {
    case SYS_HALT:
        halt();
        break;
    case SYS_EXIT:
        exit(f->R.rdi);
        break;
    case SYS_FORK:
        f->R.rax = fork(f->R.rdi, f);
        break;
    case SYS_EXEC:
        if (exec(f->R.rdi) == -1)
        {
            exit(-1);
        }
        break;
    case SYS_WAIT:
        f->R.rax = wait(f->R.rdi);
        break;
    case SYS_CREATE:
        f->R.rax = create(f->R.rdi, f->R.rsi);
        break;
    case SYS_REMOVE:
        f->R.rax = remove(f->R.rdi);
        break;
    case SYS_OPEN:
        f->R.rax = open(f->R.rdi);
        break;
    case SYS_FILESIZE:
        f->R.rax = filesize(f->R.rdi);
        break;
    case SYS_READ:
        f->R.rax = read(f->R.rdi, f->R.rsi, f->R.rdx);
        break;
    case SYS_WRITE:
        f->R.rax = write(f->R.rdi, f->R.rsi, f->R.rdx);
        break;
    case SYS_SEEK:
        seek(f->R.rdi, f->R.rsi);
        break;
    case SYS_TELL:
        f->R.rax = tell(f->R.rdi);
        break;
    case SYS_CLOSE:
        close(f->R.rdi);
        break;
    default:
        thread_exit();
        break;
    }
}

 

우선 syscall_handler의 뼈대부터 작성한다. 맨 밑에 있던 printf는 테스트 케이스 통과를 위해선 지워줘야 한다. 출력때문에 테스트에 통과하지 못 하기 때문이다.

 

첫 부분에서 인터럽트 프레임의 rax에 저장된 (f->R.rax) system call number를 가져와서 해당 number에 맞는 system call을 호출하게 된다.

return 값이 있는 system call이라면 다시 f->R.rax에 리턴값을 저장한다. %rax 레지스터는 본래 리턴값을 저장하는 레지스터이기 때문이다.

 

또한 인자는 반드시 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 들어간다. 인자 안에 뭐가 들었는지와는 관계 없이.

 

 

thread.h

/* ----------- added for project.2 ----------- */
  int exit_status;  // exit() 또는 wait() 구현에 사용되는 변수
  struct file **fd_table;  // fd table
  int fd_idx;  // fd index

  struct list child_list; // _fork(), wait() 구현 때 사용
  struct list_elem child_elem; // _fork(), _wait() 구현 때 사용
  struct intr_frame parent_if; // _fork() 구현 때 사용, __do_fork() 함수

  struct semaphore fork_sema;
  struct semaphore wait_sema;
  struct semaphore free_sema;

  struct file *running;


  /* --------------------------------------------- */

 

struct thread, 스레드 구조체이다. 

여기에 필요한 필드들을 미리 선언해두자.

 

 

syscall.c

/* pintos 종료시키는 함수 */
void halt(void){
    power_off();
}

 

halt는 pintos를 종료시키는 함수이다.

간단하게 power_off() 함수를 실행시키기만 하면 된다.

 

 

syscall.c

/* 현재 프로세스를 종료시키는 시스템 콜 */
void exit(int status)
{
    struct thread *t = thread_current();
    t->exit_status = status;

    printf("%s: exit(%d)\n", t->name, status); // Process Termination Message
    /* 정상적으로 종료됐다면 status는 0 */
    /* status: 프로그램이 정상적으로 종료됐는지 확인 */

    thread_exit();
}

 

현재 돌고 있는 프로세스를 종료시키는 시스템 콜이다.

 

스레드의 exit_status를 설정한 후 스레드 이름, status와 함께 종료 메시지를 출력한다.

 

 

thread.c

static void init_thread(struct thread *t, const char *name, int priority) {
 
  생략 ....

  /* ----------- added for Project.2 ----------- */
  t->exit_status = 0;
 
  /* ------------------------------------------- */

}

 

init_thread 함수에서 exit_status를 초기화해 준다.

 

 

syscall.c

/* 파일 생성하는 시스템 콜 */
bool create (const char *file, unsigned initial_size) {
    
    check_address(file);
   
    lock_acquire(&filesys_lock);
   
    if (filesys_create(file, initial_size)) {
        lock_release(&filesys_lock);
        return true;
    } else {
        lock_release(&filesys_lock);
        return false;
    }
}

 

파일을 생성하는 시스템 콜이다.

 

파일명과 size를 인자로 받아서 filesys_create 함수로 파일을 만든다.

파일 관련 함수를 사용하기 전에 lock을 얻고 끝나면 해제한다.

 

파일에 여러 프로세스가 동시에 접근한다면 다양한 문제가 발생할 수 있다. 또한 lock을 걸지 않으면 통과하지 못하는 테스트들도 많다.

 

따라서 file에 대한 lock은 필수사항이다.

 

 

syscall.h

#ifndef USERPROG_SYSCALL_H
#define USERPROG_SYSCALL_H
#include <stdbool.h>
#include "threads/synch.h"
#include "filesys/file.h"
#include "threads/thread.h"

void syscall_init (void);

////
struct lock filesys_lock;

 

syscall.h 헤더 파일에 filesys_lock을 추가해 주자.

 

 

syscall.c

void
syscall_init (void) {
 
   생략....
 
    write_msr(MSR_SYSCALL_MASK,
            FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
       
    lock_init(&filesys_lock);
}

 

syscall_init 함수에서 filesys_lock 초기화도 진행해 주자.

 

 

syscall.c

bool remove (const char *file) {
    check_address(file);

    lock_acquire(&filesys_lock);
   
    if (filesys_remove(file)) {
        lock_release(&filesys_lock);
        return true;
    } else {
        lock_release(&filesys_lock);
        return false;
    }
}

 

파일 이름을 입력받아 파일을 삭제하는 시스템 콜이다.

 

create와 거의 흡사하고 함수명만 다르다.

 

 

syscall.c

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

    check_address(buffer);
    struct file *fileobj = find_file_by_fd(fd);
    int read_count;
    if (fd == STDOUT_FILENO) {

        putbuf(buffer, size);
        read_count = size;
    }  
    else if (fd == STDIN_FILENO) {

        return -1;
    }
    else {

        lock_acquire(&filesys_lock);
        read_count = file_write(fileobj, buffer, size);
        lock_release(&filesys_lock);
    }
}

 

write()는 buffer에 있는 값을 size만큼 출력하는 시스템 콜이다.

 

fd가 표준출력 (1)일 경우 putbuf 함수를 이용해 그대로 출력한다.

fd가 표준입력 (0)일 경우 출력하지 않고 -1을 return한다.

fd가 파일(2이상)일 경우 해당 파일에 값을 쓴다.

PintOS에서는 표준에러(2)를 사용하지 않기 때문에 0, 1만 처리해주면 된다.

 

write()함수는 PintOS 테스트 케이스에서도 이용한다. 아래 사진처럼 출력을 비교해서 테스트 케이스 pass 여부를 검사하는데, 이때 write()가 사용된다.

 

테스트 일부

 

따라서 write()함수를 구현하지 않으면 어떤 테스트도 통과할 수 없다.

 

 

process.c

tid_t
process_create_initd (const char *file_name) {
    char *fn_copy;
    tid_t tid;

   .... 생략

    char *parsing;
    /*file_name을 받아와서 null 기준으로 문자열 파싱*/
    strtok_r(file_name," ", &parsing);

    /* Create a new thread to execute FILE_NAME. */
    tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
    if (tid == TID_ERROR)
        palloc_free_page (fn_copy);
    return tid;
}

 

그리고 argument passing에서 추가하지 않았던 코드를 추가해야 한다.

char *parsing;

strtok_r(file_name, " ", &parsing);

 

process_exec 뿐만 아니라 여기서도 파싱해주지 않으면 명령어 인자까지 그대로 thread_create 함수로 들어가기 때문에 오류가 생길 수 있다.

 

 

thread.h

/* ----------- added for project.2 ----------- */
  int exit_status;  // exit() 또는 wait() 구현에 사용되는 변수
  struct file **fd_table;  // fd table
  int fd_idx;  // fd index

 

스레드 구조체에 fd_table과 fd_idx를 추가해 준다. 두 필드는 파일 디스크럽터를 위한 필드이다.

 

운영체제는 프로세스별로 파일 디스크립터를 따로 관리한다. PintOS에서는 멀티스레딩이 지원되지 않으므로 프로세스가 곧 스레드이기에, 스레드 구조체에 파일 디스크럽터를 관리하는 필드를 생성해 주면 된다.

 

스레드마다 갖고 있는 fd_table 배열은, 현재 스레드에 의해 열린 파일들을 관리한다. 각 리스트 내 요소는 struct file을 가리키는 포인터이며, 스레드는 이 포인터를 이용해 해당 파일의 정보를 추적하게 된다.

 

 

syscall.c

int open(const char *file)
{
    check_address(file);
    lock_acquire(&filesys_lock);
    struct file *open_file = filesys_open(file);

    if (open_file == NULL)
    {
        return -1;
    }
    // fd table에 file추가
    int fd = add_file_to_fdt(open_file);
   
    // fd table 가득 찼을경우
    if (fd == -1)
    {
        file_close(open_file);
    }
    lock_release(&filesys_lock);
    return fd;
}

 

파일명을 입력받아 파일을 open하는 시스템 콜이다.

open한 파일은 스레드의 fd_table에 추가되는데, 만약 fd table이 꽉 찼을 경우엔 파일을 닫는다.

 

파일을 성공적으로 open하고 fd table에 추가했을 경우엔 할당된 fd 번호를 리턴하며, 아닐 경우 -1을 리턴한다.

 

 

syscall.c

 /* 파일을 현재 프로세스의 fdt에 추가 */
int add_file_to_fdt(struct file *file)
{
    struct thread *cur = thread_current();
    struct file **fdt = cur->fd_table;

    //  fd 범위를 넘지 않는 선에서, 할당 가능한 fd 번호를 찾는다.
    while (cur->fd_idx < FDT_COUNT_LIMIT && fdt[cur->fd_idx])
    {
        cur->fd_idx++;
    }

    // fd table이 꽉 찼을 경우 -1 리턴
    if (cur->fd_idx >= FDT_COUNT_LIMIT)
        return -1;

    // fd table에 파일을 할당하고 fd 번호를 리턴한다
    fdt[cur->fd_idx] = file;
    return cur->fd_idx;
}

 

위 open 시스템 콜에서 사용된 add_file_to_fdt 함수이다.

스레드의 fd table에서 적절한 위치를 찾아 파일을 할당하고 fd number를 리턴한다.

 

 

thread.c

tid_t thread_create(const char *name, int priority, thread_func *function,
                    void *aux) {

  생략.....
  /* ------------- added for Project.2 -------------*/

  new_t->fd_table = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
  if(new_t->fd_table == NULL) {
    return TID_ERROR;
  }
  new_t->fd_idx = 2;  // 0은 입력, 1은 출력에 이미 할당됨
  new_t->fd_table[0] = 1;
  new_t->fd_table[1] = 2;

  /* 새로 생성한 쓰레드를 ready_queue에 넣는다.
     thread_unblock() 이라는 함수 명에 혼동되면 안된다.
     단순히 thread의 state를 ready로 바꿔주는것 뿐만 아니라
     ready_list에 넣어주는 역할도 한다. */
  thread_unblock(new_t);

  /* ------------- added for Project.1-3 ------------- */
  생략.....
}

 

thread_create 함수에서 표준입력, 표준출력에 대한 fd table을 미리 할당해 둔다.

 

유닉스 시스템에서는 0, 1, 2번이 각각 표준입력, 표준출력, 표준에러를 의미한다. 하지만 PintOS에서는 표준에러를 사용하지 않기 때문에 0, 1번에만 미리 값을 할당해 주면 된다.

 

[0]과 [1]에 넣은 값 1, 2는 특별한 의미가 있는 값은 아니다.

 

 

thread.h

/* ------------ added for Project.2 ------------ */
#define FDT_PAGES 3  // fd table에 할당할 페이지 수
#define FDT_COUNT_LIMIT FDT_PAGES *(1<<9)  // fd table 최대 크기 3 * 512 : 1536

/* ----------------------------------------------- */

 

위 함수들에서 사용된 매크로는 thread.h에 선언해 두면 된다.

이렇게 선언하고 사용함으로써, fd 번호는 최대 1535가 된다. ( 0 ~ 1535 )

 

 

syscall.c

/* file size를 반환하는 함수 */
int filesize(int fd)
{
    struct file *open_file = find_file_by_fd(fd);
    if (open_file == NULL)
    {
        return -1;
    }

    lock_acquire(&filesys_lock);
    int fileLength = file_length(open_file);
    lock_release(&filesys_lock);

    return fileLength;
}

 

filesize를 반환하는 시스템 콜이다.

 

find_file_by_fd 함수를 이용해 fd 기준으로 파일을 찾은 후, file_length 함수로 파일의 크기를 구하여 리턴한다.

 

 

syscall.c

/*  fd 값을 넣으면 해당 file을 반환하는 함수 */
static struct file *find_file_by_fd(int fd)
{
    struct thread *cur = thread_current();
    if (fd < 0 || fd >= FDT_COUNT_LIMIT)
    {
        return NULL;
    }
    return cur->fd_table[fd];
}

 

find_file_by_fd 함수이다.

 

현재 스레드의 fd table에서 fd에 해당하는 파일을 리턴한다.

 

 

syscall.c

int read(int fd, void *buffer, unsigned size)
{
    check_address(buffer);
   
    // 읽은 바이트 수 저장할 변수
    off_t read_byte;
    // 버퍼를 바이트 단위로 접근하기 위한 포인터
    uint8_t *read_buffer = buffer;

    // 표준입력일 경우 데이터를 읽는다
    if (fd == 0)
    {
        char key;
        for (read_byte = 0; read_byte < size; read_byte++)
        {
            // input_getc 함수로 입력을 가져오고, buffer에 저장한다
            key = input_getc();
            *read_buffer++ = key;

            // 널 문자를 만나면 종료한다.
            if (key == '\0')
            {
                break;
            }
        }
    }
    // 표준출력일 경우 -1을 리턴한다.
    else if (fd == 1)
    {
        return -1;
    }
    // 2이상, 즉 파일일 경우 파일을 읽어온다.
    else
    {
        struct file *read_file = find_file_by_fd(fd);
        if (read_file == NULL)
        {
            return -1;
        }
        lock_acquire(&filesys_lock);
        read_byte = file_read(read_file, buffer, size);
        lock_release(&filesys_lock);
    }

    // 읽어온 바이트 수 리턴
    return read_byte;
}

 

표준입력 또는 파일을 읽어오는 read 시스템 콜이다.

 

표준입력(0)일 경우 키보드 입력을 읽어오고 파일(2이상)일 경우 file_read 함수를 이용해 파일 내용을 읽어온다.

읽어온 값은 인자로 받은 buffer에 넣으며, 마지막엔 읽어온 바이트 크기를 리턴한다.

 

 

syscall.c

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

 

seek 시스템 콜이다.

 

파일을 읽거나 쓸 위치를 지정하는 역할을 한다. 기본적으로 파일을 읽거나 쓸 땐 파일의 시작 위치부터 시작하게 되는데, 이 위치를 seek를 통해 변경할 수 있다. fd값을 갖는 파일에 대한 시작 위치를 position 값으로 변경한다.

file_seek 함수를 이용하면 간단하다.

 

 

syscall.c

unsigned tell (int fd) {
    if (fd < 2) {
        return;
    }
    struct file *file = find_file_by_fd(fd);
    check_address(file);
    if (file == NULL) {
        return;
    }
    return file_tell(file);
}

 

tell 시스템 콜이다.

 

위 seek 함수에서는 시작 위치를 변경해 주었다. tell은 현재 시작 위치를 리턴하는 함수이다.

마찬가지로 file_tell 함수를 이용하면 간단하다.

 

 

syscall.c

void close(int fd){

    if(fd < 2) {
        return;
    }

    // fd에 해당하는 파일 찾기
    struct file *fileobj = find_file_by_fd(fd);
    if (fileobj == NULL)
    {
        return;
    }

    // fd table에서 파일 삭제하기
    remove_file_from_fdt(fd);

    lock_acquire(&filesys_lock);
    // 파일 닫기
    file_close(fileobj);
    lock_release(&filesys_lock);
}

 

파일을 닫는 close 시스템 콜이다.

 

find_file_by_fd 함수로 fd에 해당하는 파일을 찾고, remove_file_from_fdt 함수로 fd table에서 파일을 지워준다.

마지막으로 file_close 함수로 파일을 닫아준다.

 

 

 

syscall.c

void remove_file_from_fdt(int fd) {
    struct thread *cur = thread_current();

    // error : invalid fd
    if (fd < 0 || fd >= FDT_COUNT_LIMIT)
        return;

    cur->fd_table[fd] = NULL;
}

 

fd table에서 fd에 해당하는 파일을 지워주는 함수이다.

fd에 해당하는 자리를 NULL로 바꿔준다.

 

===========================

 

나머지 함수는 다음 글에서 이어서 작성하도록 하겠다.

728x90
반응형