해당 포스팅은 PintOS 2주차에 배운 PintOS에 관련된 핵심적인 내용, OS의 동작 원리를 핵심적으로 다룰 예정입니다.(수정할 점이나 궁금한점이 있으시면 댓글로 남겨주세요🙂)
#Kernel Mode와 User Mode로 나뉜다
- Kernel Mode : 프로그램이 수행되다가 Inerrupt가 발생하면 OS가 호출하는 코드(H/W에 직접 Acess가능하다)
- User Mode : User Application Code가 실행되는 코드
System의 핵심.
System 전체를 관리, 감독 역할을 수행하고 H/W관련 작업을 직접 수행함
목적 : System의 보호를 위함(User Mode에서 직접적으로 메모리에 접근하여 발생하는 문제를 예방하기 위해)
프로그램 실행 중에 Interrupt가 발생하거나 System Call을 호출하게 되면 User Mode -> Kernel Mode로 전환된다.
System에서 발생한 다양한 종류의 Event 혹은 Event를 알리는 매커니즘
Interrupt의 종류
Interrupt 발생 시 CPU는 즉각적으로 Interrupt 처리를 위해 Kernel Code를 Kernel Mode에서 실행함
Program이 OS Kernel이 제공하는 서비스를 이용하고 싶을 때, System Call을 이용해 실행함
System Call의 종류
Process & Thread 관련
File I/O 관련
Socket 관련
주변장치(Device)관련
Process 통신 관련
System Call 발생 시 해당 Kernel Code가 Kernel Mode에서 실행됨
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하여 코드가 진행됨
실행 중인 Process와 Register의 정보, Interuction Count를 저장하는 Struct
- Kernel Stack에 있으며, Interrupt or System Call 시 사용 됨
함수 호출 시 Argument Value은 오른쪽에서 왼쪽으로 User Stack에 저장된다.
(이를 위해 rsp(Stack Pointer)를 활용해야 함)
System Call의 호출 과정
User Program이 System Call을 호출한다.
사용자 시스템 라이브러리(syscall.c)가 syscall number, Arguments, 해당 Program의 Interrupt frame을 정해진 순서대로 Register에 채워준다.(Systemcall Instruction을 CPU에 넘겨주고 Kernel Mode로 전환)
Previllege Level Kernel Mode(Ring 0)으로 올리고, Caller User Stack Pointer를 rbx register에 저장한다.
=> CPU와 OS가 Interrupt되기 전에 Process Status를 Process의 Kernel Stack에 저장
(Interrupt 종료 시 Interrupt전 Task로 되돌아 올 수 있게 하기 위함)
Kernel Stack에 저장한 후 Kernel Stack Pointer를 Interrupt Frame 함수의 Argument로 넣어준 후 Call한다.
Interrupt Hander가 작업을 처리한 후 Kernel Stack에 저장했던 Process Status를 Register에 넣어주면서 Stack을 지워준다. (원래 Caller의 Process를 원복하는 과정)
Caller로 돌아간다.
시스템 전체를 종료시키는 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 (;;);
}
현재 실행 중인 User Program을 종료시키고, Argument로 받은 자신의 Status를 Kernel에 반환한다.
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);
}
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;
}
이름 및 경로가 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;
}
입력받은 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
}
해당 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);
}
해당 파일이 존재하는 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;
}
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 ();
}
열린 파일의 다음으로 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;
}
입력받은 파일에서 다음으로 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;
}
해당 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);
}
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
}
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;
}
현재 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 (¤t->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;
}
이번 주는 솔직하게 말하면 내가 겪은 정글 기간 동안 젤 힘든 기간을 보낸 것 같았다. 2주차 초기에는 몸상태도 좋지 않았고, 공부를 함에도 진전이 없어 "나 코딩 정말 못하는 구나"라는 생각이 들만큼 회의감이 많이 들었다. 거기다가 대학교 졸업 후 제대로 쉬어보고나 놀아본적이 없는거 같다는 생각에 마음도 붕 뜨는 등 복합적인 이유로 가장 힘든 1.5주를 보낸 것 같다. 번아웃이라는 것을 이번 프로젝트에서 제대로 겪어보았고, 극복해 나가는 과정들이 좋은 경험이 되었던 것 같다.
요즘 내가 보고 있는 만화에서 가장 인상 깊었던 말이
도망치면 하나, 전진하면 둘을 얻는다.
라는 말이었다.
지금 포기하면 정체될 뿐이지만 끝까지 붙잡고 알아가다보면 퀀텀점프라는 것을 해보지 않을까라는 희망으로 한 주를 보냈고, 구글링을 통해 다른 사람들이 작성한 글이나 자료들을 많이 참고하여 어찌저찌 Argument Passing이 어떻게 이뤄지고, System Call이 어떠한 매커니즘으로 흘러가는지에 대해서 배울 수 있는 시간이었다.
노는거야 수료하고 남은 취준기간 동안 놀면 되고, 개발직종이 노력으로도 좋은 개발자가 있다는것을 명심하며 앞으로 남은 3주의 PintOS 주차에서 좀 더 많은 것을 얻어갈 수 있게 노력하자.