Krafton Jungle/5. PintOS

[PintOS 2주차] Day 4-5

munsik22 2025. 5. 19. 10:58

😪 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 위로) 로의 포인터
  • 커널은 포인터의 무효성을 탐지해 커널이나 다른 실행 중인 프로세스에 해를 끼치지 않고 프로세스를 종료해야 한다.
  • 어떻게 탐지해?
    1. 유저에서 제공된 포인터의 유효성 확인하기
      • 유저 메모리 접근을 다루는 가장 단순한 방법
      • pagedir.cvaddr.h의 함수 사용
    2. KERN_BASE 아래의 유저 포인터만 체크하기
      • 유효하지 않은 포인터는 page fault를 발생시킨다. page_fault() 코드를 수정해서 다룰 수 있다.
      • MMU의 이점을 살려 1번 방법보다 빠르고, 실제 커널에서 주로 사용된다 (하지만 더 어렵다).
  • 위의 두 방법 중 어떤 것을 사용하든지 간에 자원의 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()도 수정해야 한다: rax0xffffffff로 설정하고 이전 값을 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_framerax를 수정함으로써 그렇게 할 수 있다.


File Manipulation

더보기

아래 내용은 ppt(32비트) 기준으로 작성되어 지금 기준으로는 내용이 다를 수 있다.

🔹 UNIX의 파일 디스크립터

  • 파일 디스크립터를 사용해 파일에 접근한다.

🔹 파일 디스크립터 테이블

  • FDT 구현하기
    • 각 프로세스는 각각의 FDT를 가진다 (최대 크기: 64 엔트리)
    • FDT는 struct file로의 포인터의 배열이다.
    • FD는 FDT의 인덱스 번호로 순차적으로 할당된다.
    • FD 01은 각각 stdin, stdout에 할당된다.
    • open()fd를 리턴한다.
    • close()fd번 인덱스의 FD 엔트리를 0으로 설정한다.
  • struct thread에 FDT를 정의하고, 커널 메모리 영역에 FDT를 할당해 연관된 포인터를 struct thread에 추가한다.
  • 스레드가 생성되면
    • FDT를 할당한다.
    • FDT로의 포인터를 초기화한다.
    • fd0, fd1stdin, stdout을 위해 예약한다.
  • 스레드가 종료되면
    • 모든 파일을 닫는다.
    • FDT를 할당 해제한다.
  • 파일에 대한 race condition을 피하기 위해 전역 락을 사용한다.
    • syscall.h에 전역 락을 정의한다: struct lock filesys_lock
    • syscall_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을 리턴한다.
    • fd0이면, 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에 쓴다.
    • 실제로 쓰여진 바이트 수를 리턴한다.
    • fd1이면, 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()를 호출한다.
  • static bool load (const char *file_name, struct intr_frame *if_)
    • 프로그램이 open되면 file_deny_write()를 호출한다.
    • struct thread에 실행중인 파일 구조체를 추가한다.
  • void process_exit (void)
    • 실행중인 파일을 닫도록 현재 프로세스를 수정한다.

시스템 콜 구현하기

더보기
  1. void halt (void);
    • power_off()을 호출해서 핀토스를 종료한다.
    • 발생 가능한 데드락 상황 등과 관련된 정보를 잃을 수 있기 때문에 거의 사용하지 않는 편이 좋다.
  2. void exit (int status);
    • 현재 실행중인 사용자 프로그램을 종료하고 커널에 status를 리턴한다.
    • 프로세스의 부모가 wait 중이라면(하단 참고), 그것을 리턴한다.
    • 관례 상 status는 성공하면 0을, 오류면 0이 아닌 값을 가진다.
  3. 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의 빠진 부분을 채워야 한다.
  4. int exec (const char *cmd_line);
    • 현재 프로세스를 cmd_line에 (어떤 인수와 함께) 주어진 이름의 실행 프로그램으로 바꾼다.
    • 성공하면 리턴하지 않지만, 어떤 이유로 프로그램을 로드하거나 실행할 수 없다면, 그 프로그램은 -1을 리턴하면서 종료된다.
    • 이 함수는 exec를 호출한 스레드의 이름을 바꾸지 않는다. 파일 디스크립터는 exec가 호출된 이후에도 열려있다는 것을 기억하자.
  5. int wait (pid_t pid);
    • 자식 프로세스 pid를 기다리고, 자식의 종료 상태를 회수한다. pid가 아직 살아 있다면, 종료될 때까지 기다린다.
    • pid가 exit하면서 넘긴 상태값을 리턴한다.
    • pidexit()을 호출하지 않았지만 (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을 호출하는 프로세스가 이미 pidwait을 호출한 경우:
        즉, 프로세스는 어떤 주어진 자식에 대해 최대 한 번만 wait할 수 있다.
    • 프로세스들은 아무 수의 자식을 만들고, 아무 순서로 wait하고, 심지어 그 자식들 중 일부 또는 전체를 기다리지 않고 종료될 수 있다. 구현할 때 가능한 모든 경우를 고려해야 한다.
    • 부모가 기다리든지 말든지, 자식이 부모 전/후로 종료되던지 간에, struct thread를 포함한 프로세스의 모든 자원들은 free되어야 한다.
    • 최초의 프로세가 exit할 때까지는 핀토스가 종료되서는 안 된다. 제공된 핀토스는 main()에서 process_wait()를 호출해서 이 조건을 지키려고 한다. process_wait()를 먼저 구현하고, process_wait()에 따라 wait 시스템 콜을 구현해 보자.
  6. bool create (const char *file, unsigned initial_size);
    • initial_size 크기의 file이라는 파일을 생성한다. 성공하면 true, 실패하면 false를 반환한다.
  7. bool remove (const char *file);
    • file이라는 파일을 삭제한다. 성공하면 true, 실패하면 false를 반환한다.
    • 파일이 열려 있는지 아닌지와는 상관없이 파일이 삭제되고, 열린 파일을 삭제하는 것이 그것을 닫지는 않는다.
  8. int open (const char *file);
    • file이라는 파일을 연다. 성공하면 0 이상의 파일 디스크립터(fd) 값, 실패하면 -1을 리턴한다.
    • 파일 디스크립터 값 중 0은 표준 입력(STDIN_FILENO)에, 1은 표준 출력(STDOUT_FILENO)에 예약되어 있기 때문에 여기서는 0과 1을 리턴하지 않는다.
    • 각 프로세스는 파일 디스크립터의 독립적인 세트를 가지고 있고, 파일 디스크립터는 자식 프로세스로 상속된다.
    • 단일 프로세스로거나 다른 프로세스로거나에 관계 없이 단일 파일이 2번 이상 열리면 각각 새로운 파일 디스크립터를 리턴한다.
    • 단일 파일에 대한 다른 파일 디스크립터들은 각각 개별적인 close 호출에 의해 닫히고 서로 파일 위치를 공유하지 않는다.
    • 추가 작업을 하려면, 0부터 시작하는 정수를 리턴하는 Linux 방식을 따라야 한다.
  9. int filesize (int fd);
    • fd로 열린 파일의 사이즈를 바이트 단위로 리턴한다.
  10. int read (int fd, void *buffer, unsigned size);
    • 열린 파일 fd로부터 size 바이트 만큼의 데이터를 buffer에 저장한다. 실제로 read한 바이트 수(EOFend-of-file에서는 0)를 리턴하거나, EOF가 아닌 다른 이유로 파일을 읽기 실패한 경우 -1을 리턴한다.
    • fd 0은 input_getc()를 사용해 키보드로부터 읽는다.
  11. int write (int fd, const void *buffer, unsigned size);
    • buffer로부터 size 바이트 만큼의 데이터를 열린 파일 fd에 저장한다. 실제로 write한 바이트 수를 리턴한다 (실제로는 write되지 않는 바이트도 있어서 size보다 더 적을 수 있다).
    • 이전의 EOF에 쓰는 경우 일반적으로 파일을 확장하지만, 이 기능은 기본적인 파일 시스템에 구현되어 있지 않았다. 따라서 여기서는 EOF 전 까지 최대한 많이 쓰고 실제로 쓴 수를 리턴, 전혀 못 썼으면 0을 리턴한다.
    • fd 1은 콘솔에 쓰는 것이다. 콘솔에 쓸 코드는 size가 수백 바이트를 넘지 않는 한 버퍼 전체를 한 번의 putbuf() 호출로 써야 한다(큰 버퍼를 나누는 것이 합리적이다).
  12. void seek (int fd, unsigned position);
    • 열린 파일 fd에서 position(파일 시작부터 떨어진 정도; 즉, position = 0은 파일의 시작)의 다음 바이트부터 읽거나 쓰도록 한다.
    • 현재 파일의 EOF를 지나 탐색하는 것은 오류가 아니다.
      • read는 0바이트(EOF)를 얻는다.
      • write는 파일을 0으로 채워서 확장한다: 그러나 여기서는 핀토스의 파일이 고정된 길이를 가지기 때문에, EOF 이후의 write는 에러다.
      • 이러한 semantics(의미론)는 파일 시스템에 구현되어 있기 때문에 시스템 콜 구현에서 다룰 필요는 없다.
  13. unsigned tell (int fd);
    • 열린 파일 fd에서 읽거나 쓸 다음 바이트의 위치(파일 시작부터 떨어진 정도)를 리턴한다.
  14. 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