본문 바로가기
개발자 도전기/[OS] pintOS

pintOS | Project 2 | 시스템콜 구현 (halt, exit, exec, create, remove, open, filesize)

by 답수 2021. 10. 11.
728x90
SMALL

pintOS | 명령어 실행 기능 구현(Command Line Parsing)

 

(수정 중) pintOS | 명령어 실행 기능 구현(Command Line Parsing)

pintos project2 설명 보기 프로젝트1에서 실행했던 alarm clock, scheduling 등 모든 코드들은 전부 커널의 일부였고, 테스트 코드 또한 커널에 직접 컴파일했었다. 이제부터는 유저 프로그램을 실행하여

dapsu-startup.tistory.com

 

 

이전 포스팅에서 pintos의 커맨드라인에서 인자를 파싱하고 패싱할 수 있도록 구현을 했다. 하지만 아직 시스템콜 핸들러가 구현되어 있지 않기 때문에 시스템콜이 호출되지 않고, 응용프로그램을 실행할 수 없다.

 

그렇다면 시스템 콜이 뭘까? 운영체제에는 사용자 모드(User mode)와 커널 모드(Kernel mode)가 있다. 사용자 모드에서는 실행되는 코드가 제한된다. e.g. 프로세스가 사용자 모드에서 실행 중이면 I/O 요청을 할 수 없다. 요청을 하게 되면 프로세스가 인터럽트를 발생시킨다. 

 

이와 반대로 커널모드는 컴퓨터의 모든 자원에 대한 접근 권한을 가진다. 이 모드에서 실행되는 코드는 모든 특수한 명령어를 포함하여 원하는 모든 작업을 수행할 수 있다.

 

그렇다면 사용자 프로세스가 디스크 읽기와 같은 명령어를 실행하려면 어떻게 해야 할까? 이 때 필요한 것이 시스템 콜(System call)이다.

 

시스템 콜은 운영체제가 제공하는 서비스에 대한 프로그래밍 인터페이스로, 사용자 모드 프로그램이 커널 기능을 사용할 수 있도록 한다. 시스템 콜은 커널 모드에서 실행되며, 실행이 끝나면 다시 사용자 모드로 복귀된다.

 

pintos에서 시스템 콜 호출 과정

 

현재 pintos에서 시스템콜 핸들러는 구현되어 있지 않고 우리가 직접 구현해야 한다.

 

시스템콜 핸들러는 ../userprog/syscall.c 에서 확인할 수 있다. 현재 코드를 한 번 보자.

/* The main system call interface */
void
syscall_handler (struct intr_frame *f UNUSED) {
	// TODO: Your implementation goes here.
	printf ("system call!\n");
	thread_exit ();
}

... 텅 비어 있다 ㅋ.. 하나씩 시스템 콜을 구현해보도록 하즈아

 

우선 ../include/lib/syscall-nr.h 에 들어가 보면 프로젝트2에서 구현해야 할 함수들이 열거되어 있다. 이 순서에 맞게 시스콜 핸들러에 구현하자.

enum {
	/* Projects 2 and later. */
	SYS_HALT,                   /* Halt the operating system. */
	SYS_EXIT,                   /* Terminate this process. */
	SYS_FORK,                   /* Clone current process. */
	SYS_EXEC,                   /* Switch current process. */
	SYS_WAIT,                   /* Wait for a child process to die. */
	SYS_CREATE,                 /* Create a file. */
	SYS_REMOVE,                 /* Delete a file. */
	SYS_OPEN,                   /* Open a file. */
	SYS_FILESIZE,               /* Obtain a file's size. */
	SYS_READ,                   /* Read from a file. */
	SYS_WRITE,                  /* Write to a file. */
	SYS_SEEK,                   /* Change position in a file. */
	SYS_TELL,                   /* Report current position in a file. */
	SYS_CLOSE,                  /* Close a file. */
	...
};

 

 

다음과 같이 구현. 참고로 이 때 기존에 있던 printf()와 thread_exit()함수를 제거하지 않으면 'Test output failed to match any acceptable form'이라는 에러문구가 뜨면서 테스트케이스에 통과되지 않는다. 

/* The main system call interface */
void syscall_handler (struct intr_frame *f) {
	// TODO: Your implementation goes here.
	// printf ("system call!\n");

	char *fn_copy;

	/*
	 x86-64 규약은 함수가 리턴하는 값을 rax 레지스터에 배치하는 것
	 값을 반환하는 시스템 콜은 intr_frame 구조체의 rax 멤버 수정으로 가능
	 */
	switch (f->R.rax) {		// rax is the system call number
		case SYS_HALT:
			halt();			// pintos를 종료시키는 시스템 콜
			break;
		case SYS_EXIT:
			exit(f->R.rdi);	// 현재 프로세스를 종료시키는 시스템 콜
			break;
		case SYS_FORK:
			f->R.rax = fork(f->R.rdi, f);
			break;
		case SYS_EXEC:
			if (exec(f->R.rdi) == -1) {
				exit(-1);
			}
			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;
		default:
			exit(-1);
			break;
	}
	// thread_exit ();
}

핸드러 밑에 시스템콜 함수들을 만들자. 만들면서 어려운 함수들은 아직 구현 못 했다. 당장 구현한 것들부터 정리할 것임

 

 

| halt()

halt 함수는 pintos를 종료시키는 시스템 콜 함수이다. power_off()를 통해 간단하게 구현 가능하다.

// pintos 종료 시스템 콜
void halt(void) {
	power_off();
}

 

 

| exit()

 exit는 프로세스를 종료시키는 시스템 콜이다. 

// 프로세스 종료 시스템 콜
void exit(int status) {
	struct thread *cur = thread_current();
    cur->exit_status = status;		// 프로그램이 정상적으로 종료되었는지 확인(정상적 종료 시 0)

	printf("%s: exit(%d)\n", thread_name(), status); 	// 종료 시 Process Termination Message 출력
	thread_exit();		// 스레드 종료
}

여기서 thread.h 파일의 thread 구조체에

    int exit_status;

를 추가하여 준다.

 

 

| exec()

현재 프로세스를 cmd_line에서 지정된 인수를 전달하여 이름이 지정된 실행 파일로 변경

// 현재 프로세스를 cmd_line에서 지정된 인수를 전달하여 이름이 지정된 실행 파일로 변경
int exec(char *file_name) {
	check_address(file_name);

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

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

	NOT_REACHED();
	return 0;
}

 

함수 내의 처음 코드를 보면 check_address()라는 함수가 있다. 이것은 포인터가 기리키는 주소가 사용자 영역인지 확인하는 함수다. 역시 따로 구현해줘야 한다.

// 주소값이 유저 영역(0x8048000~0xc0000000)에서 사용하는 주소값인지 확인하는 함수
void check_address(const uint64_t *addr)	
{
	struct thread *cur = thread_current();
	if (addr == NULL || !(is_user_vaddr(addr)) || pml4_get_page(cur->pml4, addr) == NULL) {
		exit(-1);
	}
}

 

 

| create()

파일 이름과 파일 사이즈를 인자 값으로 받아 파일을 생성하는 함수. filesys_create 함수는 파일 이름과 파일 사이즈를 인자값으로 받아 파일을 생성하는 함수다. 이 함수는 filesys/filesys.c 디렉토리에 있다.

// 파일 생성하는 시스템 콜
// 성공일 경우 true, 실패일 경우 false 리턴
bool create(const char *file, unsigned initial_size) {		// file: 생성할 파일의 이름 및 경로 정보, initial_size: 생성할 파일의 크기
	check_address(file);
	return filesys_create(file, initial_size);
}

 

 

| remove()

파일을 삭제하는 시스템 콜로, file 인자는 제거할 파일의 이름 및 경로 정보이다. 성공일 시 true, 실패 시 false 리턴

filesys_remove() 함수 역시 filesys/filesys.c 파일에 있다.

// 파일 삭제하는 시스템 콜
// 성공일 경우 true, 실패일 경우 false 리턴
bool remove(const char *file) {			// file: 제거할 파일의 이름 및 경로 정보
	check_address(file);
	return filesys_remove(file);
}

 

 

| open()

open은 파일을 열 때 사용하는 시스템 콜이다. 성공 시 fd를 생성하고 반환, 실패 시 -1을 반환한다.

// fd값 리턴, 실패 시 -1 리턴
int open(const char *file) {
	check_address(file);
	struct file *open_file = filesys_open(file);

	if (open_file == NULL) {
		return -1;
	}

	int fd = add_file_to_fdt(open_file);

	// fd table 가득 찼다면
	if (fd == -1) {
		file_close(open_file);
	}
	return fd;
}

 

여기에서 fd를 설정하기 위해 다음과 같은 함수를 작성한다.

// 현재 프로세스의 fd테이블에 파일 추가
int add_file_to_fdt(struct file *file) {
	struct thread *cur = thread_current();
	struct file **fdt = cur->fd_table;

	// fd의 위치가 제한 범위를 넘지 않고, fdtable의 인덱스 위치와 일치한다면
	while (cur->fd_idx < FDCOUNT_LIMIT && fdt[cur->fd_idx]) {
		cur->fd_idx++;
	}

	// fdt이 가득 찼다면
	if (cur->fd_idx >= FDCOUNT_LIMIT)
		return -1;

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

thread.h 에서 thread 구조체 안에 

    struct file **fd_table;         // thread_create에서 할당

    int fd_idx;                     // fd테이블에 open spot의 인덱스

두 변수를 추가하여 준다.

 

그리고 thread.h 파일 내에

// for system call

#define FDT_PAGES 3                       // pages to allocate for file descriptor tables (thread_create, process_exit)

#define FDCOUNT_LIMIT FDT_PAGES *(1 << 9) // Limit fdIdx

이것들을 선언해주자

 

 

| filesize()

파일의 크기를 알려주는 시스템 콜

// fd인자를 받아 파일 크기 리턴
int filesize(int fd) {
	struct file *open_file = find_file_by_fd(fd);
	if (open_file == NULL) {
		return -1;
	}
	return file_length(open_file);

 

find_file_by_fd() 함수는 아래처럼 구현하자.

// fd로 파일 찾는 함수
static struct file *find_file_by_fd(int fd) {
	struct thread *cur = thread_current();

	if (fd < 0 || fd >= FDCOUNT_LIMIT) {
		return NULL;
	}
	return cur->fd_table[fd];
}

 

 

나머지 함수들은 다음 포스팅에...

728x90
LIST

댓글