본문 바로가기
OS/Pintos P.J_2

[Project 2_User Programs]_System Calls

by Success T.H.I.E.F 2022. 1. 9.

userprog 테스트 위한 아웃풋 list (디버그 할 때 사용하면 좋아요)

더보기
더보기

pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-empty:create-empty -- -q   -f run create-empty
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-null:create-null -- -q   -f run create-null
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-bad-ptr:create-bad-ptr -- -q   -f run create-bad-ptr 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-long:create-long -- -q   -f run create-long 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-exists:create-exists -- -q   -f run create-exists 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/create-bound:create-bound -- -q   -f run create-bound 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-normal:open-normal -p../../tests/userprog/sample.txt:sample.txt -- -q   -f run open-normal 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-missing:open-missing -- -q   -f run open-missing 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-boundary:open-boundary -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run open-boundary 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-empty:open-empty -- -q   -f run open-empty 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-null:open-null -- -q   -f run open-null 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-bad-ptr:open-bad-ptr -- -q   -f run open-bad-ptr 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/open-twice:open-twice -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run open-twice 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/close-normal:close-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run close-normal 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/close-twice:close-twice -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run close-twice 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/close-bad-fd:close-bad-fd -- -q   -f run close-bad-fd 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-normal:read-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run read-normal 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-bad-ptr:read-bad-ptr -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run read-bad-ptr 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-boundary:read-boundary -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run read-boundary 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-zero:read-zero -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run read-zero 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-stdout:read-stdout -- -q   -f run read-stdout 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/read-bad-fd:read-bad-fd -- -q   -f run read-bad-fd 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-normal:write-normal -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run write-normal 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-bad-ptr:write-bad-ptr -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run write-bad-ptr 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-boundary:write-boundary -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run write-boundary 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-zero:write-zero -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run write-zero 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-stdin:write-stdin -- -q   -f run write-stdin 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/write-bad-fd:write-bad-fd -- -q   -f run write-bad-fd 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-once:fork-once -- -q   -f run fork-once 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-multiple:fork-multiple -- -q   -f run fork-multiple
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-recursive:fork-recursive -- -q   -f run fork-recursive 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-read:fork-read -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run fork-read 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-close:fork-close -p ../../tests/userprog/sample.txt:sample.txt -- -q   -f run fork-close
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/fork-boundary:fork-boundary -- -q   -f run fork-boundary 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-once:exec-once -p tests/userprog/child-simple:child-simple -- -q   -f run exec-once 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-arg:exec-arg -p tests/userprog/child-args:child-args -- -q   -f run exec-arg 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-boundary:exec-boundary -p tests/userprog/child-simple:child-simple -- -q   -f run exec-boundary 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-missing:exec-missing -- -q   -f run exec-missing 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-bad-ptr:exec-bad-ptr -- -q   -f run exec-bad-ptr 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/exec-read:exec-read -p ../../tests/userprog/sample.txt:sample.txt -p tests/userprog/child-read:child-read -- -q   -f run exec-read 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-simple:wait-simple -p tests/userprog/child-simple:child-simple -- -q   -f run wait-simple 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-twice:wait-twice -p tests/userprog/child-simple:child-simple -- -q   -f run wait-twice 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-killed:wait-killed -p tests/userprog/child-bad:child-bad -- -q   -f run wait-killed 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/wait-bad-pid:wait-bad-pid -- -q   -f run wait-bad-pid 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/multi-recurse:multi-recurse -- -q   -f run 'multi-recurse 15' 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/multi-child-fd:multi-child-fd -p ../../tests/userprog/sample.txt:sample.txt -p tests/userprog/child-close:child-close -- -q   -f run multi-child-fd 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/rox-simple:rox-simple -- -q   -f run rox-simple 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/rox-child:rox-child -p tests/userprog/child-rox:child-rox -- -q   -f run rox-child
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/rox-multichild:rox-multichild -p tests/userprog/child-rox:child-rox -- -q   -f run rox-multichild
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-read:bad-read -- -q   -f run bad-read 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-write:bad-write -- -q   -f run bad-write 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-read2:bad-read2 -- -q   -f run bad-read2 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-write2:bad-write2 -- -q   -f run bad-write2 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-jump:bad-jump -- -q   -f run bad-jump 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/userprog/bad-jump2:bad-jump2 -- -q   -f run bad-jump2 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/lg-create:lg-create -- -q   -f run lg-create 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/lg-full:lg-full -- -q   -f run lg-full 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/lg-random:lg-random -- -q   -f run lg-random 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/lg-seq-block:lg-seq-block -- -q   -f run lg-seq-block 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/lg-seq-random:lg-seq-random -- -q   -f run lg-seq-random 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/sm-create:sm-create -- -q   -f run sm-create
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/sm-full:sm-full -- -q   -f run sm-full 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/sm-random:sm-random -- -q   -f run sm-random 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/sm-seq-block:sm-seq-block -- -q   -f run sm-seq-block 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/sm-seq-random:sm-seq-random -- -q   -f run sm-seq-random 
pintos -v -k -T 300 -m 20   --fs-disk=10 -p tests/filesys/base/syn-read:syn-read -p tests/filesys/base/child-syn-read:child-syn-read -- -q   -f run syn-read 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/syn-remove:syn-remove -- -q   -f run syn-remove 
pintos -v -k -T 60 -m 20   --fs-disk=10 -p tests/filesys/base/syn-write:syn-write -p tests/filesys/base/child-syn-wrt:child-syn-wrt -- -q   -f run syn-write 
pintos -v -k -T 600 -m 20 -m 20   --fs-disk=10 -p tests/userprog/no-vm/multi-oom:multi-oom -- -q   -f run multi-oom 

System Call

시스템 콜은 OS 커널이 제공하는 서비스에 대해 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다.

즉 사용자 모드 프로그램이 커널 기능을 사용할 수 있도록 하며 커널모드에서 실행되고 처리 후 사용자 모드로 복귀된다.

사용자 프로그램은 종종 file(read), 새로운 프로스세스의 생성(fork), 새 프로그램 load(execve), 현재 프로세스의 종료(exit) 등 여러 서비스를 커널에 요청해야 한다. 커널 서비스 같은 제어가 필요한 액세스를 허용하기 위해 프로세스는 사용자 프로그램이 서비스를 요청할 때 실행할 수 있는 특별한 syscall 명령을 제공한다.
-csapp 3rd 8.1.2-

 

시스템 콜 호출과정(리눅스) 출처 : 한양대 핀토스 자료

과제 목표

pintos는 시스템 콜 핸들러가 구현되어 있지 않아 시스템 콜이 호출될 수 없으므로 응용 프로그램이 정상적으로 동작하지 않는다. 따라서 시스템 콜 핸들러 및 시스템 콜(halt, exit, create, remove 등)구현한다.

 

수정 영역 : /userprog/syscall.*

시스템 콜 호출 과정(pintos) 출처 : 한양대 핀토스 자료

 

구현

1. 시스템 콜 핸들러에서 시스템 콜 번호에 해당하는 시스템 콜을 호출(시스템 콜 번호는 include/lib/syscall-nr.h 에 정의되어있다)

2. 시스템 콜 핸들러에서 유저 스택 포인터(esp) 주소와 인자가 가리키는 주소가 유저 영역인지 확인하여 유저 영역을 벗어난 주소 참조 시 페이지 폴트 발생

3. 유저 스택에 있는 인자들을 커널에 저장

4. 시스템 콜 함수의 리턴 값은 interrupt frame의 rax에 저장

 

void halt (void) NO_RETURN;
void exit (int status) NO_RETURN;
pid_t fork (const char *thread_name);
int exec (const char *file);
int wait (pid_t);
bool create (const char *file, unsigned initial_size);
bool remove (const char *file);
int open (const char *file);
int filesize (int fd);
int read (int fd, void *buffer, unsigned length);
int write (int fd, const void *buffer, unsigned length);
void seek (int fd, unsigned position);
unsigned tell (int fd);
void close (int fd);

 

우리가 구현해야 할 syscall들이다 이 시스템 콜 번호는 % rax에 저장되어있다. 

시스템 콜 핸들러로 전달되는 인터럽트 프레임 f->R.rax가 받아오는 시스템 콜의 번호에 맞게 작업되도록 switch문에 이들을 넣어준다.

 

User Memory Access

시스템콜 구현에 앞서 우리는 시스템콜의 인수로 제공되는 포인터들에서 데이터를 읽을 때 이 인자들이 사용자 영역에 있는 포인터인지, 커널 메모리에 대한 포인터는 아닌지 등 잘못된 포인터에 대한 필터링이 필요하다. 만약 잘못된 포인터라면 사용자 프로세스를 종료해 이런 경우를 처리해야 한다.

따라서 시스템 콜로 들어오는 인자에 대해 필터링해주는 함수 check_address함수를 먼저 만든다.

void check_address(const uint64_t *uaddr){
	struct thread *cur = thread_current();
	if (uaddr == NULL || !(is_user_vaddr(uaddr)) || pml4_get_page(cur->pml4, uaddr) == NULL){
		exit(-1);
	}
}

 시스템콜로 받은 인자의 주소가 NULL 이거나 유저 어드레스 영역에 있지 않거나, 할당되지 않은 주소인 경우 프로세스를 종료한다.


halt

/* userprog/syscall.c */

void halt(void)
{
	power_off();
}

halt는 power_off()(src/include/threads/init.h) 함수를 호출하여 Pintos를 종료시키는 역할이다.

power_off( ) 함수를 사용하기 위해서 threads/init.h를 추가해 준다.

 

halt는 드물게 사용되는데 그 이유는 데드락 상황 등에서 정보를 잃을 수 있기 때문이다.

 

power off()를 실행하면 아래와 같이 outw를 실행하는데 이는  QEMU 가상화 소프트웨어에서 poweroff를 하는 방법이다. 이렇게 pintos는 종료된다.


파일을 생성하고 삭제하는 시스템 콜을 만들어 보자. 

pintos가이드를 읽어보면 create함수는 file이라 불리는 새로운 file을 initial_size대로 생성하고 생성 성공 시 Return 값으로 True를 전달 아니면 False를 전달하는 함수를 만들라고 한다.

새로 생성된 파일의  open은 open이라는 다른 시스템 콜에서 할 것이라고 한다.

 

pintos에서는 기본적으로 filesys/filesys.c에 파일 시스템 생성, 제거하는 filesys_create(), filesys_remove함수가 있다. 위 함수를 이용하기 위해 filesys/filesys.h를 추가한다.

 단순히 systemcall로 들어오는 인자를 위 함수로 전달만 하면 되는 것 아닐까?라고 생각했지만 우리는 시스템 콜로 받아오는 file에 대한 주소 값이 올바른 주소인지 먼저 확인해 준 후 넘겨준다.

create & remove

bool create(const char *file, unsigned initial_size){
	bool return_code;
	check_address(file);

	lock_acquire(&file_rw_lock);
	return_code = filesys_create(file, initial_size);
	lock_release(&file_rw_lock);
	return return_code;
}

bool remove(const char *file){
	bool return_code;
	check_address(file);

	lock_acquire(&file_rw_lock);
	return_code = filesys_remove(file);
	lock_release(&file_rw_lock);
	return return_code;
}

/*syscall.h*/
struct lock file_rw_lock; //추가

파일을 생성하는 동안 중복하여 생성/삭제 시스템 콜이 올 수 있으니 이를 방지해 주기 위해  file_rw_lock구조체를 통해 생성을 보장해준다.

 

 

open

파일을 열어주는 open syscall을 만들어보자

filesys_open을 통해 현재 생성된 파일을 받아오고 이를 오픈된 파일들의 목록을 저장하는 FDT(file descriptor table)에 올려주어야 한다. FDT는 프로세스마다 하나씩 가지고 있는 구조체인데 pintos tread구조체에는 선언되어있지 않아 추가해주고 tread 생성 시 초기화를 해주어야 한다. 또한 FDT를 인덱싱 하기 위한 fdidx도 선언한다.

 

thread 구조체에 fdtable과 테이블을 인덱싱 하기 위한 fdidx를 선언하고 초기화하자.

/*include/threads/thread.h*/
struct thread {
...
struct file **fdTable;
int fdIdx;
...

}
/*threads/thread.c*/
thread_create (const char *name, int priority,
		thread_func *function, void *aux) {
    struct thread *t;
	tid_t tid;
...

	t->fdTable = palloc_get_multiple(PAL_ZERO, FDT_PAGES);
	if (t->fdTable == NULL)
		return TID_ERROR;
	t->fdIdx = 2;	// 0: stdin, 1: stdout
	t->fdTable[0] = 1; // dummy values to distinguish fd 0 and 1 from NULL
	t->fdTable[1] = 2;
	t->stdin_count = 1;
	t->stdout_count = 1;
...

	return tid;
}

이렇게 만든 후 open syscall함수를 작성 한결과는 다음과 같다.

int open(const char *file){
	check_address(file);
	struct file *fileobj = filesys_open(file);

	if (fileobj == NULL)
		return -1;

	int fd = add_file_to_fdt(fileobj);

	if (fd == -1)
		file_close(fileobj);

	return fd;
}

int add_file_to_fdt(struct file *file)
{
	struct thread *cur = thread_current();
	struct file **fdt = cur->fdTable; // file descriptor table

	while (cur->fdIdx < FDCOUNT_LIMIT && fdt[cur->fdIdx]){
		cur->fdIdx++;
	}

	if (cur->fdIdx >= FDCOUNT_LIMIT)
		return -1;

	fdt[cur->fdIdx] = file;
	return cur->fdIdx;
}

/*include/threads/thread.h*/
#define FDT_PAGES 3	// pages to allocate for file descriptor tables
#define FDCOUNT_LIMIT FDT_PAGES *(1 << 9)

create, remove와 마찬가지로 우선 파일의 주소에 대한 유효성 체크부터 하고 진행한다.

그 후 filesys_open을 호출해 불러온 file을  fdt에 추가한 후 리턴해준다.

페이지 하나당 4KB로 파일 구조체 주소가 8바이트이기 때문에 나눠주면 (1<<9) 개가 들어갈 수 있는 공간이다.

FDCOUNT_LIMIT은 3*(1<<9)로 해둔다.


filesize

파일 사이즈를 확인하는 시스템 콜로 파일을 찾아 그 파일의 length를 리턴한다. 

int filesize(int fd){
	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL)
		return -1;
	return file_length(fileobj);
}

static struct file *find_file_by_fd(int fd){
	struct thread *cur = thread_current();

	if (fd < 0 || fd >= FDCOUNT_LIMIT)
		return NULL;

	return cur->fdTable[fd]; // automatically returns NULL if empty
}

 


read/wirte

파일을 읽고 쓰는 시스템 콜인 read, write를 구현해본다

read는 인자로 fd, buffer, size를 받아온다. fd의 buffer주소에서 size만큼 읽어오는 것 같은데 우선 buffer의 유효성을 체크하고 FDT에서 fd를 불러와 읽어보는 함수를 만들자.

pintos에서 fd가 0인 경우 input_getc()를 이용해 받아오도록 조건을 주었으므로 이 조건을 만족하여 코드를 구성한다.

file_rw_lock을 이용해 파일을 읽는 동안 다른 접근으로부터 보호한다.

int read(int fd, void *buffer, unsigned size){
	check_address(buffer);
	int read_count;
	struct thread *cur = thread_current();

	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL)
		return -1;

	if (fileobj == STDIN){
		int i;
		unsigned char *buf = buffer;
		for (i = 0; i < size; i++)
		{
			char c = input_getc();
			*buf++ = c;
			if (c == '\0')
				break;
		}
		read_count = i;
	} 
	else if (fileobj == STDOUT){
		read_count = -1;
	}
	else{
		lock_acquire(&file_rw_lock);
		read_count = file_read(fileobj, buffer, size);
		lock_release(&file_rw_lock);
	}
	return read_count;
}

void syscall_init(void){
	...
	// 파일 동시에 읽고 쓰는 것 막기 위한 Lock
	lock_init(&file_rw_lock);
}

 

write 함수는 read와 동일한 인자를 할당받는다. 마찬가지로 buffer의 유효성 체크를 먼저 해주고 시작하자.

write함수는 return 값으로 실제로 쓰인 바이트의 수를 리턴하므로 주어진 size보다 작을 수 있다. 

int write(int fd, const void *buffer, unsigned size){
	check_address(buffer);
	int read_count;

	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL)
		return 0;

	struct thread *cur = thread_current();

	if (fileobj == STDOUT){
		putbuf(buffer, size);
		read_count = size;

	}
	else if (fileobj == STDIN){
		read_count = 0;
	}
	else{
		lock_acquire(&file_rw_lock);
		read_count = file_write(fileobj, buffer, size);
		lock_release(&file_rw_lock);
	}

	return read_count;
}

seek/tell

seek 시스템 콜은 다음 읽거나 쓸 위치를 positon으로 바꾸는 시스템 콜이다.

tell은 다음 읽거나 쓸 파일의 위치를 반환한다. 

 

void seek(int fd, unsigned position){	
	if (fd < 2)
		return;
	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL) 
		return;
	fileobj->pos = position;
}

unsigned tell(int fd){
	if (fd < 2)
		return;
	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL) 
		return;
	return file_tell(fileobj);
}

close

파일을 닫는 시스템 콜로 FDT에서 해당 fd를  NULL로 만들어 준다.

void close(int fd){
	struct file *fileobj = find_file_by_fd(fd);
	if (fileobj == NULL)
		return;

	struct thread *cur = thread_current();

	if (fd == 0 || fileobj == STDIN){
		cur->stdin_count--;
	}
	else if (fd == 1 || fileobj == STDOUT){
		cur->stdout_count--;
	}

	remove_file_from_fdt(fd);
	if (fd < 2 || fileobj <= 2)
		return;


void remove_file_from_fdt(int fd){
	struct thread *cur = thread_current();

	if (fd < 0 || fd >= FDCOUNT_LIMIT)
		return;

	cur->fdTable[fd] = NULL;
}

exec

현재 프로세스를 cmd_line으로부터 받은 file_name의 실행 파일로 변경하는 시스템 콜이다.

 

int exec(char *file_name){
	check_address(file_name);

	int siz = strlen(file_name) + 1;
	char *fn_copy = palloc_get_page(PAL_ZERO);
	if (fn_copy == NULL)
		exit(-1);
	strlcpy(fn_copy, file_name, siz);

	if (process_exec(fn_copy) == -1)
		return -1;

	NOT_REACHED();

	return 0;
}

exit

void exit(int status)
{
	struct thread *cur = thread_current();
	cur->exit_status = status; //프로그램이 정상적으로 종료되었는지 확인(정상 종료시 0)
	printf("%s: exit(%d)\n", cur->name, status);
	thread_exit();
}

현재 사용자 프로그램을 종료시키는 시스템 콜로 프로세스를 thread구조체에 int형인 exit_status에 인자로 받은 status를 저장하고 그 후 thread_exit()을 호출-> process_exit함수를 호출해 완전히 종료시킨다. 

 

fork

fork는 이름이 thread_name인 현재 프로세스의 복제 프로세스를 만드는 syscall이다. 

callee-saved registers.% RBX, % RSP, % RBP, and % R12 - % R15는 복제 안 해도 된다.

<callee-saved registers>

더보기
더보기

우리가 코딩을 통해 프로그램을 만들면 실행 시에 main()을 찾아 시작점으로 정해진다.
main() 안에서 어떠한 함수 func()를 호출하면 main()은 caller가 되고 func()는 calle가 된다.
그러면 여기서 calle가 실행될 때 연산을 수행하기 위해서는 어떠한 값들이 register에 저장될 것이고 그 register가 calle-saved-register이다.
즉, calle-saved-register는 콜리가 저장하는 레지스터로 콜리가 사용하기 전에 반드시 백업해야 하는 레지스터이다.
콜리 입장에서는 콜러가 백업을 필요로 하는 레지스터가 무엇인지 모르기 때문에 반드시 백업을 해주어야 한다.
그러면 왜 fork 시에 calle-saved-register를 clone 안 해도 될까?라는 의문을 가질 수 있다.
내가 생각하기에는 현재 실행 중인 스레드의 흐름에 따라 어떠한 함수(caller)가 호출한 함수(calle)에 대한 값들이 저장된 것이므로 fork 된 프로세스가 이 값들을 사용할 일이 없기 때문이라고 생각한다.

출처 : https://straw961030.tistory.com/267

fork는 부모와 자식 프로세스 입장에서 리턴 받는 것이 다르다.

child process는 0을 리턴 받고  parent process는 fork 실패 시 TID_ERROR, 성공 시 child process의 pid를 리턴 받는다. 

 

 

/*userprog/syscall.c*/

tid_t fork(const char *thread_name, struct intr_frame *f){
	return process_fork(thread_name, f);
}

/*userprog/process.c*/
tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED) {
	/* Clone current thread to new thread.*/
	struct thread *cur = thread_current();
	memcpy(&cur->parent_if, if_, sizeof(struct intr_frame)); // 부모 프로세스 memcpy 

	tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, cur); // 전달받은 thread_name으로 __do_fork() 진행 
	
	if (tid == TID_ERROR)
		return TID_ERROR;

	struct thread *child = get_child_with_pid(tid); // get_child_with_pid 함수 실행
	sema_down(&child->fork_sema); // child load 대기
	if (child -> exit_status == -1)
		return TID_ERROR;

	return tid;
}

/*threads/thread.c*/
thread_create (const char *name, int priority,
		thread_func *function, void *aux) {
	struct thread *t;
	tid_t tid;

...
	struct thread *cur = thread_current();
	list_push_back(&cur->child_list, &t->child_elem);
...
	return tid;
}

/*userprog/syscall.c*/
static void __do_fork (void *aux) {
	struct intr_frame if_;
	struct thread *parent = (struct thread *) aux;
	struct thread *current = thread_current ();
	/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
	
	/*--------------- PROJECT2: User Programs ----------------*/
	struct intr_frame *parent_if;
	parent_if = &parent->parent_if;
	bool succ = true;

	/* 1. Read the cpu context to local stack. */
	memcpy (&if_, parent_if, sizeof (struct intr_frame));
	if_.R.rax = 0; 

	/* 2. Duplicate PT */
	current->pml4 = pml4_create();
	if (current->pml4 == NULL)
		goto error;

	process_activate (current); 

...


	if (parent->fdIdx == FDCOUNT_LIMIT)
		goto error;

	/*--------------- PROJECT2: User Programs ----------------*/
	for (int i = 0; i < FDCOUNT_LIMIT; i++) {
		struct file *file = parent->fdTable[i];
		if (file == NULL)
			continue;

		// If 'file' is already duplicated in child, don't duplicate again but share it
		bool found = false;
		if (!found) {
			struct file *new_file;
			if (file > 2)
				new_file = file_duplicate(file);
			else
				new_file = file;

			current->fdTable[i] = new_file;
		}
	}


struct thread *get_child_with_pid(int pid)
{
	struct thread *cur = thread_current();
	struct list *child_list = &cur->child_list;

	for (struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e))
	{
		struct thread *t = list_entry(e, struct thread, child_elem);
		if (t->tid == pid)
			return t;
	}	
	return NULL;
}

우선 시스템콜 헨들러에서 fork()를 호출하면 process_fork()를 내부에서호출한다.

process_fork에서는 우선 memcpy() 로 부모 프로세스의 내용을 복사하고 전달받은 인수를 가지고 새로운 thread를 생성하고 현재 스레드의 child_list에 저장후 tid에 저장한다. 

부모 프로세스는 리턴받은 tid로 자식프로세스를 찾고 자식의 fork_sema를 sema down하여 자식 프로세스의 정상적인 로드를 보장한다.

 

자식 프로세스는 __do_fork를 통해 부모 프로세스의 정보를 복사후 fork_sema 를 up한다. 

 

wait

wait() 함수는 부모가 자식을 기다릴떄 사용되며 인자로는 자식 프로세스 id가 들어온다.

process_wait()함수를 호출한다. 

 

프로세스 웨잇함수에서 부모는 자식프로세스가 끝나는것을 보장하기 위해  wait_sema를 down한다. 그후 자식 프로세스가 종료되면 (process_exit) wait_sema는 up free sema는 down하여 완전히 자식 프로세스가 리스트에서 삭제되는 과정까지 보장한다. 그후 삭제까지 완료되면 free sema를 up하고 기다림을 마친다. 

int wait (tid_t pid)
{
	process_wait(pid);
};

/*userprog/process.c*/
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. */

	struct thread *cur = thread_current();
	struct thread *child = get_child_with_pid(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->free_sema);

	return exit_status;
}

void process_exit (void) {
	struct thread *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. */

	/*--------------- PROJECT2: User Programs ----------------*/
	for (int i = 0; i < FDCOUNT_LIMIT; i++){
		close(i);
	}

	palloc_free_multiple(curr->fdTable, FDT_PAGES); 

	file_close(curr->running);

	process_cleanup ();

	sema_up(&curr->wait_sema);
	
	sema_down(&curr->free_sema);
}

'OS > Pintos P.J_2' 카테고리의 다른 글

[Project 2_User Programs]_Argument Passing(인수전달)  (0) 2022.01.04
[Project 2_User Programs]_Intro  (0) 2022.01.01