PintOS | Project2. User Program | 시스템 콜 (프로세스 관련, 파일 관련)
실제 리눅스 환경에서 제공되는 시스템 콜의 종류는 다음과 같이 다양하다.
범주 | 설명 | 시스템 콜 |
프로세스 관리 | 프로세스 생성, 종료, 실행, 통신 등을 다룸 | fork, exit, execve, wait, kill |
파일 시스템 조작 | 파일 및 디렉토리 생성, 삭제, 읽기, 쓰기 등을 다룸 | open, close, read, write, chmod |
장치 관리 | 하드웨어 장치와의 상호작용 관련 기능을 다룸 | ioctl, mmap |
네트워크 관리 | 네트워크 통신 및 소켓 관리 관련 기능을 다룸 | socket, bind, connect |
메모리 관리 | 메모리 할당, 해제, 매핑 등을 다룸 | brk, mmap, munmap |
사용자 및 그룹 관리 | 사용자 및 그룹 식별, 관리 관련 기능을 다룸 | getuid, setgid, getgid |
시스템 정보 및 리소스 | 시스템 시간, 리소스 정보 조회 및 설정 관련 기능을 다룸 | time, sysinfo, getrlimit |
기타 | 로깅, 에러 처리 등 기타 기능을 다룸 | syslog, errno |
이 모든걸 다 구현하면 pint OS가 아니겠지??
핀토스에서 직접 구현하는 시스템 콜은 크게 두가지 분류로 나눌 수 있다:
1) 프로세스 관련 시스템 콜, 2) 파일 관련 시스템 콜.
핀토스에서 구현하게 되는 프로세스와 파일 시스템 조작 위주로 시스템 콜들이 어떻게 구현되는지 살펴보자.
프로세스 관련 시스템 콜은 프로세스의 생성, 실행, 종료, 상태 관리 등 프로세스 관리와 관련된 작업을 수행한다.
시스템 콜 | 용도 | 동작 방식 |
halt() | 시스템 종료 | power_off() 함수를 호출하여 전원을 끈다. |
exit(status) | 현재 실행 중인 프로세스를 종료 | 프로세스의 종료 상태를 status로 설정하고, thread_exit() 함수를 호출 |
fork(thread_name, interrupt frame) | 현재 프로세스의 복사본을 생성 | 부모 프로세스의 상태를 복사하여 새로운 자식 프로세스를 생성 |
exec(cmd_line) | 자식 프로세스 로드 후 실행 | 지정된 명령어 라인(cmd_line)에 따라 새 자식 프로그램을 로드하고 실행한다. |
wait(pid) | 자식 프로세스의 종료를 기다린다. | 지정된 프로세스 식별자(pid)를 가진 자식 프로세스가 종료될 때까지 현재 프로세스의 실행을 중단한다. |
프로세스 관련 시스템 콜 중에서, 프로세스 계층 구조 관련 내용만 따로 정리해보자.
exec과 fork, wait이 관련 시스템 콜이다.
부모 프로세스는 자식 프로세스 디스크립터를 리스트를 이용하여 관리한다.
또한 자식 프로세스는 자신의 부모 프로세스가 누구인지를 저장한다.
핀토스에서는 이를 구현하기 위해 thread 구조체에 다음과 같은 필드를 추가한다.
struct thread {
struct thread *parent; /* Parent thread. */
struct list child_list; /* List of child threads. */
struct list_elem child_elem; /* List element for child_list. */
/* 자식 프로세스가 load될 때까지 부모 프로세스를 block시키기 위한 semaphore */
struct semaphore load_sema;
/* 자식 프로세스가 exit될 때까지 부모 프로세스를 block시키기 위한 semaphore */
struct semaphore exit_sema;
struct semaphore wait_sema;
}
(부모, 자식 프로세스 실행흐름)
exec 함수는 파일 이름을 받아서, 해당 파일을 로드한다. 처음 시스템이 가동되어 데몬 프로세스가 실행될 때도 exec 함수가 사용된다.
fork 함수는 부모 프로세스를 복제한 자식을 만드는 시스템 콜이다.
/*
`name`으로 현재 프로세스를 복제한다. 새 프로세스의 스레드 ID를 반환하거나,
스레드를 생성할 수 없는 경우 TID_ERROR를 반환한다.
*/
tid_t process_fork(const char *name, struct intr_frame *if_ UNUSED) {
/* 현재 스레드를 새 스레드로 복제 */
struct thread *parent = thread_current();
tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, if_);
if (tid == TID_ERROR) {
return TID_ERROR;
}
/* 자식 프로세스 생성 대기를 위한 sema down */
struct thread *child = get_child_process(tid);
sema_down(&(child->load_sema));
return tid; // 자식프로세스 tid 반환
}
여기서 자식 프로세스 생성 시(thread_create)에, __do_fork 함수를 인자로 넣어주는데,
__do_fork 함수는 부모 프로세스의 실행 컨텍스트를 복사해서 자식에게 전달하는 역할을 수행한다.
파일 관련 시스템 콜은 파일 생성, 삭제, 읽기, 쓰기, 파일 속성 변경 등 파일 시스템과 상호작용하며 이와 관련된 작업을 수행한다.
시스템 콜 | 용도 | 동작 방식 |
create(file, initial_size) | 새 파일을 생성 | 주어진 이름(file)과 크기(initial_size)로 새 파일을 생성 |
remove(file) | 파일을 삭제 | 주어진 이름의 파일을 삭제 |
open(file) | 파일을 열고 파일 디스크립터를 반환 | 지정된 이름의 파일을 열고, 해당 파일에 대한 파일 디스크립터를 할당 |
filesize(fd) | 열린 파일의 크기를 반환 | 지정된 파일 디스크립터에 해당하는 파일의 크기를 반환 |
seek(fd, position) | 파일 내의 특정 위치로 이동 | 지정된 파일 디스크립터에 해당하는 파일의 읽기/쓰기 위치를 position으로 이동 |
tell(fd) | 파일의 현재 위치를 반환 | 지정된 파일 디스크립터의 현재 위치를 반환 |
close(fd) | 열린 파일을 닫는다. | 지정된 파일 디스크립터에 해당하는 파일을 닫고, 파일 디스크립터를 해제 |
read(fd, buffer, size) | 파일 또는 표준 입력에서 데이터를 읽는다. | 지정된 파일 디스크립터에서 size만큼 데이터를 읽어 buffer에 저장 |
write(fd, buffer, size) | 파일 또는 표준 출력에 데이터를 쓴다. | 지정된 파일 디스크립터에 buffer의 데이터를 size만큼 쓴다. |
파일 시스템 콜들을 보면 한가지 특징을 찾을 수 있다.
어떤 차이점이 있을까?
즉, 파일 이름을 사용하는 시스템 콜은 파일을 처음 찾고 조작하는 단계,
파일 디스크립터(fd)를 사용하는 시스템 콜은 이미 열린 파일에 대한 작업을 수행할 때 사용된다.
file 이름을 인자로 받는 함수 중 open을 자세히 살펴보자.
sys_open(file) 시스템 콜이 호출되면 다음과 같은 일을 수행한다.
1) 루트 디렉토리에서 주어진 파일 이름을 찾고,
2) 해당 파일에 대한 인덱스 노드를 기반으로 파일 객체를 생성하여 반환한다.
3) 해당 파일 객체는 이후 파일 읽기, 쓰기, 닫기 등의 작업에 사용된다.
(userprog/syscall.c)
int sys_open(const char *file) {
if (file == NULL) sys_exit(-1);
struct file *f = filesys_open(file);
if (f == NULL) {
return -1;
}
if (thread_current()->next_fd_idx >= 128) {
file_close(f);
return -1;
}
thread_current()->fd_table[thread_current()->next_fd_idx] = f;
return thread_current()->next_fd_idx++;
}
(filesys/filesys.c)
struct file *filesys_open(const char *name) {
struct dir *dir = dir_open_root();
struct inode *inode = NULL;
if (dir != NULL) dir_lookup(dir, name, &inode);
dir_close(dir);
return file_open(inode);
}
i-node는 물리 디스크에 적재된 데이터에 대한 위치 정보와 메타데이터를 지닌 일종의 포인터같은 역할을 수행한다.
(출처: 상기님 블로그)
(filesys/file.c)
/* Opens a file for the given INODE, of which it takes ownership,
* and returns the new file. Returns a null pointer if an
* allocation fails or if INODE is null. */
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;
}
}
PintOS | Project3. Virtual Memory | 트러블 모음 1 (0) | 2023.12.24 |
---|---|
운영체제 | 가상메모리 | 페이징과 주소번역 더 알아보기 (0) | 2023.12.16 |
PintOS | Project1. Threads | Advanced Scheduling (4.4 BSD) (2) | 2023.12.04 |
운영체제 | CPU 스케줄링 알고리즘 | MLFQ (0) | 2023.11.29 |
운영체제 | CPU 스케줄링 알고리즘 | RR, SJF, STCF (0) | 2023.11.26 |