지난 포스트에서는 전체적인 코드의 흐름을 파악하는 과정을 봤습니다. 아래 포스트에 이어서 다음 기능을 구현해 보겠습니다.
https://yunchan97.tistory.com/71
구현 순서는 Gitbook 순서를 바탕으로 진행하는데 중간중간 필요한 부분이 있다면 구현하는 방식으로 진행합니다.
구현
1️⃣ int write(int fd, const void *buffer, unsigned size)
- fd 1(STDOUT)인 경우 콘솔에 출력합니다. 이때, 콘솔에 쓰는 코드는 적어도 크기가 수백 바이트보다 크지 않은 한 putbuf() 호출 한 번으로 버퍼를 모두 씁니다.
- fd 0(STDIN)인 경우, 표준 입력에 대한 파일 디스크립터 값은 해당 함수와 관련이 없으므로 -1을 반환해 줍니다.
- 예상되는 동작은 파일 끝부분까지 가능한 한 많은 바이트를 쓰고 실제로 쓰인 바이트 수를 반환하거나, 바이트가 전혀 쓰이지 않은 경우 0을 반환하는 것입니다.
int write(int fd, const void *buffer, unsigned size) {
check_address(buffer);
struct file *file = process_get_file(fd);
/* 실행된 후 쓰여진 바이트 수를 저장하는 변수 */
int bytes_written = 0;
lock_acquire(&filesys_lock);
if (fd == STDOUT_FILENO) {
/* 쓰기가 표준 출력인 경우, 버퍼의 내용을 화면에 출력하고 쓰여진 바이트 수를 저장 */
putbuf(buffer, size);
bytes_written = size;
} else if (fd == STDIN_FILENO) {
/* 쓰기가 표준 입력인 경우, 파일 시스템 잠금 해제 후 -1 반환 */
lock_release(&filesys_lock);
return -1;
} else if (fd >= 2) {
if (file == NULL) {
/* 쓰기가 파일에 대한 것인데 파일이 없는 경우, 파일 시스템 잠금 해제 후 -1 반환 */
lock_release(&filesys_lock);
return -1;
}
/* 파일에 버퍼의 내용을 쓰고 쓰여진 바이트 수를 저장 */
bytes_written = file_write(file, buffer, size);
}
lock_release(&filesys_lock);
return bytes_written;
}
2️⃣ void putbuf (const char *buffer, size_t n)
- acquire_console() 함수를 호출하여 콘솔 잠금을 획득합니다. 이렇게 함으로써 다른 프로세스가 동시에 쓰기를 수행하지 않도록 합니다.
- putchar_have_lock() 함수는 콘솔 출력을 담당하며, 잠금을 획득한 상태로 호출됩니다.
- release_console() 함수를 호출하여 콘솔 잠금을 해제합니다. 이로써 다른 프로세스가 콘솔을 사용할 수 있게 됩니다.
void putbuf(const char *buffer, size_t n) {
acquire_console(); // 콘솔 잠금을 획득합니다. 다른 프로세스가 동시에 쓰기를 수행하지 않도록 합니다.
while (n-- > 0) {
putchar_have_lock(*buffer++); // 버퍼의 내용을 한 글자씩 콘솔에 출력합니다.
}
release_console(); // 콘솔 잠금을 해제합니다.
}
3️⃣ int open(const char *file)
- file_name 포인터 주소를 검증한다.
- filesys_open를 호출해서 파일을 open 한다.한다.
- 파일을 열 수 없는 경우 (file == NULL인 경우)에는 -1을 리턴한다.
- process_add_file 함수를 호출해서 파일 디스크립터 테이블 (fdt)에 파일을 추가하고 fd를 반환받는다.
- fdt에 추가할 수 없는 경우 (fd == -1인 경우)에는 파일을 닫고 -1을 리턴한다.
- 오픈한 파일의 fd를 반환한다.
운영체제는 프로세스마다 파일 디스크립터를 따로 관리한다. 핀토스에서는 프로세스가 곧 스레드이기에, 스레드 구조체에 파일 디스크립터를 관리하는 테이블을 하나 생성한다. 이것이 바로 파일 디스크립터 테이블 (FDT)이다. 스레드마다 갖고 있는 이 배열이 어떤 파일이 열려있는지를 관리한다. 이때 각 리스트 내 요소는 struct file을 가리키는 포인터에 해당하며, 이 스레드 구조체가 읽고 쓰는 실제 파일의 정보를 추적하는 데 쓰인다. 아래에 파일 디스크립터 테이블 구조체를 멤버로 하나 넣어주고 해당 인덱스를 가리키는 변수 fdidx를 생성해 준다.
struct thread{
( . . . 중략 . . .)
int exit_status; // exit() 또는 wait() 구현에 사용되는 변수
struct file **fdt; // 파일 디스크립터 테이블
int fdidx; // 파일 디스크립터 인덱스
}
int open (const char *file) {
check_address(file); // 주소 유효성 검사
struct file *file = filesys_open(file); // 파일 시스템에서 파일 열기
if (file == NULL) {
return -1; // 파일 열기 실패 시 -1 반환
}
int fd = process_add_file(file); // 파일을 프로세스에 추가하고 파일 디스크립터 얻기
if (fd == -1) {
file_close(file); // 파일 디스크립터 추가 실패 시 열었던 파일 닫기
}
return fd; // 파일 디스크립터 반환
}
4️⃣ int process_add_file(struct file *f)
현재 실행 중인 스레드의 파일 디스크립터 테이블에서 비어있는 위치를 찾아 주어진 파일 객체를 추가하는 함수입니다.
- 현재 실행 중인 스레드를 가리키는 포인터 t를 얻습니다.
- 현재 스레드의 파일 디스크립터 테이블을 가리키는 포인터 fdt를 얻습니다.
- 현재 스레드의 파일 디스크립터 인덱스 fd를 얻습니다.
- 파일 디스크립터 테이블에서 비어있는 위치를 찾기 위해 fd를 증가시키면서 검사합니다.
- 파일 디스크립터 테이블이 가득 차거나 비어있는 위치를 찾지 못한 경우, -1을 반환합니다.
- 비어있는 위치를 찾은 경우, 다음에 할당될 파일 디스크립터 인덱스를 갱신하고 파일 객체를 파일 디스크립터 테이블에 추가합니다.
- 할당된 파일 디스크립터를 반환합니다.
thread_create()에서 새로운 스레드를 생성할 때 fdt를 위한 하나의 페이지를 할당해 준다. 이때, 페이지의 크기는 (1<<12) 인데, 파일 구조체 주소 크기가 8바이트(1<<3)이므로 이를 분리하면 (1<<9)만큼의 공간을 할당받는 것과 같다. 이 크기를 매크로로 설정해준다.(FDT_LIMIT) 이를 위해 파일 디스크립터 테이블을 담는 하나의 페이지 공간을 할당해준다.
// 파일 객체에 대한 파일 디스크립터를 생성하는 함수
int process_add_file(struct file *file) {
struct thread *t = thread_current(); // 현재 실행 중인 스레드 구조체
struct file **fdt = t->fdt; // 현재 스레드의 파일 디스크립터 테이블
int fd = t->fdidx; // 현재 스레드의 파일 디스크립터 인덱스
// 파일 디스크립터 테이블에서 비어있는 위치를 찾아 파일을 추가한다.
while (t->fdt[fd] != NULL && fd < FDCOUNT_LIMIT) {
fd++;
}
if (fd >= FDCOUNT_LIMIT) {
// 파일 디스크립터 테이블이 가득찬 경우, -1을 반환한다.
return -1;
}
t->fdidx = fd; // 다음 파일에 할당될 파일 디스크립터 인덱스 갱신
fdt[fd] = file; // 파일 객체를 파일 디스크립터 테이블에 추가
return fd; // 할당된 파일 디스크립터 반환
}
5️⃣ int filesize(int fd)
- process_get_file를 호출해 fd에 해당하는 파일 객체를 찾는다.
- 찾은 파일 객체를 인자로 file_length 함수를 호출한다.
- fd로 열린 파일의 크기(바이트)를 반환합니다.
// 파일 디스크립터를 사용하여 파일의 크기를 가져오는 함수
int filesize(int fd) {
// 주어진 파일 디스크립터로부터 파일 객체를 가져옴
struct file *file = process_get_file(fd);
// 파일 객체가 NULL인 경우, 즉 파일을 찾을 수 없는 경우 -1을 반환
if (file == NULL) {
return -1;
}
// 파일 객체의 크기를 가져와서 반환
file_length(file);
}
주어진 파일 디스크립터를 사용해서 스레드의 파일 디스크립터 테이블에서 객체를 찾아서 반환하는 함수인 process_get_file 구현해야 합니다. 파일 디스크립터가 유효한 범위인지 확인하고 현재 실행 중인 스레드의 파일 디스크립터 테이블을 가져온 후 해당 파일 객체를 반환합니다.
// 주어진 파일 디스크립터를 사용하여 스레드의 파일 테이블에서 파일 객체를 찾아 반환하는 함수
struct file *process_get_file(int fd) {
// 파일 디스크립터가 유효한 범위를 벗어나면 NULL을 반환
if (fd < 0 || fd >= FDCOUNT_LIMIT) {
return NULL;
}
// 현재 실행 중인 스레드의 정보를 가져옴
struct thread *t = thread_current();
// 현재 스레드의 파일 테이블을 가져옴
struct file **fdt = t->fdt;
// 파일 테이블에서 주어진 파일 디스크립터에 해당하는 파일 객체를 가져옴
struct file *file = fdt[fd];
// 찾은 파일 객체를 반환
return file;
}
6️⃣ int read(int fd, void *buffer, unsigned size)
- 파일을 읽어 들일 수 없는 케이스에서는 -1을 반환하라고 되어 있다. 대표적으로 fd값이 1일 경우(=STDOUT) 출력을 나타내기에 -1을 반환합니다.
- fd 0은 입력_getc()를 사용하여 키보드에서 읽습니다.
- buffer가 유효한 메모리 주소인지 확인합니다.
- 주어진 파일 디스크립터(fd)를 사용하여 스레드의 파일 테이블에서 파일 객체를 가져옵니다. process_get_file 함수를 호출하여 파일 객체를 얻습니다.
- 파일 객체가 NULL인 경우, 즉 파일을 찾을 수 없는 경우 -1을 반환합니다.
- 파일 디스크립터가 표준 입력(stdin)인 경우에 키보드로부터 입력을 받아 buffer에 저장합니다. 입력된 바이트 수를 bytes_written에 저장합니다. 만약 '\0' (NULL 문자)가 입력되면 루프를 종료합니다.
- 파일 디스크립터가 표준 출력(stdout)인 경우에 읽기 작업을 지원하지 않으므로 -1을 반환합니다.
- 일 디스크립터가 일반 파일인 경우에 파일을 읽어와서 buffer에 저장하고, 읽은 바이트 수를 bytes_written에 저장합니다. 파일 접근을 위해 filesys_lock을 획득한 후 파일 읽기 작업을 수행하고, 작업이 끝나면 filesys_lock을 해제합니다.
- 마지막으로 읽은 바이트 수인 bytes_written을 반환합니다.
// 주어진 파일 디스크립터를 사용하여 파일로부터 데이터를 읽어오는 함수
int read(int fd, void *buffer, unsigned size) {
// 주어진 buffer의 주소 유효성을 확인
check_address(buffer);
check_address(buffer + size - 1);
// buffer를 unsigned char 포인터로 캐스팅하여 사용하기 위한 변수
unsigned char *buf = buffer;
int bytes_written;
// 주어진 파일 디스크립터로부터 파일 객체를 가져옴
struct file *file = process_get_file(fd);
// 파일 객체가 NULL인 경우, 즉 파일을 찾을 수 없는 경우 -1을 반환
if (file == NULL) {
return -1;
}
if (fd == STDIN_FILENO) {
// 파일 디스크립터가 표준 입력인 경우, 키보드 입력을 받아 buffer에 저장
char key;
for (int bytes_written = 0; bytes_written < size; bytes_written++) {
key = input_getc();
*buf++ = key;
if (key == '\0') {
break;
}
}
} else if (fd == STDOUT_FILENO) {
// 파일 디스크립터가 표준 출력인 경우, 읽기 작업을 지원하지 않으므로 -1을 반환
return -1;
} else {
// 일반 파일인 경우, 파일을 읽어와서 buffer에 저장하고 읽은 바이트 수를 반환
lock_acquire(&filesys_lock);
bytes_written = file_read(file, buffer, size);
lock_release(&filesys_lock);
}
return bytes_written;
}
7️⃣ void seek(int fd, unsigned position)
- 파일 디스크립터(fd)가 2보다 작은 경우, 즉 표준 입력(stdin) 또는 표준 출력(stdout)인 경우 함수를 종료합니다. 표준 입력과 출력은 위치를 이동할 수 없기 때문에 이 조건을 사용하여 처리합니다.
- 주어진 파일 디스크립터(fd)를 사용하여 스레드의 파일 테이블에서 파일 객체를 가져옵니다. process_get_file 함수를 호출하여 파일 객체를 얻습니다.
- check_address함수를 통해 파일 객체가 유효한 메모리 주소인지 확인합니다.
- 파일 객체가 NULL인 경우, 즉 파일을 찾을 수 없는 경우 함수를 종료합니다.
- 파일 객체의 위치를 주어진 position으로 이동합니다. 이는 파일 내에서 지정된 위치로 커서를 이동시키는 작업입니다.
// 주어진 파일 디스크립터를 사용하여 파일 내에서 지정된 위치로 이동하는 함수
void seek(int fd, unsigned position) {
// 파일 디스크립터가 표준 입력 또는 표준 출력인 경우 함수 종료
if (fd < 2) {
return;
}
// 주어진 파일 디스크립터로부터 파일 객체를 가져옴
struct file *file = process_get_file(fd);
// 파일 객체의 주소 유효성을 확인
check_address(file);
// 파일 객체가 NULL인 경우 함수 종료
if (file == NULL) {
return;
}
// 파일 객체의 위치를 주어진 position으로 이동
file_seek(file, position);
}
8️⃣ unsigned tell(int fd)
전반적으로 tell()는 seek()와 비슷한 개념입니다. 파일을 읽으려면 어디서부터 읽어야 하는지에 대한 위치 pos를 파일 내 구조체 멤버에 정보로 저장해서 fd 값을 인자로 넣어주면 해당 파일의 pos를 반환하는 함수가 tell()입니다.
- 파일 디스크립터(fd)가 2보다 작은 경우, 즉 표준 입력(stdin) 또는 표준 출력(stdout)인 경우 함수를 종료합니다. 표준 입력과 출력은 위치 정보를 가지지 않기 때문에 이 조건을 사용하여 처리합니다.
- 파일 디스크럽터가 유효하지 않은 경우 함수를 종료합니다.
- 어진 파일 디스크립터(fd)를 사용하여 스레드의 파일 테이블에서 파일 객체를 가져옵니다. process_get_file 함수를 호출하여 파일 객체를 얻습니다.
- 파일 객체의 현재 커서 위치를 반환합니다. file_tell 함수를 호출하여 현재 커서 위치를 얻습니다.
// 주어진 파일 디스크립터를 사용하여 파일 내 현재 커서의 위치를 반환하는 함수
unsigned tell(int fd) {
// 파일 디스크립터가 표준 입력 또는 표준 출력인 경우 함수 종료
if (fd < 2) {
return;
}
// 파일 디스크립터가 유효하지 않은 경우 함수 종료
if (fd < 0 || fd >= FDCOUNT_LIMIT) {
return;
}
// 주어진 파일 디스크립터로부터 파일 객체를 가져옴
struct file *file = process_get_file(fd);
// 파일 객체의 주소 유효성을 확인
check_address(file);
// 파일 객체가 NULL인 경우 함수 종료
if (file == NULL) {
return;
}
// 파일 객체의 현재 커서 위치를 반환
return file_tell(file);
}
9️⃣ void close(int fd)
- 파일 디스크립터(fd)가 2보다 작은 경우, 즉 표준 입력(stdin) 또는 표준 출력(stdout)인 경우 함수를 종료합니다. 표준 입력과 출력은 닫을 수 없기 때문에 이 조건을 사용하여 처리합니다.
- 주어진 파일 디스크립터(fd)를 사용하여 스레드의 파일 테이블에서 파일 객체를 가져옵니다. process_get_file 함수를 호출하여 파일 객체를 얻습니다.
- 파일 객체를 찾을 수 없거나 파일 디스크립터가 유효하지 않으면 함수를 종료합니다.
- 현재 스레드의 파일 테이블에서 주어진 파일 디스크립터(fd)에 해당하는 엔트리를 NULL로 설정하여 파일을 닫습니다.
// 주어진 파일 디스크립터를 사용하여 열린 파일을 닫는 함수
void close(int fd) {
// 파일 디스크립터가 표준 입력 또는 표준 출력인 경우 함수 종료
if (fd < 2) {
return;
}
// 주어진 파일 디스크립터로부터 파일 객체를 가져옴
struct file *file = process_get_file(fd);
// 파일 객체의 주소 유효성을 확인
check_address(file);
// 파일 객체가 NULL인 경우 함수 종료
if (file == NULL) {
return;
}
// 파일 디스크립터의 범위를 확인하여 파일 테이블 엔트리를 NULL로 설정하여 파일을 닫음
if (fd < 0 || fd >= FDCOUNT_LIMIT) {
return;
}
thread_current()->fdt[fd] = NULL;
}
'운영체제' 카테고리의 다른 글
[Pintos-Kaist] Project3 - Memory Management (0) | 2023.06.16 |
---|---|
[Pintos-Kaist] Project2 - System Call(4) - (exec, fork, wait) (0) | 2023.06.11 |
[Pintos-Kaist] Project2 - System Call(2) - System Call Flow (0) | 2023.06.07 |
[Pintos-Kaist] Project2 - System Call(1) - (halt, exit, create, remove) (1) | 2023.06.06 |
[Pintos-Kaist] Project2 - Argument Passing (1) | 2023.06.05 |