오늘의 TIL
시스템콜 중 간단한 거 구현할 생각이다
사실 안간단하다
이거 하나 만드는데도 종일 걸렸다
걍 filesys_open 함수 쓰면 통과되긴 하는데
전부 통과되지가 않고 bad-ptr나 missing 같은 실패시의 케이스가 원하는 대답 안나온다
이거 해결하면서 하는게 개빡이다
static int system_open(const char *file) {
char kname[NAME_MAX + 1];
if (!copy_in_string(kname, file, sizeof kname))
return -1;
lock_acquire(&filesys_lock);
struct file *f = filesys_open(kname);
lock_release(&filesys_lock);
if (f == NULL) return -1;
int fd = fd_alloc(f);
if (fd < 0) {
lock_acquire(&filesys_lock);
file_close(f);
lock_release(&filesys_lock);
return -1;
}
return fd;
}
자그마치 3개!의 헬퍼함수의 콤-비네이션이다
맨 위부터 살펴보자
static int system_open(const char *file) {
char kname[NAME_MAX + 1];
if (!copy_in_string(kname, file, sizeof kname))
return -1;
일단 값받아서 파일 검사 들어간다
copy_in_string이 하는 역할이 중요하다
저걸로 문자열 잘 읽어 온다 실패시 -1 리턴 해주고 말이다
lock_acquire(&filesys_lock);
struct file *f = filesys_open(kname);
lock_release(&filesys_lock);
if (f == NULL) return -1;
그리고 락 걸어 안전하게 파일 시스템에 접근해 오픈해서 f에 저장한다
만약 NULL이라면 (오픈 실패) -1 리턴해준다
int fd = fd_alloc(f);
if (fd < 0) {
lock_acquire(&filesys_lock);
file_close(f);
lock_release(&filesys_lock);
return -1;
}
return fd;
}
저 fd_alloc이 핵심이다
기본적으로 유저로 하여금 직접 커널 건드리게 하면 안되니까
중간 매개체로 사용할 fd 핸들러 쥐어주는 역할이다
그리고 fd 값 이상하면 닫고 -1 반환
열었으면 닫아야 한다
그 중 닫아주는 역할을 맡고 있다
static void system_close(int fd) {
struct thread *t = thread_current();
if (fd < 0 || fd >= t->fd_cap) return; // 범위 밖
struct file *f = t->fd_table[fd];
if (f == NULL) return;
t->fd_table[fd] = NULL; // 먼저 테이블에서 제거
lock_acquire(&filesys_lock);
file_close(f);
lock_release(&filesys_lock);
}
얜 간단하다 fd 범위 올바른지랑 실존하는지 확인하고
테이블에서 제거 후 file_close호출해 닫으면 끝!
더 쉽다
static int
system_filesize(int fd) {
if (fd == 1 || fd == 0) return -1;
struct file *f = fd_get(fd);
if (f == NULL) return -1;
lock_acquire(&filesys_lock);
off_t len = file_length(f);
lock_release(&filesys_lock);
return (int)len;
}
fd 올바른지 확인하고
file_length 호출하면 끝이다
한가지 유의할 점은 처음에 fd_get으로 fd 획득하는거다
위에랑 똑같은 흐름이다
차이점은 void라 리턴값 없다
static void
system_seek(int fd, unsigned position) {
if (fd == 1 || fd == 0) return;
struct file *f = fd_get(fd);
if (f == NULL) return;
lock_acquire(&filesys_lock);
file_seek(f, position);
lock_release(&filesys_lock);
}
얘는 아예 똑같은 흐름이다
static unsigned
system_tell(int fd) {
if (fd == 1 || fd == 0) return -1;
struct file *f = fd_get(fd);
if (f == NULL) return -1;
lock_acquire(&filesys_lock);
off_t pose = file_tell(f);
lock_release(&filesys_lock);
return pose;
}
흐름 거의 똑같은데
처음에 제대로 문자열 받기위해 헬퍼 함수 쓴다
copy_in_string이라는 문자열 받는 함수다
static bool
system_create(const char *file, unsigned initial_size) {
char kname[NAME_MAX + 1];
bool ok = copy_in_string(kname, file, sizeof kname);
if (!ok) {
return false;
}
lock_acquire(&filesys_lock);
bool crt = filesys_create(kname, initial_size);
lock_release(&filesys_lock);
return crt;
}
창조했으니 파괴도 해줘야한다
static bool
system_remove(const char *file) {
char kname[NAME_MAX + 1];
if (!copy_in_string(kname, file, sizeof kname))
return false;
lock_acquire(&filesys_lock);
bool rem = filesys_remove(kname);
lock_release(&filesys_lock);
return rem;
}
이상한거 주는지 copy_in_string으로 검사해주고
문제 없으면 filesys_remove호출해서 제거해준다
static bool
copy_in_string(char *kdst, const char *usrc, size_t max_len) {
if (usrc == NULL) system_exit(-1);
size_t i = 0;
while (i < max_len) {
if (!is_user_vaddr(usrc + i)) system_exit(-1);
const char *k = pml4_get_page(thread_current()->pml4, usrc + i); //매핑된 가상 주소 얻기
if (k == NULL) system_exit(-1);
char c = *k;
kdst[i++] = c;
if (c == '\0') return true; // 정상 종료: 제한 내에서 NUL 발견
}
kdst[max_len - 1] = '\0'; // 끝문자열 null 처리 (최대 길이 벗어나서 절삭)
return false;
}
역할은 문자열이 max_len보다 긴지랑 끝이 nul로 잘 끝나는지 검사하고
이를 커널 버퍼에 복사해주는 역할이다
쪼개서 보자
static bool
copy_in_string(char *kdst, const char *usrc, size_t max_len) {
if (usrc == NULL) system_exit(-1);
포인터 이상하면 종료
size_t i = 0;
while (i < max_len) {
if (!is_user_vaddr(usrc + i)) system_exit(-1);
is_user_vaddr은 유저 영역 주소인지 확인하는 함수로
영역 아니면 종료
const char *k = pml4_get_page(thread_current()->pml4, usrc + i);
if (k == NULL) system_exit(-1);
pml4_get_page로 매핑된 가상주소 받아온다
못받으면 당연히 종료
char c = *k;
kdst[i++] = c;
if (c == '\0') return true;
}
매핑 된거 읽고 커널에 저장한다
만약 nul 문자면 안전하게 다 읽은거니 true 리턴
kdst[max_len - 1] = '\0';
return false;
}
여기까지 왔다는 건 최대 길이 벗어났다는 거니..
범위 끝 그냥 nul문자로 매꿔버리고 false 리턴
시스템콜 open 같은 거 쓸때에는 커널 객체(file)를 직접 유저에게 맡기면 안되기에 쓴다
그 대신 파일 디스크립터를 줘야 하는데 이 파일 디스크립터를 할당해주는게 이녀석의 역할이다
커널 객체 직접 만드는 대신 이를 다룰 핸들러를 만들어주기 말이다
스레드의 fd테이블에서 찾아서 만들어주는데 슬롯 꽉차면 확장한다
물론, 여기선 구현안했다
그거는 나중에 짬나면 할듯?
static int fd_alloc(struct file *f) {
struct thread *t = thread_current();
if (!fd_ensure_table()) return -1;
for (int i = FIRST_FD; i < t->fd_cap; i++) {
if (t->fd_table[i] == NULL) {
t->fd_table[i] = f;
return i;
}
}
return -1;
}
로직 뭐 볼거도 없다
fd_table 스캔하다가 빈곳 발견하면 거기 쓰는거다
저기 위에 보면 !fd_ensure_table이라고 썼는데
말그대로 fd table 보장해준다
없으면 만들어주고 있으면 그냥 쓴다
성공시 true, 실패시 false 반환
static bool fd_ensure_table(void) {
struct thread *t = thread_current();
/* 있으면 그냥 쓰기 */
if (t->fd_table && t->fd_cap > 0) return true;
/* 초기 테이블 생성 */
int cap = FD_GROW_STEP;
struct file **newtab = (struct file **)palloc_get_page(PAL_ZERO);
if (!newtab) return false;
t->fd_table = newtab;
t->fd_cap = PGSIZE / (int)sizeof(t->fd_table[0]); // 페이지 한 장 크기만큼 확보
return true;
}
fd가 범위 내에 있는게 맞는지 확인하고
테이블 칸이 NULL 인지 아닌지 확인하고
돌려준다
간단하지만 자주 쓰이는 헬퍼 함수다
static struct file *
fd_get(int fd) {
struct thread *t = thread_current();
if (fd < 0 || fd >= t->fd_cap) return NULL;
return t->fd_table[fd];
}