오늘의 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];
}