[PintOS]Project 02 WIL

경준·2022년 11월 28일
0

OS

목록 보기
2/5
post-thumbnail

개요

해당 포스팅은 PintOS 2주차에 배운 PintOS에 관련된 핵심적인 내용, OS의 동작 원리를 핵심적으로 다룰 예정입니다.(수정할 점이나 궁금한점이 있으시면 댓글로 남겨주세요🙂)


OS(Operation System)의 Mode

#Kernel Mode와 User Mode로 나뉜다

  • Kernel Mode : 프로그램이 수행되다가 Inerrupt가 발생하면 OS가 호출하는 코드(H/W에 직접 Acess가능하다)
  • User Mode : User Application Code가 실행되는 코드

Kernel

System의 핵심.
System 전체를 관리, 감독 역할을 수행하고 H/W관련 작업을 직접 수행함

Kernel Mode

목적 : System의 보호를 위함(User Mode에서 직접적으로 메모리에 접근하여 발생하는 문제를 예방하기 위해)

프로그램 실행 중에 Interrupt가 발생하거나 System Call을 호출하게 되면 User Mode -> Kernel Mode로 전환된다.


User Mode에서 interrupt or system call 발생 시

  1. Program의 현재 CPU Status를 저장
  2. Interrupt System Call을 직접 관리 -> CPU에서 Kernel Code가 실행됨
  3. 중단되었던 Program의 CPU Status를 복원
  4. 통제권을 Program에게 반환(Kernel Mode -> User Mode)

Interrupt

System에서 발생한 다양한 종류의 Event 혹은 Event를 알리는 매커니즘

Interrupt의 종류

  • Power Error
  • Time Over
  • I/O작업 완료
  • 잘못된 Memory Space 참조(trap)
  • 0으로 나눴을 경우(trap)

Interrupt 발생 시 CPU는 즉각적으로 Interrupt 처리를 위해 Kernel Code를 Kernel Mode에서 실행함

  • 즉각적의 기준 : 실행 중이던 Code가 있을 때 그 Code 명령까지만 실행하고 Interrupt를 걸어준다는 뜻

System Call

Program이 OS Kernel이 제공하는 서비스를 이용하고 싶을 때, System Call을 이용해 실행함

System Call의 종류

  • Process & Thread 관련

  • File I/O 관련

  • Socket 관련

  • 주변장치(Device)관련

  • Process 통신 관련


System Call 발생 시 해당 Kernel Code가 Kernel Mode에서 실행됨

System Call & Interrupt의 간단한 예시

  • H/W, System 관련 기능들은 어떠한 Program일지라도 반드시 System Call을 통해서만 가능함
  • 우리가 사용하는 Programming Language들은 System Call을 포장(Mapping)하여 간접적으로 사용할 수 있도록 제공해주고 있음

Argument Passing(Command Line Parsing)

현재 PintOS의 실행 과정

Project 2를 시작할 당시 PintOS는 Command Line에 입력된 프로그램과 Arguments를 인식하지 못한다.
따라서 프로그램 Name과 Arguments를 구분하여 Stack에 저장한 후, Arguments를 Program에 전달하
는 기능을 구현해야 함

ex> $pintos –q run 'echo x' 의 경우

init.c/main() -> init.c/run_actions(argv) -> init.c/run_task(argv) -> process.c/process_create_initd(file_name) -> process.c/initd(file_name) -> process.c/process_exec(file_name) -> process.c/load() -> process.c/process_activate()
순으로 Commad Line으로 Name과 Argument를 Parsing하여 코드가 진행됨

Interrupt Frame

실행 중인 Process와 Register의 정보, Interuction Count를 저장하는 Struct

  • Kernel Stack에 있으며, Interrupt or System Call 시 사용 됨

함수 호출 시 Argument Value은 오른쪽에서 왼쪽으로 User Stack에 저장된다.
(이를 위해 rsp(Stack Pointer)를 활용해야 함)


System Call의 호출

System Call의 호출 과정

  1. User Program이 System Call을 호출한다.

  2. 사용자 시스템 라이브러리(syscall.c)가 syscall number, Arguments, 해당 Program의 Interrupt frame을 정해진 순서대로 Register에 채워준다.(Systemcall Instruction을 CPU에 넘겨주고 Kernel Mode로 전환)

  3. Previllege Level Kernel Mode(Ring 0)으로 올리고, Caller User Stack Pointer를 rbx register에 저장한다.
    => CPU와 OS가 Interrupt되기 전에 Process Status를 Process의 Kernel Stack에 저장
    (Interrupt 종료 시 Interrupt전 Task로 되돌아 올 수 있게 하기 위함)

  4. Kernel Stack에 저장한 후 Kernel Stack Pointer를 Interrupt Frame 함수의 Argument로 넣어준 후 Call한다.

  5. Interrupt Hander가 작업을 처리한 후 Kernel Stack에 저장했던 Process Status를 Register에 넣어주면서 Stack을 지워준다. (원래 Caller의 Process를 원복하는 과정)

  6. Caller로 돌아간다.

PintOS의 기본적인 System Call

1. hait

시스템 전체를 종료시키는 System Call


void haid(void){
	power_off();
}

void power_off (void) {
#ifdef FILESYS
	filesys_done ();
#endif
	print_stats ();
	printf ("Powering off...\n");
	outw (0x604, 0x2000);
	for (;;);
}

2. exit

현재 실행 중인 User Program을 종료시키고, Argument로 받은 자신의 Status를 Kernel에 반환한다.

  • Process 내 모든 자원들은 exit를 통해서 free되어야한다.

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

void thread_exit (void) {
  ASSERT (!intr_context ());

#ifdef USERPROG
  process_exit ();
#endif

  intr_disable ();
  do_schedule (THREAD_DYING);
  NOT_REACHED ();
}

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

  for (int i = 2; i < FD_COUNT_LIMT; i++)
    close (i); //process의 file들을 닫아준다.

  palloc_free_multiple (cur->fd_table, FD_PAGES); //fd_table로 사용한 메모리를 free시켜준다.(multi-oom)
  file_close(cur->running); 
  //file close의 file_allow_write (file)통해 file을 deny하지 않게 한다.(rox problem)
  process_cleanup ();
  sema_up (&cur->wait_sema);
  sema_up (&cur->fork_sema);
  sema_down (&cur->exit_sema);
}

3. create

filesys를 생성하는 System Call


bool create (const char *file, unsigned initial_size) {

  check_address (file); //해당 파일의 시작주소가 User Memory Space인지 확인
  return filesys_create (file, initial_size); //이름 및 경로가 file, 크기가 initial_size인 파일 생성
}

bool filesys_create (const char *name, off_t initial_size) {
	disk_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;
}

4. remove

이름 및 경로가 file인 파일을 remove하는 System Call


bool remove (const char *file) {
  check_address (file); //해당 파일의 시작주소가 User Memory Space인지 확인
  return (filesys_remove (file));
}

bool filesys_remove (const char *name) {
	struct dir *dir = dir_open_root ();
	bool success = dir != NULL && dir_remove (dir, name);
	dir_close (dir);

	return success;
}

5. open

입력받은 name을 가진 file을 open하고, Address를 가리키는 pointer를 return


int open (const char *file) {
  check_address (file);//해당 파일의 시작주소가 User Memory Space인지 확인
  struct file *file_obj = filesys_open (file);//해당 file 이름을 가진 파일을 찾아 Open
  
  if (file_obj == NULL) return -1; 
  //해당 이름의 filedl 없을경우 return -1
 
  int fd_idx = add_file_to_FDT (file_obj);
  //해당 파일을 가리키는 포인터를 fdt에 넣어주고 식별자 return

  if (fd_idx == -1) file_close (file_obj);
 
  return fd_idx;
}

int add_file_to_FDT (struct file *file) {
  struct thread *cur = thread_current ();
  struct file **fdt = cur->fd_table; //현재 thread의 file table
  int fd_index = cur->fd_idx;

  while (fdt[fd_index] != NULL && fd_index < FD_COUNT_LIMIT) {
    fd_index++;
  }
	
  if (fd_index >= FD_COUNT_LIMIT) return -1;
  //file 식별자가 식별자 범위 내에 없을경우 return -1

  cur->fd_idx = fd_index;
  fdt[fd_index] = file; //new file 식별자를 index로 하는 fdt와, 해당 file을 가리키고 있는 포인터와 mapping
  return fd_index; //새로 Open한 파일의 식별자 return
}

6. filesize

해당 file의 incode data에서 파일의 크기(byte)를 return하는 System Call


int file_size (int fd) {
  struct file *file_obj = find_file_using_fd(fd);//현재 사용하고 있는 file의 포인터를 return

  if (file_obj == NULL) return -1; //해당 file이 없으면 return -1

  return file_length (file_obj);//파일의 size를 return
}

static struct file * find_file_using_fd (int fd) {
  struct thread *cur = thread_current ();
  if (fd < 0 || fd >= FD_COUNT_LIMIT) return NULL;

  return cur->fd_table[fd];
}

off_t file_length (struct file *file) {
	ASSERT (file != NULL);
	return inode_length (file->inode);
}

7. read

해당 파일이 존재하는 disk의 sector에 들어가 char 변수 단위로 읽어 버퍼에 담는 System Call


int read (int fd, const void *buffer, unsigned size) {
  check_address (buffer); //해당 파일의 시작주소가 User Memory Space인지 확인
  int read_result;
  struct file *file_obj = find_file_using_fd (fd); //현재 사용하고 있는 file의 포인터를 return

  if (file_obj == NULL) return -1;

  if (fd == STDIN_FILENO) { //해당 파일이 표준 입력파일일 때
    char word;
    for (read_result = 0; read_result < size; read_result++) {
      word = input_getc (); char 변수 단위로 읽는다.
      if (word == "\0") //word가 file의 끝(EOF)를 만나면 break한다.
        break;
    }
  } 
  else if (fd == STDOUT_FILENO) return -1;
  //해당 파일이 표준 출력 파일일 때 : 출력 출력 파일 데이터를 읽을 수는 없다.
  else {
    lock_acquire (&filesys_lock);
    read_result = file_read (file_obj, buffer, size);//buffer에 size만큼 담는다.
    lock_release (&filesys_lock);
  }
  return read_result;
}

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;
}

8. write

buffer에 담긴 data를 size만큼 fd파일에 write해주는 System Call


int write (int fd, const void *buffer, unsigned size) {
  check_address (buffer); //해당 파일의 시작주소가 User Memory Space인지 확인
  int read_result;
  struct file *file_obj = find_file_using_fd (fd); //현재 사용하고 있는 file의 포인터를 return
  if (fd == STDIN_FILENO) return 0; //해당 파일이 표준 입력파일일 때 return 0

  if (fd == STDOUT_FILENO) { //해당 파일이 표준 출력 파일일 때
    putbuf (buffer, size); //buffer에 있는 data를 size만큼 console에 보내 출력
    return size;
  } 
  else {
    if (file_obj == NULL) return 0;
    
    lock_acquire (&filesys_lock);
    off_t write_result = file_write (file_obj, buffer, size); //출력한 data의 byte
    lock_release (&filesys_lock);
    
    return write_result; //출력한 데이터의 byte를 반환
  }
}

void putbuf (const char *buffer, size_t n) {
	acquire_console (); //해당 console를 호출한 process만 사용가능하게 함
	while (n-- > 0)
		putchar_have_lock (*buffer++);
	release_console ();
}

9. seek

열린 파일의 다음으로 read or write할 파일 내 data의 위치를 입력받은 position으로 옮기는 System Call


void seek (int fd, unsigned position) {
  struct file *file_obj = find_file_using_fd (fd); //현재 사용하고 있는 file의 포인터를 return
  file_seek (file_obj, position);
}

void file_seek (struct file *file, off_t new_pos) {
	ASSERT (file != NULL);
	ASSERT (new_pos >= 0);
	file->pos = new_pos;
}

10. tell

입력받은 파일에서 다음으로 read or write할 data의 position(파일의 시작점부터의 byte의 offset)을 출력한다.


unsigned tell (int fd) {
  if (fd <= 2) return;
  struct file *file_obj = find_file_using_fd (fd); //현재 사용하고 있는 file의 포인터를 return
  check_address (file_obj); //해당 파일의 시작주소가 User Memory Space인지 확인
  if (file_obj == NULL) return;
  
  return file_tell (file_obj);
}

off_t file_tell (struct file *file) {
	ASSERT (file != NULL);
	return file->pos;
}

11. close

해당 Process의 FD_Table에서 없에줘 연결을 끊어주는 System Call


void close (int fd) {
  struct file *file_obj = find_file_using_fd (fd); //현재 사용하고 있는 file의 포인터를 return
  if (file_obj == NULL) return;

  if (fd < 0 || fd >= FD_COUNT_LIMT) return;
  thread_current ()->fd_table[fd] = NULL; //fd_table에서 연결을 끊어줌
  palloc_free_page(thread_current ()->fd_table[fd]); //할당한 테이블 내 file을 free

  lock_acquire (&filesys_lock);
  file_close (file_obj);   //을 닫아줌
  lock_release (&filesys_lock);
}

12. wait

Child Process가 올바르게 종료되었는지 Check한 후, 모두 종료될 때까지 대기하는 System Call


int wait (tid_t pid) {
  return process_wait (pid); // Child Process의 Status를 return
}

int process_wait (tid_t child_tid UNUSED) {
  struct thread *child = get_child (child_tid); //child_tid를 가지는 Child Process를 List에서 가져온다
  
  if (child == NULL) return -1;
  
  sema_down (&child->wait_sema);
  list_remove (&child->child_elem); //Parent Process의 child_list에서 제거한다.
  sema_up (&child->exit_sema);

  return child->exit_status; //child_process의 종료 status를 return
}

13. exec

Argument로 받은 실행파일을 실행시키는 System Call

int exec (const char *file) {
  check_address (file); //해당 파일의 시작주소가 User Memory Space인지 확인
  char *file_name_copy = palloc_get_page (PAL_ZERO); 
  /*파일이름을 받기위한 문자열
  (copy를 하지 않으면 process_exec()에서 process_cleanup()을 할 때 해당 file_name이 지워지므로 
  Page Table도 같지 지워지기 때문에 할당을 한 후 kernel stack에 저장함) */ 
 

  if (file_name_copy == NULL) exit (-1);

  strlcpy (file_name_copy, file, strlen (file) + 1); //file name을 복사

  if (process_exec (file_name_copy) == -1) return -1;

  NOT_REACHED ();//Caller Process는 do_iret() 후 return하지 않음
  
  return 0; //error가 발생했을 경우에만 0을 return함
}

int process_exec (void *f_name) {
  char *file_name = f_name;
  bool success;

  struct intr_frame _if;
  _if.ds = _if.es = _if.ss = SEL_UDSEG;
  _if.cs = SEL_UCSEG;
  _if.eflags = FLAG_IF | FLAG_MBS;

  process_cleanup (); //현재 context를 kill

  success = load (file_name, &_if); // _if에 file name을 올릴때 palloc이 page를 할당(pml4_create)


  palloc_free_page (file_name);   //file_name을 동적할당 해제
  if (!success) return -1;

  do_iret (&_if);   // load가 완료되었다면 context switching을 진행
  NOT_REACHED ();
}

static bool load (const char *file_name, struct intr_frame *if_) {
  struct thread *t = thread_current ();
  struct ELF ehdr;
  struct file *file = NULL;
  off_t file_ofs;
  bool success = false;
  int i;

  uintptr_t stack_ptr;   // stack pointer가 가리키는 위치
  char *address[64];   // stack pointer가 처음 들어간 주소를 다시 넣을 공간

  char *argv[64];   // Argument를 나눠서 저장할 공간 - 0: file name
                    // Arguments - 2중 Pointer 사용
  int argc = 0;   // Argument 개수

  char *token;           // file name을 token화
  char *remain_string;   // 남은 string을 저장하기 위한 공간

  token = strtok_r (file_name, " ", &remain_string);   // strtok_r 을 사용해서 토큰으로 구분하고 나눈 후 저장
  argv[0] = token;

  while (token != NULL) {
    token = strtok_r (NULL, " ", &remain_string);
    argc++;
    argv[argc] = token;
  }

  t->pml4 = pml4_create ();   //내부에서 palloc이 할당
  if (t->pml4 == NULL)
    goto done;
  process_activate (thread_current ());

  file = filesys_open (argv[0]);
  if (file == NULL) {
    printf ("load: %s: open failed\n", file_name);
    goto done;
  }

  if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr ||
      memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7) || ehdr.e_type != 2 ||
      ehdr.e_machine != 0x3E   // amd64
      || ehdr.e_version != 1 || ehdr.e_phentsize != sizeof (struct Phdr) ||
      ehdr.e_phnum > 1024) {
    printf ("load: %s: error loading executable\n", file_name);
    goto done;
  }

  t->running = file; //현재 thread가 사용 중인 파일임을 저장
  file_deny_write(file); //파일 접근 권한을 제한함(file_close)시 file_allow_write로 해제(rox-problem)
  
  file_ofs = ehdr.e_phoff;
  for (i = 0; i < ehdr.e_phnum; i++) {
    struct Phdr phdr;

    if (file_ofs < 0 || file_ofs > file_length (file))
      goto done;
    file_seek (file, file_ofs);

    if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
      goto done;
      
    file_ofs += sizeof phdr;
    switch (phdr.p_type) {
    case PT_NULL:
    case PT_NOTE:
    case PT_PHDR:
    case PT_STACK:
    default:
      /* Ignore this segment. */
      break;
    case PT_DYNAMIC:
    case PT_INTERP:
    case PT_SHLIB:
      goto done;
    case PT_LOAD:
      if (validate_segment (&phdr, file)) {
        bool writable = (phdr.p_flags & PF_W) != 0;
        uint64_t file_page = phdr.p_offset & ~PGMASK;
        uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
        uint64_t page_offset = phdr.p_vaddr & PGMASK;
        uint32_t read_bytes, zero_bytes;
        if (phdr.p_filesz > 0) {
          /* Normal segment.
           * Read initial part from disk and zero the rest. */
          read_bytes = page_offset + phdr.p_filesz;
          zero_bytes =
              (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE) - read_bytes);
        } else {
          /* Entirely zero.
           * Don't read anything from disk. */
          read_bytes = 0;
          zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
        }
        if (!load_segment (file, file_page, (void *) mem_page, read_bytes,
                           zero_bytes, writable))
          goto done;
      } else
        goto done;
      break;
    }
  }

  /* Set up stack. */
  if (!setup_stack (if_))
    goto done;

  /* Start address. */
  if_->rip = ehdr.e_entry;

  address[0] = if_->rsp;
  for (int i = argc - 1; i > -1; i--) {
    if_->rsp = if_->rsp - (strlen (argv[i]) + 1);
    memcpy (if_->rsp, argv[i], strlen (argv[i]) + 1);
    address[i] = if_->rsp;
  }

  if ((USER_STACK - (if_->rsp)) % 8 != 0) {
    int i = 8 - (USER_STACK - (if_->rsp)) % 8;
    if_->rsp = if_->rsp - i;
    memset (if_->rsp, 0, i);
  }


  if_->rsp = if_->rsp - 8;
  memset (if_->rsp, 0, 8);

  if (address != NULL) {
    size_t addr_size = argc * sizeof (address[0]) / sizeof (char);
    if_->rsp = if_->rsp - addr_size;

    memcpy (if_->rsp, address, addr_size);
  }
  
  if_->rsp = if_->rsp - 8;
  memset (if_->rsp, 0, 8);

  success = true;

  if_->R.rdi = argc;
  if_->R.rsi = if_->rsp + 8;

done:
  return success;
}

14. fork

현재 Process를 Copy한 Child Process를 만드는 System Call


tid_t fork (const char *thread_name, struct intr_frame *f) {
  return process_fork (thread_name, f);
}

tid_t process_fork (const char *name, struct intr_frame *if_) {
  struct thread *parent = thread_current ();
  

  struct argv *fork_argv = (struct argv *) malloc (sizeof (struct argv)); 
  fork_argv->fork_if = if_; 
  fork_argv->fork_thread = thread_current ();

  tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, fork_argv);
  //현재 thread를 copy
  
  if (tid == TID_ERROR) return TID_ERROR;

  struct thread *child = get_child (tid);//Parent Process의 child_list에 넣어줌
  sema_down (&child->fork_sema);
  
  return tid;
}

static void __do_fork (void *aux) {
  struct intr_frame if_;

  struct argv *fork_argv = (struct argv *) aux;
  struct thread *parent = fork_argv->fork_thread;
  struct thread *current = thread_current ();
  
  struct intr_frame *parent_if;
  parent_if = fork_argv->fork_if;
  bool succ = true;

  memcpy (&if_, parent_if, sizeof (struct intr_frame));
  if_.R.rax = 0; //rax값을 0으로 초기화

  current->pml4 = pml4_create ();
  if (current->pml4 == NULL)
    goto error;

  process_activate (current);

  if (!pml4_for_each (parent->pml4, duplicate_pte, fork_argv))
    goto error;

  if (parent->fd_idx >= FD_COUNT_LIMT)
    goto error;

  current->fd_table[0] = parent->fd_table[0];
  current->fd_table[1] = parent->fd_table[1];

  for (int i = 2; i < FD_COUNT_LIMIT; i++) {
    struct file *temp_file = parent->fd_table[i]; 
    //Parent Process의 fd_table을 search하면서 자신의 fd_table로 복사해간다.
    if (temp_file == NULL) 
    	continue;
    current->fd_table[i] = file_duplicate (temp_file);
  }

  current->fd_idx = parent->fd_idx;
  sema_up (&current->fork_sema);

  process_init ();

  if (succ)
  free(fork_argv); //malloc한 fork_argv를 free
    do_iret (&if_);
error:
  exit (TID_ERROR);
}

static bool duplicate_pte (uint64_t *pte, void *va, void *aux) {
  struct thread *current = thread_current ();
  struct argv *argv_fork = (struct argv *) aux;
  
  struct thread *parent = argv_fork->fork_thread;
  void *parent_page;
  void *newpage;
  bool writable;

  
  if (is_kernel_vaddr (va)) return true;
  
  parent_page = pml4_get_page (parent->pml4, va); 
  //va에 해당하는 물리 메모리 주소를 parent->pml4에서 찾는다
  if (parent_page == NULL) return false;
  
  newpage = palloc_get_page (PAL_USER);
  //사용자 물리 메모리에서 새로은 Page를 할당받는다.
  if (newpage == NULL) {
    printf ("duplicate_pte page fault\n");
    return false;
  }




Project 2 회고

이번 주는 솔직하게 말하면 내가 겪은 정글 기간 동안 젤 힘든 기간을 보낸 것 같았다. 2주차 초기에는 몸상태도 좋지 않았고, 공부를 함에도 진전이 없어 "나 코딩 정말 못하는 구나"라는 생각이 들만큼 회의감이 많이 들었다. 거기다가 대학교 졸업 후 제대로 쉬어보고나 놀아본적이 없는거 같다는 생각에 마음도 붕 뜨는 등 복합적인 이유로 가장 힘든 1.5주를 보낸 것 같다. 번아웃이라는 것을 이번 프로젝트에서 제대로 겪어보았고, 극복해 나가는 과정들이 좋은 경험이 되었던 것 같다.

요즘 내가 보고 있는 만화에서 가장 인상 깊었던 말이

도망치면 하나, 전진하면 둘을 얻는다.

라는 말이었다.

지금 포기하면 정체될 뿐이지만 끝까지 붙잡고 알아가다보면 퀀텀점프라는 것을 해보지 않을까라는 희망으로 한 주를 보냈고, 구글링을 통해 다른 사람들이 작성한 글이나 자료들을 많이 참고하여 어찌저찌 Argument Passing이 어떻게 이뤄지고, System Call이 어떠한 매커니즘으로 흘러가는지에 대해서 배울 수 있는 시간이었다.

노는거야 수료하고 남은 취준기간 동안 놀면 되고, 개발직종이 노력으로도 좋은 개발자가 있다는것을 명심하며 앞으로 남은 3주의 PintOS 주차에서 좀 더 많은 것을 얻어갈 수 있게 노력하자.

profile
코린이 좌충우돌 메모장

0개의 댓글