[SW사관학교 정글]69일차 TIL- systemcall 구현 2(open, filesize, read, write, close, seek)

김승덕·2022년 11월 26일
0

SW사관학교 정글 5기

목록 보기
109/150
post-thumbnail

Systamcall 정리 - 2

pintos에서 우리가 구현해야할 system call

  1. void halt(void)
  2. void exit (int status);
  3. bool create (const char *file, unsigned initial_size);
  4. bool remove (const char *file);
  5. int open (const char *file);
  6. int filesize (int fd);
  7. int read (int fd, void *buffer, unsigned size);
  8. int write (int fd, const void *buffer, unsigned size);
  9. void close (int fd);
  10. void seek (int fd, unsigned position);
  11. unsigned tell (int fd);
  12. int exec (const char *cmd_line);
  13. int wait (pid_t pid);
  14. tid_t fork(const char *thread_name, struct intr_frame *f);

저번에 했던 것들 이후에 open부터 시작해보겠다.

그 전에 open을 구현할때 필요한 check_address를 설명하고 시작해보자면…

void check_address(void *addr)

void check_address(void *addr)
{
	struct thread *t = thread_current();
	if (is_kernel_vaddr(addr) || addr == NULL || pml4_get_page(t->pml4, addr) == NULL)
		// if (addr == NULL || is_kernel_vaddr(addr) || pml4e_walk(t->pml4, addr, false) == NULL)
		exit(-1);
}

check_address 함수는 주소의 유효성을 체크라는 함수이다.

포인터가 가리키는 주소가 유저 영역의 주소인지 확인하고, 잘못된 접근일 경우 프로세스를 종료한다.

int open (const char *file);

int open(const char *file)
{
	check_address(file);

	struct thread *t = thread_current();

	struct file **file_descriptor_table = t->file_descriptor_table;
	int fd = t->fd_number;
	/* 파일을 open */ 
	struct file *f = filesys_open(file);
	// 해당 파일이 존재하지 않으면 -1 리턴
	if (f == NULL)
		return -1;

	while (t->file_descriptor_table[fd] != NULL && fd < FDT_COUNT_LIMIT)
		fd++;

	if (fd >= FDT_COUNT_LIMIT)
		file_close(f);

	t->fd_number = fd;
	file_descriptor_table[fd] = f;
	// 파일 디스크립터 리턴
	return fd;
}

Opens the file called file. Returns a nonnegative integer handle called a "file descriptor" (fd), or -1 if the file could not be opened. File descriptors numbered 0 and 1 are reserved for the console: fd 0 (STDIN_FILENO) is standard input, fd 1 (STDOUT_FILENO) is standard output. The open system call will never return either of these file descriptors, which are valid as system call arguments only as explicitly described below. Each process has an independent set of file descriptors. File descriptors are inherited by child processes. When a single file is opened more than once, whether by a single process or different processes, each open returns a new file descriptor. Different file descriptors for a single file are closed independently in separate calls to close and they do not share a file position. You should follow the linux scheme, which returns integer starting from zero, to do the extra.

open() 시스템 콜은 파일을 열때 사용하는 시스템 콜이다.

성공시 fd를 생성하고 반환한다. 그리고 실패시 -1 을 반환한다. 인자 file은 파일의 이름 및 경로 정보이다.

파일 디스크립터의 내용을 참조하면 좋다. file_descriptor_table 은 파일 디스크립터들이 모여있는 테이블이다. 여기 안에 파일 디스크립터의 인덱스가 있다. filesys_open 함수를 통해 파일을 연다.

그리고 프로세스가 실행중인 파일을 open하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해주어야한다. 그래서 while문을 통해 순회를 하며 fd 의 숫자를 1씩 증가시켜준다.

실패시 -1을 반환해주고, fdLIMIT보다 커지면 파일을 종료시켜주는 등의 예외 처리를 해준다.

최종적으로 fd 를 반환해준다

int filesize (int fd);

int filesize(int fd)
{
	struct file *f = get_file(fd);

	if (f == NULL)
		return -1;

	int result = file_length(f);

	return result;

}

Returns the size, in bytes, of the file open as fd.

filesize 시스템 콜은 파일의 크기를 알려주는 시스템 콜이다. 성공시 파일의 크기를 반환하고, 실패시 -1을 반환한다.

여기서도 file.c에 있는 file_length() 함수를 사용한다면 쉽게 구현할 수 있다.

struct file *fd_to_file(int fd)

struct file *fd_to_file(int fd)
{
	if (fd < 0 || fd >= FDT_COUNT_LIMIT || fd == NULL)
		return NULL;

	struct thread *t = thread_current();
	struct file *f;
	f = t->file_descriptor_table[fd];

	return f;
}

파일디스크립터를 이용하여 파일 객체를 검색하는 함수이다.

여러 시스템콜에서 쓰일것이어서 미리 함수를 만들어놓았다.

lock을 위한 초기 설정

@/include/userprog/syscall.h

struct lock filesys_lock;
@/userprog/syscall.c

void syscall_init (void) {
	(...)
    
	lock_init(&filesys_lock);
}

int read (int fd, void *buffer, unsigned size);

int read(int fd, void *buffer, unsigned size)
{
	struct file *f = fd_to_file(fd);

	check_address(buffer);
	check_address(buffer + size - 1);

	unsigned char *buf = buffer;
	char key;

	if (f == NULL)
		return -1;

	int i = 0;

	if (size == 0)
		return 0;

	if (fd == 0)
	{
		while (i <= size)
		{
			key = input_getc();
			buf = key;
			buffer++;

			if (key == "\0")
				break;

			i++;
		}
	}

	else if (fd == 1)
		return -1;

	lock_acquire(&filesys_lock);
	i = file_read(f, buffer, size);
	lock_release(&filesys_lock);

	return i;
}

Reads size bytes from the file open as fd into buffer. Returns the number of bytes actually read (0 at end of file), or -1 if the file could not be read (due to a condition other than end of file). fd 0 reads from the keyboard using input_getc().

read는 열린 파일의 데이터를 읽는 시스템 콜이다. 성공 시 읽은 바이트 수를 반환하고, 실패시 -1을 반환한다. fd 값이 0일때 키보드의 데이터를 읽어 버퍼에 저장한다.

인자는 다음과 같다.

buffer : 읽은 데이터를 저장할 버퍼의 주소 값

size : 읽을 데이터 크기

공식 문서의 설명처럼 fd가 0일때는 input_getc() 함수를 사용해야한다. 이 함수는 키보드에 입력을 하나씩 버퍼에 저장한 수 버퍼의 저장한 크기를 리턴해주는 함수이다. 여기서 중요한것은 하나씩이라는 점이다. 그래서 반복문을 통해 모든 입력을 저장하도록 해준다. fd가 1일때도 return -1 을 통해 종료를 시켜주어야한다.

그리고 파일이 동시에 접근이 일어날수있으므로 lock을 이용해야한다. lock으로 묶고 file_read함수를 이용해서 시스템 콜을 완성한다.

int write(int fd, const void *buffer, unsigned size)

int write(int fd, const void *buffer, unsigned size)
{
	struct file *f = fd_to_file(fd);

	if (fd == 0)
		return 0;

	check_address(buffer);

	if (fd == 1)
	{
		putbuf(buffer, size);
		return size;
	}

	if (f == NULL)
		return 0;

	if (size == 0)
		return 0;

	lock_acquire(&filesys_lock);
	int i = file_write(f, buffer, size);
	lock_release(&filesys_lock);

	return i;
}

Writes size bytes from buffer to the open file fd. Returns the number of bytes actually written, which may be less than size if some bytes could not be written. Writing past end-of-file would normally extend the file, but file growth is not implemented by the basic file system. The expected behavior is to write as many bytes as possible up to end-of-file and return the actual number written, or 0 if no bytes could be written at all. fd 1 writes to the console. Your code to write to the console should write all of buffer in one call to putbuf(), at least as long as size is not bigger than a few hundred bytes (It is reasonable to break up larger buffers). Otherwise, lines of text output by different processes may end up interleaved on the console, confusing both human readers and our grading scripts.

write 시스템콜은 열린 파일의 데이터를 기록하는 시스템 콜이다.

성공시 기록한 데이터의 바이트 수를 반환하고, 실패시 -1을 반환한다. fd값이 1이면 버터에 저장된 데이터를 화면에 출력한다. 이는 putbuf() 함수를 사용한다.

read와 마찬가지로 동시 접근이 일어날수 있으므로 writelock을 이용해서 보호를 해준다. 그리고 file_write() 함수를 이용해서 버퍼에 저장된 데이터를 크기만큼 파일에 기록후 기록한 바이트 수를 반환한다.

void close (int fd);

void close(int fd)
{
	struct thread *t = thread_current();
	struct file *f = fd_to_file(fd);
	if (f == NULL)
		return;

	lock_acquire(&filesys_lock);
	file_close(f);
	lock_release(&filesys_lock);

	t->file_descriptor_table[fd] = NULL;
}

Closes file descriptor fd. Exiting or terminating a process implicitly closes all its open file descriptors, as if by calling this function for each one.

close 시스템콜은 열린 파일을 닫는 시스템콜이다. file_close 함수를 이용해서 닫는다. close도 마찬가지로 lock을 통해서 동시접근으로 인한 오류를 막는다.

void seek(int fd, unsigned position)

void seek(int fd, unsigned position)
{
	if (fd < 2)
		return;
	struct file *f = fd_to_file(fd);
	if (f == NULL)
		return;

	file_seek(f, position);
}

seek 시스템 콜은 열린 파일의 위치를 이동하는 시스템콜이다. file_seek함수를 이용하면 쉽게 구현이 가능하다. 그 외의 예외 처리도 해주어야한다.

unsigned tell(int fd)

unsigned tell(int fd)
{
	if (fd < 2)
		return;
	struct file *f = fd_to_file(fd);
	if (f == NULL)
		return;
	check_address(f);

	return file_tell(f);
}

tell 시스텀 콜은 열린 파일의 위치를 알려주는 시스템 콜이다. file_tell 함수를 이용하여 쉽게 구현가능하다.

profile
오히려 좋아 😎

0개의 댓글