sys_exit
이제 기본적인 시스템콜 구현을 보도록 하겠습니다
유저 프로그램이 exit을 호출하면 내부적으로 다음과 같은 시스템 콜 래퍼 함수가 호출됩니다:
void exit (int status) {
syscall1(SYS_EXIT, status); // 시스템 콜 호출
NOT_REACHED(); // 이후 코드는 절대 도달하지 않음
}
즉, SYS_EXIT이라는 번호를 가진 시스템 콜이 커널에 요청되지요~
void syscall_handler(struct intr_frame *f UNUSED)
{
uint64_t syscall_num = f->R.rax;
uint64_t arg1 = f->R.rdi;
uint64_t arg2 = f->R.rsi;
uint64_t arg3 = f->R.rdx;
uint64_t arg4 = f->R.r10;
uint64_t arg5 = f->R.r8;
uint64_t arg6 = f->R.r9;
switch (syscall_num)
{
case SYS_HALT:
sys_halt();
break;
case SYS_EXIT:
sys_exit(arg1);
break;
case SYS_FORK:
f->R.rax = process_fork((const char *)arg1, f);
break;
case SYS_EXEC:
f->R.rax = sys_exec((void *)arg1);
break;
case SYS_WAIT:
f->R.rax = sys_wait((tid_t)arg1);
break;
case SYS_CREATE:
f->R.rax = sys_create(arg1, arg2);
break;
case SYS_REMOVE:
f->R.rax = sys_remove(arg1);
break;
case SYS_OPEN:
f->R.rax = sys_open(arg1);
break;
case SYS_FILESIZE:
f->R.rax = sys_filesize(arg1);
break;
case SYS_READ:
f->R.rax = sys_read(arg1, arg2, arg3);
break;
case SYS_WRITE:
f->R.rax = sys_write(arg1, arg2, arg3);
break;
case SYS_SEEK:
sys_seek(arg1, arg2);
break;
case SYS_TELL:
f->R.rax = sys_tell(arg1);
break;
case SYS_CLOSE:
sys_close(arg1);
break;
default:
thread_exit();
break;
}
}
커널은 syscall 번호에 따라 sys_exit()함수를 호출하게 됩니다
sys_exit
void sys_exit(int status)
{
struct thread *cur = thread_current();
cur->exit_status = status;
printf("%s: exit(%d)\n", thread_name(), status);
thread_exit();
}
스레드 이름: exit(상태)의 형태를 출력하고 스레드를 종료하는 흐름인디
현재 실행 중인 스레드의 종료상태를 저장해두고,
메세지를 출력한 후 thread_exit()를 호출합니다!~
void thread_exit(void)
{
ASSERT(!intr_context());
#ifdef USERPROG
process_exit();
#endif
/* Just set our status to dying and schedule another process.
We will be destroyed during the call to schedule_tail(). */
intr_disable();
do_schedule(THREAD_DYING);
NOT_REACHED();
}
현재 스레드가 더이상 실행되지 않도록 THREAD_DYING상태로 표시하고
스케줄러가 다음 스레드를 선택하고
해당 스레드는 완저니 소멸되게됩니다....
결론적으로!
exit() 시스템 콜은 유저가 호출하면 커널이 처리하고,
현재 프로세스를 종료하고 스레드를 제거하는 흐름이다.
종료 상태는 커널이 추적할 수 있도록 exit_status에 저장된다.
로 정리할 수 있겠습니다
sys_write
/* write:
* 열린 파일 디스크립터에 데이터를 쓴다.
* fd: 쓸 대상 파일 디스크립터
* buffer: 쓸 데이터가 담긴 메모리 주소
* size: 쓸 바이트 수
* SYS_WRITE: write syscall 번호
* syscall3을 통해 fd, buffer, size를 전달한다.
* 반환값은 실제로 쓴 바이트 수이고, 실패 시 -1을 반환할 수 있다. */
int
write (int fd, const void *buffer, unsigned size) {
return syscall3 (SYS_WRITE, fd, buffer, size);
}
static int sys_write(int fd, const void *buffer, unsigned size)
{
if (fd == 1)
{
putbuf(buffer, size);
return size;
}
}
- 커널은 파일 디스크립터 fd의 값에 따라 어떤 출력 장치에 쓸지를 결정함
- fd == 1일 경우:
- stdout (표준 출력) 을 의미함
- 따라서 putbuf(buffer, size)를 호출해 콘솔에 출력
- putbuf는 buffer에 담긴 내용을 size만큼 터미널에 출력하고 끝
- 수정 예정입니다~
sys_open
open은 파일 열고 파일 디스트립터(fd)를 반환하고 실패시 -1을 반환하는 함수입니다

우선 파일 디스크립터를 사용하려면 각 프로세스 또는 스레드가 자신만의 파일 디스크립터테이블을 가져야합니다
Why?
- 유저는 파일을 “이름”으로 열지만,
- 유저 프로그램이 이후 접근할 땐 “숫자 fd”를 통해 접근함
- 그래서 내부적으로 fd → file 구조체 포인터 매핑을 유지해야 함
아무튼간 그래서 thread구조체에 추가해줍니다
#define MAX_FD 64
struct thread {
...
struct file **fd_table; // fd 번호로 파일에 접근하는 테이블
int fd_idx; // 다음 사용할 fd 번호
...
};
그리고 process_init에서 fd_table할당합니다
process_init(void)
{
struct thread *current = thread_current();
current->fd_table = calloc(MAX_FD, sizeof(struct file *));
current->fork_sema = malloc(sizeof(struct semaphore));
sema_init(current->fork_sema, 0);
ASSERT(current->fd_table != NULL);
}
참고로 MAX_FD는 32입니다
이제 sys_open함수를 보면
int sys_open(const char *file) {
check_address(file); // 사용자 영역 유효성 검사
if (file == NULL || strcmp(file, "") == 0)
return -1;
struct file *file_obj = filesys_open(file); // 내부 파일 시스템에서 열기
if (file_obj == NULL)
return -1;
int fd = find_unused_fd(file_obj); // 빈 FD 찾고 저장
return fd;
}
sys_open함수는 이런 흐름
filesys_open
struct file *filesys_open(const char *name) {
struct dir *dir = dir_open_root();
struct inode *inode = NULL;
bool exist = dir && dir_lookup(dir, name, &inode);
dir_close(dir);
if (!exist)
return NULL;
return file_open(inode);
}
- dir_lookup()을 통해 실제로 존재하는 파일인지 확인
- 존재한다면 inode를 얻고 file_open()으로 파일 객체 반환
bool
dir_lookup (const struct dir *dir, const char *name,
struct inode **inode) {
struct dir_entry e;
ASSERT (dir != NULL);
ASSERT (name != NULL);
if (lookup (dir, name, &e, NULL)) // 디렉터리 내 검색
*inode = inode_open (e.inode_sector); // 찾으면 inode 열기
else
*inode = NULL; // 못 찾으면 NULL
return *inode != NULL; // 성공 여부 반환
}
- 디렉터리 객체 dir에서 주어진 이름 name의 엔트리를 검색
- 찾으면 inode 포인터에 해당 파일의 inode를 열어서 저장
- 못 찾으면 inode는 NULL이 됨
- 성공 여부는 true / false로 반환
lookup()
- 디렉터리 파일을 한 엔트리씩 읽음 (inode_read_at)
- name이 일치하고 in_use라면 성공
inode_read_at()
- 디스크에서 엔트리 정보를 읽어오는 함수
- sector 단위로 읽고 필요한 부분만 복사
이러한 흐름을 거쳐서
파일이 존재하고 inode 사용 가능하면 *inode = inode_open(...) 결과를 저장하게 됩니다
file_open
주어진 inode를 기반으로 파일 객체(struct file)를 동적 생성하여 반환
struct file *
file_open (struct inode *inode) {
struct file *file = calloc (1, sizeof *file);
if (inode != NULL && file != NULL) {
file->inode = inode;
file->pos = 0;
file->deny_write = false;
return file;
} else {
inode_close (inode);
free (file);
return NULL;
}
}
- sizeof(struct file)만큼 heap에서 메모리 할당 (calloc)
- inode가 NULL이 아니고, 메모리 할당도 성공했으면:
- file->inode = inode : 해당 inode를 연결
- file->pos = 0 : 파일 포인터 시작 위치 초기화
- file->deny_write = false : 쓰기 허용 상태로 시작
- → file 반환
- 실패한 경우:
- inode_close(inode) 호출 → 참조 수 감소
- file 메모리 해제
- NULL 반환
그럼 이제 find_unused_fd함수로 사용되지 않은 파일 디스크립터 번호를 찾아 할당하고 반환해줍니다
int find_unused_fd(const char *file)
{
struct thread *cur = thread_current();
while (cur->fd_table[cur->fd_idx] && cur->fd_idx < MAX_FD)
cur->fd_idx++;
if (cur->fd_idx >= MAX_FD)
return -1;
cur->fd_table[cur->fd_idx] = file;
return cur->fd_idx;
}
- 현재 스레드의 파일 디스크립터 테이블(fd_table)을 확인
- fd_idx 위치가 비어 있지 않으면 다음 인덱스로 이동
- fd_idx가 MAX_FD 이상이면 → 더 이상 빈 공간이 없음 → -1 반환 (에러)
- 비어 있는 위치를 찾으면 해당 인덱스에 파일을 등록하고, 그 인덱스를 반환
핵심 포인트
- fd_idx는 테이블에서 탐색을 시작할 위치이며, 사용될 때마다 증가한다
- 실제로는 fd_table[2]부터 사용하게 구현되는 게 일반적 (0: stdin, 1: stdout 예약됨)
- 반환된 fd_idx는 유저 프로그램이 이후 read, write 등에서 사용할 파일 핸들
sys_close
// lib>user>syscall.c
int
open (const char *file) {
return syscall1 (SYS_OPEN, file);
}
//userprog>syscall.c
void sys_close(int fd)
{
struct thread *curr = thread_current();
if (fd < 2 || fd >= MAX_FD)
return NULL;
struct file *file_object = curr->fd_table[fd];
if (file_object == NULL)
return;
file_close(file_object);
curr->fd_table[fd] = NULL;
}
파일 디스크립터 fd를 닫는 함수입니다 프로세스가 종료되면 모든 fd는 자동으로 닫힙니다
file_close - 파일 구조체를 해제하고, 해당 inode도 닫아준다
void
file_close (struct file *file) {
if (file != NULL) {
file_allow_write (file);
inode_close (file->inode);
free (file);
}
}
- file_allow_write()로 쓰기 금지 해제
- inode_close()로 inode 참조 정리
- file 구조체 자체를 free()
inode_close
해당 Inode의 열려있는 참조 수(open_cnt)를 관리하고, 마지막 참조일 경우 자원을 완전히 해제
void
inode_close (struct inode *inode) {
/* Ignore null pointer. */
if (inode == NULL)
return;
/* Release resources if this was the last opener. */
if (--inode->open_cnt == 0) {
/* Remove from inode list and release lock. */
list_remove (&inode->elem);
/* Deallocate blocks if removed. */
if (inode->removed) {
free_map_release (inode->sector, 1);
free_map_release (inode->data.start,
bytes_to_sectors (inode->data.length));
}
free (inode);
}
}
- open_cnt를 하나 감소
- 참조가 모두 끊기면:
- inode 리스트에서 제거
- inode->removed == true일 경우, 디스크 블록까지 해제
- inode 자체를 free()
더이상 열려 있는 파일이 없으면, inode 및 디스크 자원을 완전히 해제한다
sys_create
사용자 입력으로 들어온 file이름의 파일을 커널이 완전한지 검사한 뒤, 내부적으로 inode를 생성하고 디렉토리에 등록하여 파일 생성
/* create:
* 파일 시스템에 새 파일을 생성하는 시스템 콜을 호출한다.
* file: 생성할 파일의 이름 (문자열 포인터)
* initial_size: 파일의 초기 크기 (바이트 단위)
* SYS_CREATE: create syscall 번호
* syscall2를 통해 인자 2개를 전달하고, 생성 성공 여부를 bool로 반환한다. */
bool
create (const char *file, unsigned initial_size) {
return syscall2 (SYS_CREATE, file, initial_size);
}
크기 initial_size의 새 파일을 생성합니다. 성공 시 true, 실패 시 false 반환
bool sys_create(const char *file, unsigned initial_size)
{
check_address(file);
if (file == NULL || strcmp(file, "") == 0)
{
sys_exit(-1);
}
return filesys_create(file, initial_size);
}
- file 포인터가 유저 영역의 올바른 주소인지 검증
- file이 비었거나 NULL이면 종료
- 실제 생성은 filesys_create()에게 위임
check_address
// 주소값이 유저 영역(0x8048000~0xc0000000)에서 사용하는 주소값인지 확인하는 함수
void check_address(const uint64_t *addr)
{
struct thread *cur = thread_current();
if (addr == "" || !(is_user_vaddr(addr)) || pml4_get_page(cur->pml4, addr) == NULL)
{
sys_exit(-1);
}
}
주소 유효성 검사 함수
- 현재 스레드의 page table(pml4)을 기준으로,
- 주소가 유저 영역에 있는가
- 페이지 테이블에서 매핑되어 있는가
- 조건을 하나라도 만족 못하면 sys_exit(-1)로 프로세스 종료
filesys_create
파일 시스템 레벨에서 inode와 디렉터리 엔트리를 직접 조작
bool
filesys_create (const char *name, off_t initial_size) {
// printf("FILE NAME :%s\\n", name);
disk_sector_t inode_sector = 0;
struct dir *dir = dir_open_root ();
bool success = (dir != NULL
&& name !=NULL
&& strlen(name)<=14
&& free_map_allocate (1, &inode_sector)
&& inode_create (inode_sector, initial_size)
&& dir_add (dir, name, inode_sector));
if (!success && inode_sector != 0)
free_map_release (inode_sector, 1);
dir_close (dir);
// printf("sucees? :%d\\n", success);
return success;
}
- dir_open_root() → 루트 디렉토리 열기
- 유효성 검사:
- name이 비어있지 않은지
- 이름 길이가 14글자 이하인지 (Pintos 내부 제한)
- inode 할당 (free_map_allocate)
- inode 생성 (inode_create)
- 디렉토리에 추가 (dir_add)
- 실패 시: 할당된 inode 공간 반환
성공 시: true, 실패 시: false
sys_remove
파일을 삭제합니다. 열려 있든 닫혀 있든 상관없이 삭제 가능. 성공 시 true
/* remove:
* 파일 시스템에서 주어진 파일을 삭제하는 시스템 콜을 호출한다.
* file: 삭제할 파일 이름
* SYS_REMOVE: remove syscall 번호
* syscall1로 파일 이름을 넘기고, 삭제 성공 여부를 bool로 반환한다. */
bool
remove (const char *file) {
return syscall1 (SYS_REMOVE, file);
}
bool sys_remove(const char *file)
{
return filesys_remove(file);
}
음….. 이게 다에요..
filesys_remove
bool
filesys_remove (const char *name) {
struct dir *dir = dir_open_root ();
bool success = dir != NULL && dir_remove (dir, name);
dir_close (dir);
return success;
}
- 루트 디렉터리를 열고
- dir_remove()로 해당 파일 삭제 시도
- 디렉터리 닫고 결과 반환
/* dir_remove - 디렉터리에서 주어진 이름(name)의 파일을 제거합니다.
*
* 동작 순서:
* 1. 디렉터리에서 name을 가진 파일을 검색 (lookup).
* 2. 해당 엔트리의 inode를 열어 실제 파일 메타데이터 접근.
* 3. 엔트리의 사용 상태를 false로 설정해 디렉터리에서 삭제.
* 4. 해당 inode를 제거하여 실제 파일 삭제 처리.
* 5. 성공 여부 반환.
*/
bool
dir_remove(struct dir *dir, const char *name)
{
struct dir_entry e; // 디렉터리 엔트리 구조체 (name, inode_sector 등 포함)
struct inode *inode = NULL; // 파일의 inode 포인터
bool success = false; // 성공 여부 초기화
off_t ofs; // 디스크 오프셋 (엔트리 위치)
ASSERT(dir != NULL);
ASSERT(name != NULL);
/* 1. 디렉터리에서 해당 이름의 엔트리를 찾는다. */
if (!lookup(dir, name, &e, &ofs))
goto done; // 찾지 못하면 실패
/* 2. 해당 엔트리의 inode를 연다. (파일의 메타데이터 접근) */
inode = inode_open(e.inode_sector);
if (inode == NULL)
goto done;
/* 3. 디렉터리 엔트리에서 삭제 표시 (in_use 플래그 해제) */
e.in_use = false;
/* 디스크 상의 디렉터리 파일에서 해당 엔트리 정보 갱신 */
if (inode_write_at(dir->inode, &e, sizeof e, ofs) != sizeof e)
goto done;
/* 4. 해당 inode를 삭제 (inode의 제거 플래그 설정 + 디스크 해제) */
inode_remove(inode);
/* 5. 삭제 성공으로 설정 */
success = true;
done:
/* 열었던 inode를 닫고 메모리 해제 */
inode_close(inode);
return success;
}
대충 흐름 설명하자면
- filesys_remove(name)
- 루트 디렉터리에서 dir_remove() 호출
- 성공하면 true 반환
- dir_remove(dir, name)
- 디렉터리 엔트리(파일 목록)에서 해당 파일 검색 → 삭제 플래그 설정
- 해당 파일의 inode 열어서 삭제 처리
- 디스크에서도 inode 제거
'크래프톤정글 > Pintos' 카테고리의 다른 글
| VM 시작 (0) | 2025.05.30 |
|---|---|
| User Program _System Call(테스트 별 정리_read-nomal, bad, syn, rox) (0) | 2025.05.25 |
| User Program _System Call(filesize, read, seek, tell) (0) | 2025.05.19 |
| User Program _ Argument Passing (1) | 2025.05.17 |
| User Program _ main부터 (0) | 2025.05.17 |