프로세스는 "머신(machine)에 대한 추상화" 이다.
조금 더 구체적으로는 CPU / Memory / Storage를 가진 컴퓨터의 추상화
각 응용프로그램이 이러한 머신의 자원을 독점해 사용하고 있는 것 같은 환상(편한 착각)을 제공하는 추상화
프로세스라는 추상화를 통해 protection 과 isolation 이라는 목적을 달성할 수 있다.
목적 달성 방법 : 유저 프로그램이 머신 자원에 직접적으로 접근하는 것을 막는다.
➡️ 유저 모드 / 커널 모드의 구분, 사적 주소공간(각 프로그램의 주소공간을 구분)
Argument Passing
저 프로그램을 실행할 수 있어야 하기 때문에, 유저 프로그램을 load하고, command line으로 받은 argument 들을 프로그램에 전달하는 과정을 다뤘다.

목표: command line을 parsing해서 file_name을 찾는다.

... process.c 추가된 부분
process_create_initd ()
/* Create a new thread to execute FILE_NAME. */
/** Project2: for Test Case - 직접 프로그램을 실행할 때에는 이 함수를 사용하지 않지만 make check에서
* 이 함수를 통해 process_create를 실행하기 때문에 이 부분을 수정해주지 않으면 Test Case의 Thread_name이
* 커맨드 라인 전체로 바뀌게 되어 Pass할 수 없다.
*/
char *ptr;
strtok_r(file_name, " ", &ptr);
/** --------------------------------------------------------------------------------------------- */
/* FILE_NAME을 실행할 새 스레드를 만듭니다. */
tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
...
파일 이름 복사
fn_copy = palloc_get_page (0);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy (fn_copy, file_name, PGSIZE);
file_name 문자열의 사본을 생성하여 fn_copy에 저장합니다.file_name은 호출자에 의해 공유될 수 있으므로, load() 함수가 동시에 접근하면 Race Condition이 발생할 수 있습니다. 이를 방지하기 위해 사본을 생성합니다.새로운 스레드 생성
tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
thread_create 함수는 새로운 스레드를 생성하고 file_name을 스레드 이름으로 설정합니다.initd는 새로 생성된 스레드가 실행할 함수입니다.
char *ptr;
strtok_r(file_name, " ", &ptr);
file_name 전체가 스레드 이름으로 사용됩니다. 예를 들어, 아래와 같이 호출된 경우:
process_create_initd("program arg1 arg2");
file_name으로 전달된 전체 문자열 "program arg1 arg2"가 스레드 이름으로 설정됩니다."program"만 스레드 이름으로 설정되어야 합니다.strtok_r로 해결strtok_r를 사용하여 file_name에서 첫 번째 단어를 추출합니다. 결과적으로 스레드 이름은 프로그램 이름인 "program"으로 설정됩니다.thread_create는 이제 "program"을 스레드 이름으로 사용하므로 Test Case의 조건을 만족하게 됩니다.목표: 인자로 들어오는 f_name을 parsing하고 user stack에 매개변수들을 push한다.

// process.c
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. */
/* 스레드 구조에서는 intr_frame을 사용할 수 없습니다.
* 현재 쓰레드가 재스케줄 되면 실행 정보를 멤버에게 저장하기 때문입니다. */
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 ();
/** #Project 2: Command Line Parsing - 문자열 분리 */
char *ptr, *arg;
int argc = 0;
char *argv[64];
for (arg = strtok_r(file_name, " ", &ptr); arg != NULL; arg = strtok_r(NULL, " ", &ptr))
argv[argc++] = arg;
/* And then load the binary */
success = load (file_name, &if_);
/* If load failed, quit. */
if (!success)
return -1;
argument_stack(argv, argc, &if_);
palloc_free_page(file_name);
/** #Project 2: Command Line Parsing - 디버깅용 툴 */
// hex_dump(if_.rsp, if_.rsp, USER_STACK - if_.rsp, true);
/* Start switched process. */
do_iret(&if_);
NOT_REACHED();
}
// 추가된 부분
/** #Project 2: Command Line Parsing - 문자열 분리 */
char *ptr, *arg;
int argc = 0;
char *argv[64];
for (arg = strtok_r(file_name, " ", &ptr); arg != NULL; arg = strtok_r(NULL, " ", &ptr))
argv[argc++] = arg;
argument_stack(argv, argc, &if_);
추가된 부분은 Command Line Parsing 기능을 추가하기 위해 삽입되었습니다. 프로그램 실행 시 전달된 명령어와 인자를 파싱하여 문자열로 분리하고, 이를 배열에 저장하는 역할을 합니다.
c
코드 복사
char *ptr, *arg;
int argc = 0;
char *argv[64];
ptr: strtok_r에서 문자열의 나머지 부분을 저장하는 포인터.arg: 문자열 분리 결과, 현재 토큰을 저장.argc: 인자의 개수를 세는 변수.argv[64]: 명령어와 인자를 저장하는 배열. 최대 64개의 인자를 저장 가능.c
코드 복사
for (arg = strtok_r(file_name, " ", &ptr); arg != NULL; arg = strtok_r(NULL, " ", &ptr))
argv[argc++] = arg;
strtok_r를 사용하여 file_name 문자열을 공백(" ")을 기준으로 분리.file_name을 strtok_r로 전달하여 첫 번째 토큰을 추출.NULL을 첫 번째 인자로 전달하여 나머지 문자열에서 다음 토큰을 추출.argv 배열에 저장.argc를 증가시켜 인자 개수를 기록.명령어가 아래와 같이 전달되었다고 가정:
c
코드 복사
process_exec("program arg1 arg2 arg3")
파싱 후 변수 값:
argc = 4 (총 4개의 토큰)argv = {"program", "arg1", "arg2", "arg3"}file_name 문자열 전체를 하나의 실행 파일 이름으로 처리했습니다."program arg1 arg2"를 실행할 때, "program arg1 arg2" 전체를 실행 파일 이름으로 간주했습니다."program"은 실행 파일 이름, "arg1 arg2"는 프로그램에 전달될 인자.argument_stack() 함수에서 유저 스택에 적절히 저장됩니다.argc와 argv를 통해 명령어와 인자에 접근할 수 있습니다.문자열 분리
strtok_r를 사용하여 명령어와 인자를 분리해 argv 배열에 저장.argc에 인자 개수를 기록.유저 스택 초기화
c
코드 복사
argument_stack(argv, argc, &if_);
argv와 argc를 사용하여 유저 스택에 명령어와 인자를 저장.main(int argc, char *argv[])의 형태로 인자 접근 가능.실행
목표: process_exec() 함수에서 parsing한 프로그램 이름과 인자를 스택에 저장하기 위해 사용할 함수를 새로 선언한다.
/** #Project 2: Command Line Parsing - 유저 스택에 파싱된 토큰을 저장하는 함수 */
void argument_stack(char **argv, int argc, struct intr_frame *if_) {
char *arg_addr[100];
int argv_len;
for (int i = argc - 1; i >= 0; i--) {
argv_len = strlen(argv[i]) + 1;
if_->rsp -= argv_len;
memcpy(if_->rsp, argv[i], argv_len);
arg_addr[i] = if_->rsp;
}
while (if_->rsp % 8)
*(uint8_t *)(--if_->rsp) = 0;
if_->rsp -= 8;
memset(if_->rsp, 0, sizeof(char *));
for (int i = argc - 1; i >= 0; i--) {
if_->rsp -= 8;
memcpy(if_->rsp, &arg_addr[i], sizeof(char *));
}
if_->rsp = if_->rsp - 8;
memset(if_->rsp, 0, sizeof(void *));
if_->R.rdi = argc;
if_->R.rsi = if_->rsp + 8;
}
이 코드는 유저 스택에 명령어와 인자를 저장하는 스택 초기화 함수로, argv와 argc 정보를 이용하여 스택에 파싱된 데이터를 배치하고 프로그램 실행에 필요한 상태를 설정합니다.
argv 문자열을 스택에 저장argv 배열을 참조할 수 있도록 설정합니다.argc와 argv 전달argc(인자 개수)와 argv(인자 주소 배열)를 설정합니다.c
코드 복사
for (int i = argc - 1; i >= 0; i--) {
argv_len = strlen(argv[i]) + 1; // 인자의 길이를 계산 (널 문자를 포함)
if_->rsp -= argv_len; // 스택 포인터를 인자의 길이만큼 이동
memcpy(if_->rsp, argv[i], argv_len); // 스택에 인자를 복사
arg_addr[i] = if_->rsp; // 복사한 인자의 주소를 저장
}
argv 배열의 마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 저장.arg_addr 배열에 기록.css
코드 복사
[ arg3 ] <- rsp
[ arg2 ]
[ arg1 ]
[ program ]
c
코드 복사
while (if_->rsp % 8)
*(uint8_t *)(--if_->rsp) = 0;
c
코드 복사
if_->rsp -= 8;
memset(if_->rsp, 0, sizeof(char *));
argv 배열의 끝을 나타내는 NULL 포인터를 삽입하여 프로그램이 이를 확인하도록 함.c
코드 복사
for (int i = argc - 1; i >= 0; i--) {
if_->rsp -= 8;
memcpy(if_->rsp, &arg_addr[i], sizeof(char *));
}
arg_addr 배열의 주소를 거꾸로 스택에 저장.css
코드 복사
[ &arg3 ] <- rsp
[ &arg2 ]
[ &arg1 ]
[ &program ]
argc와 NULL 포인터 추가c
코드 복사
if_->rsp -= 8;
memset(if_->rsp, 0, sizeof(void *));
argc와 argv 설정c
코드 복사
if_->R.rdi = argc;
if_->R.rsi = if_->rsp + 8;
argc(인자 개수)을 rdi 레지스터에 저장.argv(인자 주소 배열의 시작 주소)를 rsi 레지스터에 저장.main(int argc, char *argv[]) 형태로 호출되도록 설정.argv 배열의 데이터를 스택에 복사하고 주소를 기록.argv 배열을 구성.argc와 argv를 레지스터에 설정하여 프로그램 실행 준비 완료."program arg1 arg2")파싱 결과:
c
코드 복사
argv = {"program", "arg1", "arg2"}
argc = 3
스택 구조:
css
코드 복사
[ NULL ]
[ &arg2 ]
[ &arg1 ]
[ &program ]
[ "arg2" ]
[ "arg1" ]
[ "program" ]
레지스터 상태:
rdi = 3 (argc)rsi = rsp + 8 (argv 배열의 시작 주소)// process.c
// process_wait () 구현
int
process_wait (tid_t child_tid UNUSED) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
/* XXX: Hint) process_wait(initd)인 경우 pintos가 종료됩니다. process_wait를 구현하기
전에 여기에 무한 루프를 추가하는 것이 좋습니다. */
thread_t *child = get_child_process(child_tid);
if (child == NULL)
return -1;
sema_down(&child->wait_sema); // 자식 프로세스가 종료될 때 까지 대기.
int exit_status = child->exit_status;
list_remove(&child->child_elem);
sema_up(&child->exit_sema); // 자식 프로세스가 죽을 수 있도록 signal
return exit_status;
}
// process.c
// process_exit () 구현
void
process_exit (void) {
thread_t *curr = thread_current();
/* TODO: Your code goes here.
* TODO: Implement process termination message (see
* TODO: project2/process_termination.html).
* TODO: We recommend you to implement process resource cleanup here. */
for (int fd = 0; fd < curr->fd_idx; fd++) // FDT 비우기
close(fd);
file_close(curr->runn_file); // 현재 프로세스가 실행중인 파일 종료
palloc_free_multiple(curr->fdt, FDT_PAGES);
process_cleanup();
sema_up(&curr->wait_sema); // 자식 프로세스가 종료될 때까지 대기하는 부모에게 signal
sema_down(&curr->exit_sema); // 부모 프로세스가 종료될 떄까지 대기
}
// process.c
// load () 안에 선언
/** #Project 2: System Call - 파일 실행 명시 및 접근 금지 설정 */
t->runn_file = file;
file_deny_write(file); /** #Project 2: Denying Writes to Executables */
// load () 안에 기존 코드 주석 처리
// file_close (file);

/** #Project 2: System Call */
#include <string.h>
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/mmu.h"
#include "threads/palloc.h"
#include "userprog/process.h"
/** ----------------------- */
/** #Project 2: System Call */
struct lock filesys_lock; // 파일 읽기/쓰기 용 lock

void
syscall_init(void) {
write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48 |
((uint64_t)SEL_KCSEG) << 32);
write_msr(MSR_LSTAR, (uint64_t)syscall_entry);
/* The interrupt service rountine should not serve any interrupts
* until the syscall_entry swaps the userland stack to the kernel
* mode stack. Therefore, we masked the FLAG_FL. */
write_msr(MSR_SYSCALL_MASK,
FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
/** #Project 2: System Call - read & write 용 lock 초기화 */
lock_init(&filesys_lock);
}
void
syscall_handler (struct intr_frame *f UNUSED) {
// TODO: Your implementation goes here.
int sys_number = f->R.rax;
// Argument 순서
// %rdi %rsi %rdx %r10 %r8 %r9
switch (sys_number) {
case SYS_HALT:
halt();
break;
case SYS_EXIT:
exit(f->R.rdi);
break;
case SYS_FORK:
f->R.rax = fork(f->R.rdi);
break;
case SYS_EXEC:
f->R.rax = exec(f->R.rdi);
break;
case SYS_WAIT:
f->R.rax = process_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;
case SYS_DUP2:
f->R.rax = dup2(f->R.rdi, f->R.rsi);
break;
default:
exit(-1);
}
}
void check_address(void *addr) {
if (is_kernel_vaddr(addr) || addr == NULL || pml4_get_page(thread_current()->pml4, addr) == NULL)
exit(-1);
}
void halt(void) {
power_off();
}
void exit(int status) {
thread_t *curr = thread_current();
curr->exit_status = status;
/** #Project 2: Process Termination Messages */
printf("%s: exit(%d)\n", curr->name, curr->exit_status);
thread_exit();
}
pid_t fork(const char *thread_name) {
check_address(thread_name);
return process_fork(thread_name, NULL);
}
int exec(const char *cmd_line) {
check_address(cmd_line);
off_t size = strlen(cmd_line) + 1;
char *cmd_copy = palloc_get_page(PAL_ZERO);
if (cmd_copy == NULL)
return -1;
memcpy(cmd_copy, cmd_line, size);
if (process_exec(cmd_copy) == -1)
return -1;
return 0; // process_exec 성공시 리턴 값 없음 (do_iret)
}
int wait(pid_t tid) {
return process_wait(tid);
}
bool create(const char *file, unsigned initial_size) {
check_address(file);
return filesys_create(file, initial_size);
}
bool remove(const char *file) {
check_address(file);
return filesys_remove(file);
}
int open(const char *file) {
check_address(file);
struct file *newfile = filesys_open(file);
if (newfile == NULL)
return -1;
int fd = process_add_file(newfile);
if (fd == -1)
file_close(newfile);
return fd;
}
int filesize(int fd) {
struct file *file = process_get_file(fd);
if (file == NULL)
return -1;
return file_length(file);
}
int read(int fd, void *buffer, unsigned length) {
thread_t *curr = thread_current();
check_address(buffer);
struct file *file = process_get_file(fd);
if (file == 1) { // 0(stdin) -> keyboard로 직접 입력
int i = 0; // 쓰레기 값 return 방지
char c;
unsigned char *buf = buffer;
for (; i < length; i++) {
c = input_getc();
*buf++ = c;
if (c == '\0')
break;
}
return i;
}
if (file == NULL || file == STDOUT || file == STDERR) // 빈 파일, stdout, stderr를 읽으려고 할 경우
return -1;
// 그 외의 경우
off_t bytes = -1;
lock_acquire(&filesys_lock);
bytes = file_read(file, buffer, length);
lock_release(&filesys_lock);
return bytes;
}
int write(int fd, const void *buffer, unsigned length) {
check_address(buffer);
thread_t *curr = thread_current();
off_t bytes = -1;
struct file *file = process_get_file(fd);
if (file == STDIN || file == NULL) // stdin에 쓰려고 할 경우
return -1;
if (file == STDOUT) { // 1(stdout) -> console로 출력
putbuf(buffer, length);
return length;
}
if (file == STDERR) { // 2(stderr) -> console로 출력
putbuf(buffer, length);
return length;
}
lock_acquire(&filesys_lock);
bytes = file_write(file, buffer, length);
lock_release(&filesys_lock);
return bytes;
}
void seek(int fd, unsigned position) {
if (fd < 0)
return;
struct file *file = process_get_file(fd);
if (file == NULL || (file >= STDIN && file <= STDERR))
return;
file_seek(file, position);
}
int tell(int fd) {
struct file *file = process_get_file(fd);
if (file == NULL || (file >= STDIN && file <= STDERR))
return -1;
return file_tell(file);
}
void close(int fd) {
thread_t *curr = thread_current();
struct file *file = process_get_file(fd);
if (file == NULL)
return;
process_close_file(fd);
if (file == STDIN) {
file = 0;
return;
}
if (file == STDOUT) {
file = 0;
return;
}
if (file == STDERR) {
file = 0;
return;
}
if (file->dup_count == 0)
file_close(file);
else
file->dup_count--;
}
/** #Project 2: Extend File Descriptor (Extra) */
int dup2(int oldfd, int newfd) {
struct file *oldfile = process_get_file(oldfd);
struct file *newfile = process_get_file(newfd);
if (oldfile == NULL)
return -1;
if (oldfd == newfd)
return newfd;
if (oldfile == newfile)
return newfd;
close(newfd);
newfd = process_insert_file(newfd, oldfile);
return newfd;
}

static void
page_fault (struct intr_frame *f) {
bool not_present; /* True: not-present page, false: writing r/o page. */
bool write; /* True: access was write, false: access was read. */
bool user; /* True: access by user, false: access by kernel. */
void *fault_addr; /* Fault address. */
/* Obtain faulting address, the virtual address that was
accessed to cause the fault. It may point to code or to
data. It is not necessarily the address of the instruction
that caused the fault (that's f->rip). */
fault_addr = (void *) rcr2();
/* Turn interrupts back on (they were only off so that we could
be assured of reading CR2 before it changed). */
intr_enable ();
/* Determine cause. */
not_present = (f->error_code & PF_P) == 0;
write = (f->error_code & PF_W) != 0;
user = (f->error_code & PF_U) != 0;
#ifdef VM
/* For project 3 and later. */
if (vm_try_handle_fault (f, fault_addr, user, write, not_present))
return;
#endif
/* Count page faults. */
page_fault_cnt++;
exit(-1); /** Test Case 가 Hardware 수준에서 페이지 폴트를 호출하기 때문에 Test Case 통과를 위해서 exception을 수정해야함. */
/* If the fault is true fault, show info and exit. */
printf ("Page fault at %p: %s error %s page in %s context.\n",
fault_addr,
not_present ? "not present" : "rights violation",
write ? "writing" : "reading",
user ? "user" : "kernel");
kill (f);
}