😪 Day 4: Day-off

💻 인수 전달 구현 (process.c)
#define ARGUMENT_LIMIT 64 // 임의로 정한 인수 최대값
int process_exec (void *f_name) {
char *file_name = f_name;
bool success;
/* Parse file_name into command line and arguments */
char *argv[ARGUMENT_LIMIT];
char *save_ptr = NULL;
char *token = strtok_r(f_name, " ", &save_ptr);
int argc = 0;
while (token) {
argv[argc++] = token;
token = strtok_r(NULL, " ", &save_ptr);
}
argv[argc] = 0;
file_name = argv[0];
…
_if.R.rsi = (uint64_t) argv; // Point %rsi to argv (the address of argv[0])
_if.R.rdi = argc; // Set %rdi to argc
process_cleanup ();
success = load (file_name, &_if); // Pass the program name to load()
argument_stack(&_if);
hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true); // for debugging user stack
…
}
#define STACK_BOTTOM (USER_STACK - PGSIZE)
static bool argument_stack(struct intr_frame *if_) {
char** argv = (char **) if_->R.rsi;
int argc = if_->R.rdi;
char *addrs[argc]; // argv[i]의 유저 스택 상의 주소 저장
/* 1. Push argv[i][…] */
for (int i = argc-1; i >= 0; i--) {
size_t len = strlen(*(argv + i)) + 1; // strlen은 '\0'을 세지 않기 때문에 +1 해줌
if_->rsp -= len;
if ((uint64_t)if_->rsp < STACK_BOTTOM)
return false;
memcpy(if_->rsp, *(argv + i), len);
addrs[i] = if_->rsp;
}
/* 2. Push word-align */
while ((uint64_t)if_->rsp % 8 != 0)
if_->rsp--;
if ((uint64_t)if_->rsp < STACK_BOTTOM)
return false;
/* 3. Push argv[argc] */
uintptr_t zero = 0;
if_->rsp -= sizeof(uintptr_t);
memcpy(if_->rsp, &zero, sizeof(uintptr_t));
/* 4. Push argv (pointers) */
for (int i = argc-1; i >= 0; i--) {
// if_->rsp -= sizeof(uintptr_t);
// memcpy(if_->rsp, (argv + i), sizeof(uintptr_t));
if_->rsp -= sizeof(char *);
if ((uint64_t)if_->rsp < STACK_BOTTOM)
return false;
memcpy(if_->rsp, &addrs[i], sizeof(char *));
}
/* 5. Push fake return address */
if_->rsp -= sizeof(uintptr_t);
if ((uint64_t)if_->rsp < STACK_BOTTOM)
return false;
memcpy(if_->rsp, &zero, sizeof(uintptr_t));
return true;
}
#define STACK_BOTTOM (USER_STACK - PGSIZE): 커널 주소나 매핑되지 않은 메모리 영역에 접근해서 page fault가 발생하는 것을 막기 위해 사용자 스택의 하한선(bottom)을 정의한 것이다.if ((uint64_t)if_->rsp < STACK_BOTTOM) return false;:rsp를 내릴 때마다 스택 허용 범위에 있는지 검사해서, 영역을 벗어나게 되면 false를 리턴한다.
😲 프로그램 실행 실패 이슈

$ pintos --fs-disk=10 -p tests/userprog/args-single -- -q -f run 'args-single onearg'
…
Kernel PANIC at ../../filesys/fsutil.c:109 in fsutil_put(): tests/userprog/args-single: create failed
…
backtrace 결과 작성한 코드 상의 문제가 아니라, PintOS 디스크 내부에 파일을 생성하는 데 실패한 것이 원인이었다. 리눅스 상에서 /tests 디렉토리가 존재하는 것과는 별개로, 핀토스의 파일 시스템이 자동으로 디렉토리를 생성하지 않기 때문이라고는 하는데…
$ cp tests/userprog/args-single ./args-single
$ pintos -v -k --fs-disk=filesys.dsk -p args-single -- -f -q
문제 해결을 위해 경로 없이 파일 시스템 루트에 바로 args-single을 복사했다.
-p args-single: 로컬 경로에 있는 파일을 디스크의 /args-single로 복사함- 이걸 위해 tests/userprog/args-single을 현재 디렉토리로 복사해야 함

드디어 kernel PANIC도 뜨지 않고 created failed도 뜨지 않았다.
$ pintos -v -k --fs-disk=filesys.dsk -- -q run 'args-single onearg'

이제 PintOS에서 유저 프로그램을 성공적으로 실행했고, (아마도) 인자도 잘 전달됐고, 스택도 제대로 구성되었다 🥳
🔎 hex_dump 출력 분석
Executing 'args-single onearg':
000000004747ffc0 00 00 00 00 00 00 00 00 | ........|
000000004747ffd0 ed ff 47 47 00 00 00 00-f9 ff 47 47 00 00 00 00 |..GG......GG....|
000000004747ffe0 00 00 00 00 00 00 00 00-00 00 00 00 00 61 72 67 |.............arg|
000000004747fff0 73 2d 73 69 6e 67 6c 65-00 6f 6e 65 61 72 67 00 |s-single.onearg.|
이 내용은 리틀 엔디안 기준으로 해석되어야 하며, 가장 위 주소(0x4747ffc8)가 스택의 최하단(rsp)이다.

깃북에서 봤던 이론 상의 테이블과 동일한 양상으로 argv 배열의 데이터, 패딩, argv 배열의 포인터, 가짜 리턴 주소가 스택에 잘 푸시된 것으로 보아 argument passing이 잘 구현된 것으로 보인다
![]()
args-multiple 테스트
$ cp tests/userprog/args-single ./args-multiple
$ pintos -v -k --fs-disk=filesys.dsk -p args-multiple -- -f -q
$ pintos -v -k --fs-disk=filesys.dsk -- -q run 'args-multiple some arguments for you!'

여러 개의 인수를 전달하는 것도 잘 확인되었다.

System Call
아래 내용은 ppt(32비트) 기준으로 작성되어 지금 기준으로는 내용이 다를 수 있다.
🔹 시스템 콜
- OS에 의해 제공되는 서비스를 위한 프로그래밍 인터페이스
- 유저 모드 프로그램들이 커널의 기능을 사용하도록 허락
- 시스템 콜들은 커널 모드에서 실행되어 유저 모드로 리턴함
- 시스템 콜의 핵심 포인트는 실행 모드의 우선순위가 하드웨어 인터럽트가 만드는 시스템 콜 수준으로 상승하는 것

🔹 시스템 콜의 호출 과정

🔹 시스템 콜 핸들러
- 시스템 콜 번호를 사용해 시스템 콜 핸들러로부터 시스템 콜을 호출한다.

- 시스템 콜 핸들러 구현 사항
- 시스템 콜 핸들러가 시스템 콜 번호를 사용해서 시스템 콜을 호출하게 만들기
- 파라미터 리스트의 포인터들의 유효성 체크
- 포인터들은 커널 영역이 아닌 유저 영역을 가리켜야 함
- 포인터들이 유효한 주소를 가리키지 않으면 page fault
- 유저 스택에서 커널로 인수 복사하기
- rax 레지스터에 시스템 콜의 리턴값 저장하기
🔹 주소의 유효성
- 유저는 시스템 콜을 통해 유효하지 않은 포인터를 지나갈 수 있다.
- null 포인터 또는 매핑되지 않은 VM으로의 포인터
- 커널 VMAS (
KERN_BASE위로) 로의 포인터
- 커널은 포인터의 무효성을 탐지해 커널이나 다른 실행 중인 프로세스에 해를 끼치지 않고 프로세스를 종료해야 한다.
- 어떻게 탐지해?
- 유저에서 제공된 포인터의 유효성 확인하기
- 유저 메모리 접근을 다루는 가장 단순한 방법
pagedir.c와vaddr.h의 함수 사용
- KERN_BASE 아래의 유저 포인터만 체크하기
- 유효하지 않은 포인터는 page fault를 발생시킨다.
page_fault()코드를 수정해서 다룰 수 있다. - MMU의 이점을 살려 1번 방법보다 빠르고, 실제 커널에서 주로 사용된다 (하지만 더 어렵다).
- 유효하지 않은 포인터는 page fault를 발생시킨다.
- 유저에서 제공된 포인터의 유효성 확인하기
- 위의 두 방법 중 어떤 것을 사용하든지 간에 자원의 leak가 발생하면 안 된다.
- 아래 그림과 같은 경우, 종료하기 전에 lock을 release하거나 페이지를 free해야 한다.

/* Reads a byte at user virtual address UADDR.
UADDR must be below PHYS_BASE.
Returns the byte value if successful, -1 if a segfault
occurred. */
static int
get_user (const uint8_t *uaddr)
{
int result;
asm ("movl $1f, %0; movzbl %1, %0; 1:"
: "=&a" (result) : "m" (*uaddr));
return result;
}
/* Writes BYTE to user address UDST.
UDST must be below PHYS_BASE.
Returns true if successful, false if a segfault occurred.*/
static bool
put_user (uint8_t *udst, uint8_t byte)
{
int error_code;
asm ("movl $1f, %0; movb %b2, %1; 1:"
: "=&a" (error_code), "=m" (*udst) : "q" (byte));
return error_code != -1;
}
page_fault()도 수정해야 한다:rax를0xffffffff로 설정하고 이전 값을rip로 복사하기
🔹 프로세스 계층
- 부모와 자식 간의 관계를 표현
- 부모로의 포인터:
struct thread* - 형제로의 포인터:
struct list - 자식으로의 포인터:
struct list_elem
- 부모로의 포인터:

현대의 x86-64 아키텍처에서는, syscall 인스트럭션이 시스템 콜을 발생시키는 가장 흔하게 쓰이는 방법이다. 핀토스에서는 사용자 프로그램이 시스템 콜을 만드는 데 syscall을 사용한다. 시스템 콜 번호와 추가적인 인수들은 일반적인 방법으로 syscall 인스트럭션을 실행하기 전에 레지스터에 설정될 것이다. 단, 다음 두 가지는 다르다.
%rax는 시스템 콜 번호다.- 네 번째 인수는
%rcx가 아니라%r10이다.
따라서, 시스템 콜 핸들러 syscall_handler()가 컨트롤을 가질 때, 시스템 콜 번호는 rax에, 그리고 인수들은 %rdi, %dsi, %rdx, %r10, %r9, %r8 순서로 전달된다.
호출자의 레지스터는 그것으로 전달되는 struct intr_frame에 접근 가능하다. (struct intr_frame은 커널 스택에 있다.)
함수 리턴값에 대한 x86-64 규약은 RAX 레지스터에 넣는 것이다. 리턴값이 있는 시스템 콜도 struct intr_frame의 rax를 수정함으로써 그렇게 할 수 있다.
File Manipulation
아래 내용은 ppt(32비트) 기준으로 작성되어 지금 기준으로는 내용이 다를 수 있다.
🔹 UNIX의 파일 디스크립터
- 파일 디스크립터를 사용해 파일에 접근한다.

🔹 파일 디스크립터 테이블
- FDT 구현하기
- 각 프로세스는 각각의 FDT를 가진다 (최대 크기: 64 엔트리)
- FDT는
struct file로의 포인터의 배열이다. - FD는 FDT의 인덱스 번호로 순차적으로 할당된다.
- FD
0과1은 각각stdin,stdout에 할당된다. open()은fd를 리턴한다.close()는fd번 인덱스의 FD 엔트리를 0으로 설정한다.
struct thread에 FDT를 정의하고, 커널 메모리 영역에 FDT를 할당해 연관된 포인터를struct thread에 추가한다.

- 스레드가 생성되면
- FDT를 할당한다.
- FDT로의 포인터를 초기화한다.
fd0,fd1을stdin,stdout을 위해 예약한다.
- 스레드가 종료되면
- 모든 파일을 닫는다.
- FDT를 할당 해제한다.
- 파일에 대한 race condition을 피하기 위해 전역 락을 사용한다.
syscall.h에 전역 락을 정의한다:struct lock filesys_locksyscall_init()에서 락을 초기화한다:lock_init()- 전역 락으로 파일 시스템과 관련된 코드를 보호한다.
🔹 테스트를 위해 page_fault() 수정하기
- 일부 테스트는 우리의 커널이 bad 프로세스를 적절히 처리하는지 확인한다.
- 핀토스는 page fault 발생 시 프로세스를 죽이고 스레드 이름과 exit 상태 -1을 출력해야 한다.
(예)bad-jump: exit(-1)
static void page_fault (struct intr_frame *f)
{
…
not_present = (f->error_code & PF_P) == 0;
write = (f->error_code & PF_W) != 0;
user = (f->error_code & PF_U) != 0;
/* Call exit(-1) */
…
}
🔹 파일 관련 시스템 콜 추가하기
bool create(const char *file, unsigned initial_size)initial_size크기의 파일을 생성한다.bool filesys_create(const char *name, off_t initial_size)를 사용한다.- 성공하면 true, 실패하면 false를 리턴한다.
bool remove(const char *file)- 이름이
file인 파일을 제거한다. bool filesys_remove(const char *name)를 사용한다.- 성공하면 true, 실패하면 false를 리턴한다.
- 파일은 열려있든지 아닌지에 관계없이 삭제된다.
- 이름이
💭 열려 있는 파일이 삭제되면 어떻게 될까?
파일을 위한 기본 UNIX semantics를 구현해야 한다. 즉, 파일이 삭제될 때 그 파일에 대한 파일 디스크립터를 가지는 프로세스는 계속해서 그 디스크립터를 사용할 것이다. 그 프로세스가 여전히 그 파일을 읽고 쓸 수 있다는 뜻이다. 파일은 이름을 가지지 않고, 다른 프로세스들은 그 파일을 열 수도 없지만, 그 파일과 관련된 모든 파일 디스크립터들이 닫히거나 머신이 종료되지 전까지는 계속해서 존재할 것이다.
int open(const char *file)file이라는 경로에 대응되는 파일을 연다.- 파일의
fd를 리턴한다. struct file *filesys_open(const char *name)를 사용한다.
int filesize(int fd)fd로 열린 파일의 바이트 사이즈를 리턴한다.off_t file_length(struct file *file)를 사용한다.
int read(int fd, void *buffer, unsigned size)fd로 열린 파일에서size바이트 만큼buffer로 읽어들인다.- 실제로 읽어들인 바이트의 수를 리턴한다(EOF에서는 0). 실패 시에는 -1을 리턴한다.
fd가0이면,uint8_t input_getc(void)를 사용해 키보드로부터 입력을 받는다.- 그 외에는
off_t file_read(struct file *file, void *buffer, off_t size)를 사용해 파일로부터 입력을 받는다.
int write(int fd, const void *buffer, unsigned size)buffer에서size바이트만큼 열린 파일fd에 쓴다.- 실제로 쓰여진 바이트 수를 리턴한다.
fd가1이면,void putbuf(const char *buffer, size_t n)를 사용해 콘솔에 출력한다.- 그 외에는
off_t file_write(struct file *file, const void *buffer, off_t size)를 사용해 파일에 출력한다.
void seek(int fd, unsigned position)- 열린 파일
fd에서 read나 write를 할 다음 바이트를position으로 바꾼다. void file_seek(struct file *file, off_t new_pos)를 사용한다.
- 열린 파일
unsigned tell(int fd)- 열린 파일
fd에서 read나 write를 할 다음 바이트의 position을 리턴한다. off_t file_tell(struct file *file)를 사용한다.
- 열린 파일
void close(int fd)- 파일 디스크립터
fd를 닫는다. void file_close(struct file *file)를 닫는다.
- 파일 디스크립터
🔹 실행 파일에 대한 쓰기 거부하기
- OS가 수정되고 있는 파일을 실행하려고 시도하면 어떻게 될까? → 예측 불가능한 결과를 초래한다.
- 파일이 실행되는 동안 열린다면, 그 파일을 수정하게 허락해서는 안 된다.
- 접근법
- 파일이 실행 중 load되면
file_deny_write()를 호출한다. - 파일이 실행을 종료하면
file_allow_write()를 호출한다.
- 파일이 실행 중 load되면
static bool load (const char *file_name, struct intr_frame *if_)- 프로그램이 open되면
file_deny_write()를 호출한다. struct thread에 실행중인 파일 구조체를 추가한다.
- 프로그램이 open되면
void process_exit (void)- 실행중인 파일을 닫도록 현재 프로세스를 수정한다.
시스템 콜 구현하기
void halt (void);power_off()을 호출해서 핀토스를 종료한다.- 발생 가능한 데드락 상황 등과 관련된 정보를 잃을 수 있기 때문에 거의 사용하지 않는 편이 좋다.
void exit (int status);- 현재 실행중인 사용자 프로그램을 종료하고 커널에
status를 리턴한다. - 프로세스의 부모가
wait중이라면(하단 참고), 그것을 리턴한다. - 관례 상
status는 성공하면0을, 오류면 0이 아닌 값을 가진다.
- 현재 실행중인 사용자 프로그램을 종료하고 커널에
pid_t fork (const char *thread_name);THREAD_NAME이라는 이름으로 현재 프로세스의 복사본을 생성한다.- 피호출자가 저장한 레지스터인
%RBX,%RSP,%RBP,%R12-%R15는 반드시 그 값을 복사해야 하지만, 나머지는 그럴 필요는 없다. - 자식 프로세스의 pid를 리턴해야만 하며, 그렇지 않은 경우 유효한 pid를 가지면 안 된다.
- 자식 프로세스에서는 리턴값이
0이어야 한다. - 자식 프로세스는 파일 디스크립터와 VM 공간 등을 포함해 복제된 리소스를 가져야 한다.
- 부모 프로세스는 자식 프로세스가 성공적으로 복제된 것을 알기 전까지는 fork로부터 리턴하면 안 된다. 즉, 자식 프로세스가 자원을 복제하는 데 실패했다면, 부모의
fork()call은TID_ERROR를 리턴해야 한다. - 템플릿은 대응하는 페이지 테이블 구조를 포함한 전체 유저 메모리 공간을 복사하는데
pml4_for_each()를 사용하지만,pte_for_each_func의 빠진 부분을 채워야 한다.
int exec (const char *cmd_line);- 현재 프로세스를
cmd_line에 (어떤 인수와 함께) 주어진 이름의 실행 프로그램으로 바꾼다. - 성공하면 리턴하지 않지만, 어떤 이유로 프로그램을 로드하거나 실행할 수 없다면, 그 프로그램은
-1을 리턴하면서 종료된다. - 이 함수는
exec를 호출한 스레드의 이름을 바꾸지 않는다. 파일 디스크립터는exec가 호출된 이후에도 열려있다는 것을 기억하자.
- 현재 프로세스를
int wait (pid_t pid);- 자식 프로세스
pid를 기다리고, 자식의 종료 상태를 회수한다.pid가 아직 살아 있다면, 종료될 때까지 기다린다. pid가 exit하면서 넘긴 상태값을 리턴한다.pid가exit()을 호출하지 않았지만 (exception 등에 의해) 커널에 의해 종료되었다면,wait(pid)는-1을 리턴한다.- 부모 프로세스가
wait을 호출했을 때 이미 종료된 자식 프로세스를 기다릴 수도 있다. 하지만 커널은 여전히 부모가 자식의 종료 상태를 회수하거나 자식이 커널에 의해 종료되었음을 알도록 해야 한다. - 다음과 같은 조건들이 참인 경우
wait은 즉시 실패하고-1을 리턴해야 한다:pid가 호출한 프로세스의 직계 자식direct child을 참조하지 않는 경우:
호출한 프로세스가fork를 성공적으로 호출했을 때pid를 리턴값으로 받을 때,pid는 호출한 프로세스의 직계 자식이다. A가 자식 B를 만들고, B가 자식 C를 만들면, A는 B가 죽어도 C를wait할 수 없다는 점에서 자식은 상속되지 않는다. A에 의한wait(C)는 실패해야 한다. 비슷하게, 고아 프로세스들은 그들의 부모 프로세스가 (새로운 부모를 할당하기 전에) 종료한다면 새로운 부모를 가질 수 없다.wait을 호출하는 프로세스가 이미pid에wait을 호출한 경우:
즉, 프로세스는 어떤 주어진 자식에 대해 최대 한 번만wait할 수 있다.
- 프로세스들은 아무 수의 자식을 만들고, 아무 순서로
wait하고, 심지어 그 자식들 중 일부 또는 전체를 기다리지 않고 종료될 수 있다. 구현할 때 가능한 모든 경우를 고려해야 한다. - 부모가 기다리든지 말든지, 자식이 부모 전/후로 종료되던지 간에,
struct thread를 포함한 프로세스의 모든 자원들은 free되어야 한다. - 최초의 프로세가 exit할 때까지는 핀토스가 종료되서는 안 된다. 제공된 핀토스는
main()에서process_wait()를 호출해서 이 조건을 지키려고 한다.process_wait()를 먼저 구현하고,process_wait()에 따라wait시스템 콜을 구현해 보자.
- 자식 프로세스
bool create (const char *file, unsigned initial_size);initial_size크기의file이라는 파일을 생성한다. 성공하면 true, 실패하면 false를 반환한다.
bool remove (const char *file);file이라는 파일을 삭제한다. 성공하면 true, 실패하면 false를 반환한다.- 파일이 열려 있는지 아닌지와는 상관없이 파일이 삭제되고, 열린 파일을 삭제하는 것이 그것을 닫지는 않는다.
int open (const char *file);file이라는 파일을 연다. 성공하면 0 이상의 파일 디스크립터(fd) 값, 실패하면 -1을 리턴한다.- 파일 디스크립터 값 중 0은 표준 입력(
STDIN_FILENO)에, 1은 표준 출력(STDOUT_FILENO)에 예약되어 있기 때문에 여기서는 0과 1을 리턴하지 않는다. - 각 프로세스는 파일 디스크립터의 독립적인 세트를 가지고 있고, 파일 디스크립터는 자식 프로세스로 상속된다.
- 단일 프로세스로거나 다른 프로세스로거나에 관계 없이 단일 파일이 2번 이상 열리면 각각 새로운 파일 디스크립터를 리턴한다.
- 단일 파일에 대한 다른 파일 디스크립터들은 각각 개별적인
close호출에 의해 닫히고 서로 파일 위치를 공유하지 않는다. - 추가 작업을 하려면, 0부터 시작하는 정수를 리턴하는 Linux 방식을 따라야 한다.
int filesize (int fd);fd로 열린 파일의 사이즈를 바이트 단위로 리턴한다.
int read (int fd, void *buffer, unsigned size);- 열린 파일
fd로부터 size 바이트 만큼의 데이터를 buffer에 저장한다. 실제로 read한 바이트 수(EOFend-of-file에서는 0)를 리턴하거나, EOF가 아닌 다른 이유로 파일을 읽기 실패한 경우 -1을 리턴한다. fd0은input_getc()를 사용해 키보드로부터 읽는다.
- 열린 파일
int write (int fd, const void *buffer, unsigned size);- buffer로부터 size 바이트 만큼의 데이터를 열린 파일
fd에 저장한다. 실제로 write한 바이트 수를 리턴한다 (실제로는 write되지 않는 바이트도 있어서 size보다 더 적을 수 있다). - 이전의 EOF에 쓰는 경우 일반적으로 파일을 확장하지만, 이 기능은 기본적인 파일 시스템에 구현되어 있지 않았다. 따라서 여기서는 EOF 전 까지 최대한 많이 쓰고 실제로 쓴 수를 리턴, 전혀 못 썼으면 0을 리턴한다.
fd1은 콘솔에 쓰는 것이다. 콘솔에 쓸 코드는size가 수백 바이트를 넘지 않는 한 버퍼 전체를 한 번의putbuf()호출로 써야 한다(큰 버퍼를 나누는 것이 합리적이다).
- buffer로부터 size 바이트 만큼의 데이터를 열린 파일
void seek (int fd, unsigned position);- 열린 파일
fd에서position(파일 시작부터 떨어진 정도; 즉,position=0은 파일의 시작)의 다음 바이트부터 읽거나 쓰도록 한다. - 현재 파일의 EOF를 지나 탐색하는 것은 오류가 아니다.
read는 0바이트(EOF)를 얻는다.write는 파일을 0으로 채워서 확장한다: 그러나 여기서는 핀토스의 파일이 고정된 길이를 가지기 때문에, EOF 이후의 write는 에러다.- 이러한 semantics(의미론)는 파일 시스템에 구현되어 있기 때문에 시스템 콜 구현에서 다룰 필요는 없다.
- 열린 파일
unsigned tell (int fd);- 열린 파일
fd에서 읽거나 쓸 다음 바이트의 위치(파일 시작부터 떨어진 정도)를 리턴한다.
- 열린 파일
void close (int fd);- 파일 디스크립터
fd를 닫는다. - 프로세스를 exit하거나 종료하는 것은 (마치 각 파일에 대해 이 함수를 호출한 것 같이) 묵시적으로 모든 열린 파일을 닫는다.
- 파일 디스크립터
어떤 수의 사용자 프로세스가 한 번에 시스템 콜을 만들 수 있도록 동기화해야 한다. 특히, 한 번에 여러 스레드들에서 filesys 디렉토리에서 제공된 파일 시스템 코드를 호출하는 것은 위험하다. 시스템 콜 구현은 파일 시스템 코드를 critical section으로 다뤄야 한다. process_exec() 또한 파일에 접근함을 잊지 말자. 현재로서는 filesys 디렉토리의 코드를 건들지 않는 편이 좋다.
'Krafton Jungle > 5. PintOS' 카테고리의 다른 글
| [PintOS 2주차] Day 7 (2) | 2025.05.21 |
|---|---|
| [PintOS 2주차] Day 6 (0) | 2025.05.20 |
| [PintOS 2주차] Day 3 (0) | 2025.05.17 |
| [PintOS 2주차] Day 2 (0) | 2025.05.16 |
| [PintOS 2주차] Day 1 (0) | 2025.05.15 |