🚫 현재 글은 정확하지 않은 정보가 있을 수 있습니다. 언제든지 틀린 부분에 대해 댓글을 달아주세요! 🚫
01. JUNGLE_PINTOS PROJECT2: User Programs 과제 가이드라인
https://casys-kaist.github.io/pintos-kaist/project2/introduction.html
Introduction · GitBook
Project2: User Programs Now that you've worked with Pintos and are becoming familiar with its infrastructure and thread package, it's time to start working on the parts of the system that allow running user programs. The base code already supports loading
casys-kaist.github.io
(1) Introduction
Now that you've worked with Pintos and are becoming familiar with its infrastructure and thread package, it's time to start working on the parts of the system that allow running user programs. The base code already supports loading and running user programs, but no I/O or interactivity is possible. In this project, you will enable programs to interact with the OS via system calls. You will be working out of the userprog directory for this assignment, but you will also be interacting with almost every other part of Pintos. We will describe the relevant parts below.
이번 과제에는 프로그램이 시스템 콜을 통해서 OS와 상호작용할 수 있도록 만들어야 합니다.
(2) Argument Passing
(1) /bin/ls -l foo bar 와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해봅시다.
- Break the command into words: /bin/ls, l, foo, bar.
- Place the words at the top of the stack. Order doesn't matter, because they will be referenced through pointers.
- Push the address of each string plus a null pointer sentinel, on the stack, in right-to-left order. These are the elements of argv. The null pointer sentinel ensures that argv[argc] is a null pointer, as required by the C standard. The order ensures that argv[0] is at the lowest virtual address. Word-aligned accesses are faster than unaligned accesses, so for best performance round the stack pointer down to a multiple of 8 before the first push.
- Point %rsi to argv (the address of argv[0]) and set %rdi to argc.
- Finally, push a fake "return address": although the entry function will never return, its stack frame must have the same structure as any other.
1) 스택의 마지막을 가짜 주소값으로 push하는 이유는 뭘까? 왜?
- 스택 마지막으로 가짜 주소값을 push하는 이유
fake return address를 넣어야 하는 이유는 함수를 실행한 측(Caller)에서 함수를 실행하면 stack의 맨 아랫쪽에 자동으로 return address를 넣어주는 기능이 있는데, 프로그램에서 처음 실행되는 함수는 caller가 없어서 return adderss가 안들어가 있으므로 커널이 직접 넣어야 하는 것이다.
왜 굳이 stack frame과 같은 구조를 만들줘야 할까? stack alignment 규칙에 따르면 함수가 실행된 직후(return address 가 들어가있는 상태)에 (%rsp + 8)이 16의 배수가 되어야 한다는 것이므로, 이 조건을 맞추기 위해 fake return address를 넣는다. - stack alignment는 뭘까?
많은 컴퓨터 시스템들은 기본 자료형들에 대해 사용 가능한 주소를 제한하고 있어서 어떤 객체의 주소는 어떤 값 K(일반적으로 2, 4, 8의 배수가 되도록 요구한다. 이러한 데이터 정렬제한은 프로세서와 메모리 시스템 간의 인터페이스를 구성하는 하드웨어의 설계를 단순화한다. x86-64 하드웨어는 데이터 정렬과 상관없이 정확하게 동작할 것이지만 인텔은 메모리 시스템 성능을 개선하기 위해서 데이터가 정렬될 것을 추천한다.
stack aligment는 stack이 topdml 16의 배수로 유지된 상태이며, 메모리의 access cycle을 최소한으로 줄이기 위해 사용한다. 예를 들면 어떤 프로세서는 항상 8의 배수인 주소로 메모리에서 8바이트를 인출한다고 하자. 만일 모든 double 값이 주소가 8의 배수로 정렬될 것이라고 보장할 수 있다면 이 값은 하나의 메모리 연산으로 읽거나 쓸 수 있다. 그렇지 않다면 객체가 두 개의 8바이트 메모리 블록에 나누어 있으므로 메모리에 두 번 접근해야 할 수 있다. (출처 : caspp.261pg)
(2) 아래 표는 스택 관련 레지스터들이 유저 프로그램이 시작되기 직전에 어떤 상태인지를 보여줍니다.
0x4747fffc | argv[3][...] | 'bar\0' | char[4] |
0x4747fff8 | argv[2][...] | 'foo\0' | char[4] |
0x4747fff5 | argv[1][...] | '-l\0' | char[3] |
0x4747ffed | argv[0][...] | '/bin/ls\0' | char[8] |
0x4747ffe8 | word-align | 0 | uint8_t[] |
0x4747ffe0 | argv[4] | 0 | char * |
0x4747ffd8 | argv[3] | 0x4747fffc | char * |
0x4747ffd0 | argv[2] | 0x4747fff8 | char * |
0x4747ffc8 | argv[1] | 0x4747fff5 | char * |
0x4747ffc0 | argv[0] | 0x4747ffed | char * |
0x4747ffb8 | return address | 0 | void (*) () |
02. JUNGLE_PINTOS PROJECT2: Argument Passing
Argument Passing은 현재 커널모드인 pintos 커맨드 라인에 명령어를 작성하면, 프로그램과 인자를 구분해서 pintos가 프로그램과 인자를 저장하도록 하는 것이다. 커널은 유저 프로그램이 실행되는 것을 허가하기 전에, 레지스터에 올라가 있는 초기 함수를 위한 인자를 반드시 넣어줘야 한다.
예를 들어 "grep foo bar"를 입력하게 되면 "grep"이라는 프로그램과 "foo bar"라는 인자를 받아 프로그램을 실행시킬 수 있도록 하는 것인데, 커맨드 라인에서 여러개의 공백은 하나의 공백과 같게 처리해야한다. 즉, "grep foo bar"라고 작성해도 똑같이 작동하도록 해야한다.
스택 포인터를 초기화 하고, include/threads/vaddr.h에 정의된 USER_STACK 값에서부터 스택을 시작시켜야 한다.
❓pintos는 최초로 커맨드 라인에서 받은 실행 파일을 실행시키는 것이므로 커널 스레드가 사용자 프로그램을 실행시킨다.
03. Argument Passing 전에 PINTOS 실행과정을 알아보자
pintos가 너무 많은 코드들을 가지고 있어 main함수부터 차근차근 찾아볼 코드를 따라가며 뜯어볼 필요성이 있다. 렛츠고
① int main() : pintos의 메인함수!
- 메모리 영역, thread, console을 초기화한다.
/* Pintos main program. */
int
main (void) {
uint64_t mem_end;
char **argv;
/* Clear BSS and get machine's RAM size. */
bss_init ();
/* Break command line into arguments and parse options. */
argv = read_command_line ();
argv = parse_options (argv);
/* Initialize ourselves as a thread so we can use locks,
then enable console locking. */
thread_init ();
console_init ();
/* Initialize memory system. */
mem_end = palloc_init ();
malloc_init ();
paging_init (mem_end);
#ifdef USERPROG
tss_init ();
gdt_init ();
#endif
/* Initialize interrupt handlers. */
intr_init ();
timer_init ();
kbd_init ();
input_init ();
#ifdef USERPROG
exception_init ();
syscall_init ();
#endif
/* Start thread scheduler and enable interrupts. */
thread_start ();
serial_init_queue ();
timer_calibrate ();
#ifdef FILESYS
/* Initialize file system. */
disk_init ();
filesys_init (format_filesys);
#endif
#ifdef VM
vm_init ();
#endif
printf ("Boot complete.\n");
/* Run actions specified on kernel command line. */
run_actions (argv);
/* Finish up. */
if (power_off_when_done)
power_off ();
thread_exit ();
}
② static_void run_action() :
- is_user_program? actions에 담긴 배열과 인자로 받은 *argv와 비교하면서 같은 것이 있는지 확인 후, 해당 명령 funtion으로 이동한다.
/* Executes all of the actions specified in ARGV[]
up to the null pointer sentinel. */
static void
run_actions (char **argv) {
/* An action. */
struct action {
char *name; /* Action name. */
int argc; /* # of args, including action name. */
void (*function) (char **argv); /* Function to execute action. */
};
/* Table of supported actions. */
static const struct action actions[] = {
{"run", 2, run_task},
#ifdef FILESYS
{"ls", 1, fsutil_ls},
{"cat", 2, fsutil_cat},
{"rm", 2, fsutil_rm},
{"put", 2, fsutil_put},
{"get", 2, fsutil_get},
#endif
{NULL, 0, NULL},
};
while (*argv != NULL) {
const struct action *a;
int i;
/* Find action name. */
for (a = actions; ; a++)
if (a->name == NULL)
PANIC ("unknown action `%s' (use -h for help)", *argv);
else if (!strcmp (*argv, a->name))
break;
/* Check for required arguments. */
for (i = 1; i < a->argc; i++)
if (argv[i] == NULL)
PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1);
/* Invoke action and advance. */
a->function (argv);
argv += a->argc;
}
}
③ static void run_task()
- process_create_initd(), process_wait()를 호출한다.
static void
run_task (char **argv) {
const char *task = argv[1];
printf ("Executing '%s':\n", task);
#ifdef USERPROG
if (thread_tests){
run_test (task);
} else {
process_wait (process_create_initd (task));
}
#else
run_test (task);
#endif
printf ("Execution of '%s' complete.\n", task);
}
- create_initd(task)
👉 새 프로그램을 실행시킬 커널 스레드를 만든다.
👉 file_name이 이름인 스레드를 만든다.
👉 process_wait()안에 있는 이유는 main 커널 스레드가 종료되지 않도록 기다리게 한다.
tid_t
process_create_initd (const char *file_name) {
char *fn_copy;
tid_t tid;
/* Make a copy of FILE_NAME.
* Otherwise there's a race between the caller and load(). */
fn_copy = palloc_get_page (0);
if (fn_copy == NULL)
return TID_ERROR;
/* fn_copy 주소값에 file_name을 복사해 넣어주고, 4kb로 길이를 한정한다. */
strlcpy (fn_copy, file_name, PGSIZE);
char *tmp;
file_name = strtok_r(file_name, " ", &tmp);
/* 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;
}
④ tid_t process_execute()
- 현재 실행되고 있는 사용자 프로세스를 새 실행 파일의 프로세스로 바꾼다.
/* Switch the current execution context to the f_name.
* Returns -1 on fail. */
int
process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* We cannot use the intr_frame in the thread structure.
* This is because when current thread rescheduled,
* it stores the execution information to the member. */
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
/* We first kill the current context */
process_cleanup();
/* And then load the binary */
success = load (file_name, &_if);
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
/* Start switched process. */
do_iret (&_if);
NOT_REACHED ();
}
⑤ bool load() : 프로그램을 메모리에 적재
- User process의 페이지 테이블 생성
- 파일을 open하고 ELF헤더 정보를 메모리로 읽어들임
- 각 세그먼트의 가상주소공간 위치를 읽어들임
- 각 세그먼트를 파일로부터 읽어 들임
- 스택 생성 및 초기화
/* Loads an ELF executable from FILE_NAME into the current thread.
* Stores the executable's entry point into *RIP
* and its initial stack pointer into *RSP.
* Returns true if successful, false otherwise. */
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;
/* Allocate and activate page directory. */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
// Tokenize & put them to argv
char *token, *save_ptr;
char *argv[64];
int argc = 0;
if (strchr(file_name, ' ')){
for (token = strtok_r (file_name, " ", &save_ptr); token != NULL; token = strtok_r (NULL, " ", &save_ptr)){
argv[argc] = token;
argc++;
}
} else {
argv[argc] = file_name;
argc++;
}
// strlcpy(thread_current()->name, file_name, 16);
file = filesys_open(file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
/* Read and verify executable header. */
/* ELF 파일의 헤더 정보를 읽어와 저장 */
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;
}
/* Read program headers. */
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;
/* TODO: Your code goes here.
* TODO: Implement argument passing (see project2/argument_passing.html). */
/* PROJECT 2: ARGUMENT PASSING */
size_t sum = 0;
char *argv_address[64];
// step 3-1. Break the command into words
for(int i = argc-1; i >= 0; i--) {
size_t len = strlen(argv[i]) + 1; // '\0' 포함
sum += len;
argv_address[i] = (if_->rsp - sum);
memcpy((if_->rsp - sum), argv[i], len);
}
// step 3-2. Word-Align
while((if_->rsp - sum) % 8 != 0){
sum++;
*(uint8_t *) (if_->rsp - sum) = 0;
}
// step 2. Push the address of each string plus a null pointer sentinel
for (int i = argc; i >=0 ; i--){
sum += 8;
if (i == argc)
memset(if_->rsp - sum, 0, sizeof(char **));
else
memcpy(if_->rsp - sum, &argv_address[i], sizeof(char **));
}
// step 1-1. Point %rsi to argv (the address of argv[0]) and set %rdi to argc.
if_->rsp -= sum;
if_->R.rdi = argc;
if_->R.rsi = if_->rsp;
// step 1-2. Push a fake "return address"
memset(if_->rsp - 8, 0, sizeof(void *));
success = true;
done:
/* We arrive here whether the load is successful or not. */
// file_close (file);
if (file)
file_deny_write(file);
return success;
}
02. JUNGLE_PINTOS PROJECT2: Argument Passing 문제 개선
(0) 고민했던 부분
최종적으로 내가 할일 1. 받은 문자열을 pasing한다. 2. pasing하고 남은 인자들을 스택에 저장한다. 처음에는 passing할 곳에 대한 고민을 많이 했었다. load 안에 있는 함수 file_open 때문이였는데, 주석으로는 load에 "여기서 파싱하세요!!"라고 해서 팀원들과 많은 이야기를 나누었었다. 요리저리 해보다 결국은 load 안에서 파싱을 해주고 그 위치는 filesys_open 전으로 파싱해서 filesys_open(file_name)은 인자가 들어가지 않은 file name만 보내준다.
(1) process.c/process_exec()
int process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* We cannot use the intr_frame in the thread structure.
* This is because when current thread rescheduled,
* it stores the execution information to the member. */
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
/* We first kill the current context */
process_cleanup ();
/* argument를 parsing한다. */
char *argv[128];
int argc = 0;
char *token, *save_ptr;
for (token = strtok_r(file_name, " ", &save_ptr); token != NULL;
token = strtok_r(NULL, " ", &save_ptr)){
argv[argc] = token;
argc++;
}
// argv = {"args-single", "onearg", NULL}
/* And then load the binary */
success = load (file_name, &_if);
if (!success)
{
palloc_free_page(file_name);
return -1;
}
/* command line에서 받은 인자들을 스택에 차곡차곡 쌓는다. */
/* 유저 스택에 쌓은 argv의 주소를 가져와야 하므로 _if.rsp+8이다. */
argument_stack(argv, argc, &_if);
_if.R.rdi = argc; // argc -> RDI.
_if.R.rsi = _if.rsp + 8; // argv -> RSI.
/* 작업이 끝났으므로 동적할당한 file_name이 담긴 메모리 free */
palloc_free_page (file_name);
/* 지금까지 바꿔준 인터럽트 프레임의 값으로 레지스터 값을 바꿔준다. 새 프로세스로 switching한다. */
do_iret (&_if);
NOT_REACHED ();
}
(2) process.c / load() 수정
/* Loads an ELF executable from FILE_NAME into the current thread.
* Stores the executable's entry point into *RIP
* and its initial stack pointer into *RSP.
* Returns true if successful, false otherwise. */
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;
/* Allocate and activate page directory. */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
// Tokenize & put them to argv
char *token, *save_ptr;
char *argv[40];
int argc = 0;
if (strchr(file_name, ' '))
{
for (token = strtok_r (file_name, " ", &save_ptr); token != NULL; token = strtok_r (NULL, " ", &save_ptr)){
argv[argc] = token;
argc++;
}
}
else
{
argv[argc] = file_name;
argc++;
}
file = filesys_open(file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
/* Read and verify executable header. */
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;
}
/* Read program headers. */
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;
/* TODO: Your code goes here.
* TODO: Implement argument passing (see project2/argument_passing.html). */
/* PROJECT 2: ARGUMENT PASSING */
size_t sum = 0;
char *argv_address[40];
// step 3-1. Break the command into words
for(int i = argc-1; i >= 0; i--) {
size_t len = strlen(argv[i]) + 1; // '\0' 포함
sum += len;
argv_address[i] = (if_->rsp - sum);
memcpy((if_->rsp - sum), argv[i], len);
}
// step 3-2. Word-Align
while((if_->rsp - sum) % 8 != 0){
sum++;
*(uint8_t *) (if_->rsp - sum) = 0;
}
// step 2. Push the address of each string plus a null pointer sentinel
for (int i = argc; i >=0 ; i--){
sum += 8;
if (i == argc)
memset(if_->rsp - sum, 0, sizeof(char **));
else
memcpy(if_->rsp - sum, &argv_address[i], sizeof(char **));
}
// step 1-1. Point %rsi to argv (the address of argv[0]) and set %rdi to argc.
if_->rsp -= sum;
if_->R.rdi = argc;
if_->R.rsi = if_->rsp;
// step 1-2. Push a fake "return address"
memset(if_->rsp - 8, 0, sizeof(void *));
success = true;
done:
/* We arrive here whether the load is successful or not. */
if (file){
if (thread_current()->my_file){
file_close(thread_current()->my_file);
thread_current()->my_file = NULL;
}
thread_current()->my_file = file;
file_deny_write(thread_current()->my_file);
}
return success;
}
03. JUNGLE_PINTOS PROJECT2: Argument Passing 하면서 알게된 것
(0) MEMORY 란?
컴퓨터는 많은 진화를 거쳐 멀티 프로그래밍에 대한 개념이 등장했다. 이전에는 하나의 프로세스만 사용하다보니 메모리에 통채로 전환해주며 실행하게 되었는데, 이제는 많은 프로세스들이 하나의 물리적 메모리를 공용으로 사용하게 되었다. 그러면서 많은 문제점들이 발생하게 된다. 문제점들은 다음과 같다.
1. 많은 사용자가 동시에 컴퓨터를 사용하고, 현재 실행 중인 작업으로 부터 즉각 응답을 원한다.
2. 여러 프로그램이 메모리에 동시에 존재할 때, A라는 프로세스가 B라는 프로세스를 읽거나 혹은 더 안좋게 쓸 수 있는 상황이 존재할 것이다.
시분할을 구현하는 한 가지 방법은 하나의 프로세스를 짧은 시간 동안 실행시켜 해당 기간 동안 프로세스에게 모든 메모리를 접근할 권한이 주어진다.
우리는 이런 위험에 대비하여 주소공간(address space)를 만들었다.
- 코드는 반드시 메모리에 존재해야하고 따라서 주소 공간에 존재한다.
- 스택은 함수 호출 체인 상의 현재 위치, 지역 변수, 함수 인자와 반환 값 등을 저장하는데 사용된다.
- 힙은 동적으로 할당되는 메모리를 위해 사용된다.
(1) 그럼 OS가 하는 memory의 가상화는 대체 뭘까?
실행 중인 프로그램은 자신의 특정 주소의 메모리에 탑재되고 , 매우 큰 주소 공간을 가지고 있다고 생각한다. 하지만 실상은 그렇지 않다.
가상 메모리 시스템의 주요 목표는 총 4가지이다. 첫째, 투명성은 운영체제는 실행 중인 프로그램이 가상 메모리의 존재를 인식하지 못하도록 가상 메모리 시스템을 구현해야 한다. 즉 프로그램은 자신의 전용 물리 메모리를 소유한 것 처럼 많은 작업들이 메모리를 공유하고 소유한 것 처럼 작성해야한다. 둘째, 효율성이다. 운영체제는 가상화가 시간과 공간 측면에서 효율적이도록 해야한다. 프로그램이 너무 느리게 실행되서는 안되고 공간적으로 가상화를 지원하기 위한 구조를 위해 너무 많은 메모리를 사용해서는 안된다. 마지막으로 운영체제는 프로세스가 다른 프로세스로부터 보호해야하고 운영체제 자신도 프로세스로부터 보호해야 한다. 우리는 각 프로세스를 격리해야하며, 프로세스가 각 메모리에서 안전하게 실행할 수 있도록 해야한다.
(2) Memory Layout
1) USER VIRTUAL MEMORY(0 ~ KERN_BASE) : 유저 가상메모리
2) KERNEL VIRTUAL MEMORY : 커널 가상 메모리
#define KERN_BASE { /* Omit details */ }
#define is_user_vaddr(vaddr) { /* Omit details */ }
#define is_kernel_vaddr(vaddr) { /* Omit details */ }
USER_STACK +----------------------------------+
| user stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| grows upward |
| ^ |
| | |
| | |
+----------------------------------+
| uninitialized data segment (BSS) |
+----------------------------------+
| initialized data segment |
+----------------------------------+
| code segment |
0x400000 +----------------------------------+
| |
| |
| |
| |
| |
0 +----------------------------------+
'회고록' 카테고리의 다른 글
AWSOME DAY! AWS 온라인 컨퍼런스 참여기 (0) | 2023.06.08 |
---|---|
[WIL]🙈PINTOS_KAIST : Project 3. VIRTUAL MEMORY (2) : Memory Management 🙉 (0) | 2022.12.01 |
[WIL]🙈PINTOS_KAIST : Project 1. THREADS (1) Alarm Clock 🙉 (0) | 2022.11.20 |
👩🏼💻TIL : 16. 🎄🎄RED-BLACK 트리를 구현하자🎄🎄 (0) | 2022.10.25 |
👩🏼💻TIL : 13. CODE Review (0) | 2022.10.20 |
댓글