728x90
- 과제 관련 설명
- Process related: halt, exit, exec, wait, fork
- File related: create, remove, open, filesize, read, write, seek, tell, close
🎯 Goal
- Modified files
threads/thread.h
threads/thread.c
userprog/syscall.h
userprog/syscall.c
userprog/process.c
📝 Functions List
Ⅰ. Help Functions(추가 함수)
1. check_address
👉 시스템 콜의 인자로 들어오는 모든 포인터 값을 검사한다.
- 전체 시스템 콜 관련 함수
/* userprog/syscall.c */
static void
check_address(void *addr) {
struct thread *curr = thread_current();
if (!is_user_vaddr(addr) || pml4_get_page(curr -> pml4, addr) == NULL || addr == NULL) // 유저 영역인지 NULL 포인터인지 확인
exit(-1);
}
- 시스템 콜의 인자로 들어오는 주소 값이 유효한 주소 영역인지 확인한다.
- ‘USER_STACK’ ->
is_user_vaddr
내장 함수 응용 - [Hanyang Univ p.70] exit(-1)로 처리하라.
- 예외 처리 경우
- Null 포인터
- 매핑되지 않은 가상 메모리에 대한 포인터
- 커널 가상 주소 공간에 대한 포인터(KERN_BASE)
2. fdt_add_fd
👉 fd table에 인자로 들어온 파일 객체를 저장하고 fd를 생성한다.
- ⭐ 파일 디스크립터 관련 함수
/* userprog/syscall.c */
static int
fdt_add_fd(struct file *f) {
struct thread *curr = thread_current();
struct file **fdt = curr->fdt;
// fd가 제한 범위를 넘지 않고 fdt의 인덱스 위치와 일치 시
while (curr->next_fd < FDCOUNT_LIMIT && fdt[curr->next_fd]) {
curr->next_fd++;
}
// fdt가 가득 찼을 때 return -1
if (curr->next_fd >= FDCOUNT_LIMIT)
return -1;
fdt[curr->next_fd] = f; // fdt에 해당 fd 새로 넣어줌
return curr->next_fd;
}
- [Hanyang Univ] ‘process_add_file’ 함수 → 의미적 명확성을 위해 이름 변경
3. fdt_get_fd
👉 fd table에서 인자로 들어온 fd를 검색하여 찾은 파일 객체를 리턴한다.
- ⭐ 파일 디스크립터 관련 함수
/* userprog/syscall.c */
static struct file *
fdt_get_file(int fd) {
struct thread *curr = thread_current();
if (fd < STDIN_FILENO || fd >= FDCOUNT_LIMIT) { // 실패
return NULL;
}
return curr->fdt[fd]; // 성공
}
- [Hanyang Univ] ‘process_get_file’ 함수 → 의미적 명확성을 위해 이름 변경
4. fdt_remove_fd
👉 fd table에서 인자로 들어온 fd를 제거한다.
- ⭐ 파일 디스크립터 관련 함수
/* userprog/syscall.c */
static void
fdt_remove_fd(int fd) {
struct thread *curr = thread_current();
if (fd < STDIN_FILENO || fd >= FDCOUNT_LIMIT) // 실패
return;
curr->fdt[fd] = NULL; // 성공
}
- [Hanyang Univ] ‘process_close_file’ 함수 → 의미적 명확성을 위해 이름 변경
Ⅱ. 파일 디스크립터 구현
- 📢 file descriptor 필요!
- open, filesize, read, write, seek, tell, close
- open, read, write 구현 시에는 파일에 대한 동시접근이 발생할 수 있으니, Lock을 사용한다!
👀 파일 디스크립터 관련 개념!
Ref. CS110: Principles of Computer Systems
- 파일 디스크립터는 리소스(대부분 파일에 해당)간에 상호작용(시스템 콜을 통한)하기 위한 식별자이다.
- 식별자의 별다른 의미는 없다.
- 새로운 파일 디스크립터를 할당할 때, 커널은 가장 작은 숫자를 할당한다.
- 파일 디스크립터의 진입점은 단지 하나의 파일 테이블에 대한 포인터이다.
- i-node 🆚 v-node
- i-node : 파일에 대한 메타 데이터(커널 x)
- v-node : 파일에 대한 실질적 객체(커널 o)
- Ref. Inode vs Vnode
- 파일 디스크립터를 통한 파일 참조의 구조(큰 그림)
- 파일 디스크립터는 프로세스 당 하나 존재하며, 열려 있는 파일에 대한 파일 테이블 엔트리는 커널에 존재한다. 각 프로세스 당의 작업이 다르므로(작업 현황 R/W) 파일 테이블 엔트리도 각각 생기게 되는 것 같다.
(⭐ 정확하진 않으니까 레퍼런스 더 찾아보기!) - But, 파일 자체는 똑같으므로 i-node는 동일하다.
- 같은 프로세스에서 같은 파일 참조
- open 시스템 콜을 통해서 파일 디스크립터가 생성된다는 걸 뜻하는 것 같다.
그래서 같은 파일임에도 다른 ‘fd flags(idx)’를 갖게 된다.
- 다른 프로세스에서 같은 파일 참조
- 핀토스의 파일 디스크립터
- Project 3 이전의 할당은 모두 palloc 으로 구현한다.
- [KAIST p.59] 파일 구조체를 가진 파일 디스크립터 테이블을 만든다.
fd는 파일 디스크립터 테이블의 인덱스이다. (순차적 할당) - fd 0, 1 은 stdin, stdout에 할당되어져 있다.
- [KAIST p.60] FDT를 커널 메모리 공간에 할당한다.
1. thread
/* threads/thread.h */
#define FDT_PAGES 3 // fdt 할당시 필요한 페이지 개수
#define FDCOUNT_LIMIT FDT_PAGES *(1<<9) // 3(테이블 개수) * 512(한 테이블 당 전체 엔트리 개수)
- fdt 페이지 할당을 위해 페이지 크기를 정의한다.
/* threads/thread.h */
struct thread {
...
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
struct file **fdt; // 파일 디스크립터 테이블(프로세스당 개별적으로 존재)
int next_fd; // 다음 fd 인덱스
...
};
- fd table을 가리키는 포인터 값
fdt
과 fdt의 다음 fd 인덱스를 가리키는next_fd
를 선언한다. - ⚠️ 이때, fd table에는 파일 객체 자체가 들어가므로
struct file
타입의 포인터로 선언해준다!
/* threads/thread.c */
tid_t
thread_create (const char *name, int priority, thread_func *function, void *aux) {
...
t->fdt = palloc_get_multiple(PAL_ZERO, FDT_PAGES); // fdt 공간 할당
if (t->fdt == NULL) {
return TID_ERROR;
}
t->next_fd = 2; // 0 : stdin, 1 : stdout
t->fdt[0] = 1; // STDIN_FILENO -> dummy value
t->fdt[1] = 2; // STDOUT_FILENO -> dummy value
...
return tid;
}
- 스레드 생성 시, fdt 공간을 새로 할당해준다.
STDIN(0)
,STDOUT(1)
은 미리 콘솔을 위해 예약된 fd이므로next_fd
는 2부터 시작하도록 초기화한다.fdt[0]
과fdt[1]
에는 0(NULL)이 아닌 값으로 채워준다.
/* userprog/process.c */
#include "lib/user/syscall.h" // need to Calling syscall_close.
...
void
process_exit (void) {
...
for (int i = 0; i < FDCOUNT_LIMIT; i++) { // 프로세스 종료 시, 해당 프로세스의 fdt의 모든 값을 0으로 만들어준다.
close(i);
}
palloc_free_multiple(curr->fdt, FDT_PAGES); // fd table 메모리 해제
...
}
close
system call 설명에 대한 구현이다.- [Hanyang Univ p.132] “프로세스가 종료될 때, 메모리 누수 방지를 위해 프로세스에 열린 모든 파일을 닫는다.”
- 프로세스 종료 시, 해당 프로세스 fdt에 열려있는 모든 파일에 대한 fd를 0으로 만들어준다.
thread_create
의 메모리 할당 부분에서 페이지를 모두 0으로 초기화한 뒤, 할당 하지만 혹시 모를 메모리 값에 대한 충돌을 막기 위해 for문을 돌면서 fdt의 모든 엔트리를 0으로 만들어주는 작업을 수행한다.
t->fdt = palloc_get_multiple(PAL_ZERO, FDT_PAGES);- 💡 실제로 코드 내 for문 부분을 지워도 TC는 통과된다!
🙋♂️ Q1. palloc_get_multiple
로 할당하는 이유
/* threads/palloc.h */
void *palloc_get_page (enum palloc_flags flags) // 하나의 페이지
void *palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) // page_cnt개의 연속된 페이지
- fdt 할당 시 페이지 개수를 3으로 해주는 이유는 페이지 개수를 3미만으로 할당하게 되면 "multi-oom" TC는 불통한다...
- 아마 fd개수가 512 * 2(1024)를 넘게 넣어주는듯
- 따라서 공간을 넉넉히 할당해주는 것!
🙋♂️ Q2. 페이지 하나 당 엔트리 개수가 1<<9
(512개)인 이유?
- 핀토스 기본 페이지 하나의 사이즈 :
1 << 12(PGSIZE)
threads/vaddr.h
에 선언되어있다.
- fd table에 저장하는 데이터 타입 : 파일 객체의 포인터(
struct file**
)- sizeof(struct file**) : 8(byte)
- ∴
1<<12
/1<<3
= 512
4096(byte) / 8(byte) = 512
2. lock
/* userprog/syscall.h */
...
void close (int fd); // "userprog/process.c"에서 close 시스템 콜 호출을 위해 필요
struct lock filesys_lock; // 파일 점유시 필요한 락
...
- [Hanyang Univ p.133]
- read, write 시 파일에 대한 동시 접근이 일어날 수 있으므로 lock을 사용한다.
/* userprog/syscall.c */
void
syscall_init (void) {
...
lock_init(&filesys_lock); // 파일 읽고 쓰기에 필요한 락 초기화 _defined "userprog/syscall.h"
}
Ⅲ. 파일 관련 시스템 콜 구현
해당 프로젝트의 초점은 파일 시스템이 아니므로 filesys 디렉토리에 간단하게 구현된 파일 시스템을 이용한다.
(ref. Gitbook - Introduction)
🔅 파일 관련 시스템 콜에서 사용하는 함수 모두가 “filesys/filesys.h” 또는 "filesys/file.h" 에 있으므로 적절히 가져다 쓰면 된다.
1. create
file
을 이름으로 하고 크기가initial_size
인 새로운 파일을 생성한다.- 파일 생성은 파일 열기를 의미하지 않는다.
파일을 여는 것은open
시스템 콜이 수행한다. - return
- 성공 시, true
- 실패 시, false
/* userprog/syscall.c */
bool
create(const char *file, unsigned initial_size){
check_address(file);
return filesys_create(file, initial_size);
}
2. remove
file
이름을 가진 파일을 삭제한다.- 파일은 열려있는 지 닫혀있는지와 관계없이 삭제될 수 있다.
- ⭐ 삭제 전 파일 닫기 작업 필요!!
- return
- 성공 시, true
- 실패 시, false
/* userprog/syscall.c */
bool
remove(const char *file){
check_address(file);
return filesys_remove(file);
}
3. open
- 파일(
file
: 파일 포인터)을 연다. - 콘솔용으로 예약 되어있는 파일 디스크립터
0
: 표준 입력(STDIN_FILENO)1
: 표준 출력(STDOUT_FILENO)
- 각 프로세스는 독립적인 파일 디스크립터들의 세트를 가지고 있다.
→ ⭐ 파일 디스크립터 테이블 구현 필요 - 파일 디스크립터는 자식 프로세스들에게 상속된다.
- 하나의 파일이 한번 이상 열릴 때(단일 프로세스 또는 다중 프로세스로 부터), 각 open 프로세스는 새로운 파일 디스크립터를 반환한다.
→ ⭐ open 을 통해 새로운 파일 디스크립터가 생성된다.→ 💥 주의
- close를 하면서 각각의 닫힌 파일 위치는 공유하지 않는다.
- return
- 성공 시, fd
- 실패 시, -1
/* userprog/syscall.c */
int
open (const char *file){
check_address(file);
struct file *target_file = filesys_open(file);
if (target_file == NULL) {
return -1;
}
int fd = fdt_add_fd(target_file); // fdt : file data table
// fd table이 가득 찼다면
if (fd == -1) {
file_close(target_file);
}
return fd;
}
4. filesize
filesys
의 내장 함수file_length
를 이용한다.
- ⚠️ 이때, file_length 의 파라미터가
struct file*
임을 유의! - ⚠️ 따로 테스트 코드 x
/* userprog/syscall.c */
int
filesize (int fd){
struct file *target_file = fdt_get_file(fd);
if (target_file == NULL)
return -1;
return file_length(target_file);
}
5. read
fd
로 열린 파일에서size
바이트를buffer
로 읽는다.- 실제로 읽은 바이트 수를 리턴하거나 만약 파일을 읽을 수 없다면(EOF로 인한) -1을 리턴한다.
fd
0은input_getc()
를 이용해서 키보드에서 읽는다.
- input_getc
/* devices/input.c */
/* Retrieves a key from the input buffer.
If the buffer is empty, waits for a key to be pressed. */
/* 입력 버퍼로부터 누른 키 값을 가져온다.
만약 입력 버퍼가 비어있으면 키를 누를 때까지 기다린다. */
uint8_t
input_getc (void) {
enum intr_level old_level;
uint8_t key;
old_level = intr_disable ();
key = intq_getc (&buffer);
serial_notify ();
intr_set_level (old_level);
return key;
}
/* userprog/syscall.c */
int
read(int fd, void *buffer, unsigned size) {
check_address(buffer);
int read_bytes = -1;
if(fd == STDIN_FILENO){ // fd 0 reads from the keyboard using input_getc()._gitbook
int i;
unsigned char *buf = buffer;
for (i = 0; i < size; i++)
{
char c = input_getc();
*buf++ = c;
if (c == '\0')
break;
}
return i;
}
else{
struct file *file = fdt_get_file(fd);
if (file != NULL && fd != STDOUT_FILENO){ // STDOUT_FILENO
lock_acquire(&filesys_lock); // 파일을 읽는 동안은 접근 못하게 락 걸어줌
read_bytes = file_read(file, buffer, size);
lock_release(&filesys_lock); // 락 해제
}
}
return read_bytes;
}
- 파일에 대한 읽기 작업을 수행하므로 lock을 걸어준다.
6. write
buffer
에서 열린 파일fd
로size
만큼의 바이트를 쓴다.- 실제로 쓴 바이트 수를 리턴한다.
- 일부의 바이트를 쓸 수 없는 경우에는 리턴이
size
보다 작을 수 있다. - 일반적으로 파일의 끝을 지나서 쓰면 파일이 확장되지만 핀토스 파일 시스템으로는 불가하다.
- 파일 끝까지 가능한 한 많은 바이트를 쓰고, 실제 바이트 수를 리턴하거나 쓸 수 없는 경우
0
을 리턴한다. fd
1은putbuf()
를 이용해서 콘솔에 쓴다.
/* userprog/syscall.c */
int
write (int fd, const void *buffer, unsigned size) {
check_address(buffer);
int write_bytes = -1;
if (fd == STDOUT_FILENO){
putbuf (buffer, size);
return size;
}
else {
struct file *file = fdt_get_file(fd);
if (file != NULL && fd != STDIN_FILENO){ // STDIN_FILENO
lock_acquire(&filesys_lock); // 파일을 쓰는 동안은 접근 못하게 락 걸어줌
write_bytes = file_write(file, buffer, size);
lock_release(&filesys_lock); // 락 해제
}
}
return write_bytes;
}
7. seek
- 열린 파일
fd
에서 읽거나 쓸 다음 바이트를position
으로 바꾼다.- 즉,
position
0
은 파일의 시작 위치이다.
- 즉,
- 현재 파일의 끝을 지나서 읽는 것은 오류가 아니다.
- 파일의 끝을 지나서
read
를 실행하면 0(byte)를 반환한다.
- 파일의 끝을 지나서
- But, 파일의 끝을 지나서 쓰는 건(
write
) 불가능하다.- 프로젝트 4 이전까지는 파일의 크기가 고정되어 있기 때문
/* userprog/syscall.c */
void
seek (int fd, unsigned position){
struct file *target_file = fdt_get_file(fd);
if (fd <= STDOUT_FILENO || target_file == NULL)
return;
file_seek(target_file, position);
}
8. tell
- 열린 파일
fd
에서 읽거나 쓸 다음 바이트(파일의 시작 지점 부터)를 반환한다.
/* userprog/syscall.c */
unsigned
tell (int fd){
struct file *target_file = fdt_get_file(fd);
if (fd <= STDOUT_FILENO || target_file == NULL)
return;
file_tell(target_file);
}
9. close
- fd를 닫는다.
- 프로세스를 종료하거나 제거하는 것은 묵시적으로 해당 프로세스에 열려있는 모든 fd를 닫는다.
- fd 각각에 대해 호출한다.
/* userprog/syscall.c */
void
close (int fd){
struct file *target_file = fdt_get_file(fd);
if (fd <= STDOUT_FILENO || target_file == NULL || target_file <= 2)
return;
fdt_remove_fd(fd); // fd table에서 해당 fd값을 제거한다.
file_close(target_file); // 열었던 파일을 닫는다.
}
728x90
'🌱 Dev Diary > 📄 TIL' 카테고리의 다른 글
Project 3. Introduction (0) | 2023.01.19 |
---|---|
Project 2. System Calls 구현 (3) - Process related (0) | 2023.01.05 |
Project 2. System Calls 구현 (1) - 개요 (0) | 2023.01.02 |
Project 1. Priority Scheduling 구현 (2) (0) | 2022.12.28 |
Project 1. Priority Scheduling 구현 (1) (1) | 2022.12.26 |