System calls and Handlers
개요
주 목표
- 기존 : system call handler table 비어있음.
- 수정 후
- Pintos의 system call handler 완성
- 유저에게 서비스를 제공하기 위한 시스템 콜 추가
- 프로세스 관련 :
halt
,exit
,exec
,wait
- 파일 관련 :
create
,remove
,open
,filesize
,read
,write
,seek
,tell
,close
- 프로세스 관련 :
수정할 파일
- threads/thread
- userprog/syscall
- userprog/process
System call
-
운영체제가 제공하는 서비스를 위한 programming interface
- 유저 모드 프로그램이 커널 기능을 사용하게 해준다.
- 시스템 콜은 커널 모드에서 실행되고 유저 모드에게 반환한다.
- 시스템 콜의 주요 포인트는 시스템 콜을 호출하기 위해 하드웨어 interrupts가 발생하여서 실행 모드의 우선순위가 특수(special) 모드로 상승한다는 점이다.
Pintos에서 시스템 콜의 호출 프로세스
그림
system call handler
시스템 콜 number를 사용하여 시스템 콜 handler에서 시스템 콜을 호출한다.
시스템 콜 number : syscall.c에 정의되어 있음
Requirement
- 시스템 콜 handler가 시스템 콜 number를 사용하여 시스템 콜을 호출하도록 해야함
- parameter 리스트에 있는 포인터의 타당함(validation) 확인
- 이 포인터들은 kernel area가 아닌 user area를 가리켜야 한다.
- 이 포인터들이 타당한 주소를 가리키지 않으면, page fault이다.
- user stack의 arguments를 kernel로 복사함
eax
레지스터에서 시스템 콜의 반환 값을 저장함
Address Validation
- 유저는 시스템 콜을 통해 잘못된 포인터를 전달할 수 있다.
- null pointer나 mapping되지 않은 가상 메모리를 가리키는 포인터
- 커널 가상 메모리 주소 공간(
PHYS_BASE
)을 가리키는 포인터
- 커널은 부적절한 포인터를 감지해야 하고 커널이나 다른 작동중인 프로세스들에게 악영향 없이 프로세스를 종결시켜 한다.
- 감지하는 방법
- 방법 1 : 유저가 제공하는 포인터의 타당성 검사
- 유저 메모리 접근을 다루는 가장 간단한 방식
- userprog/pagedir.c 나 threads/vaddr.h 에 있는 함수 사용
- 포인터의 타당성 확인한 페이지만 lock이나 할당
- 방법 2 : PHYS_BASE 밑만 가리키는 것만 확인
- 잘못된 포인터는 page_fault 야기.
page_fault()
코드를 수정하여 해결 가능 - MMU에서 강점이 있어 첫번째 방법보다 빠름
- 보통 실제 kernel에서 사용
- 메모리 접근에서 에러코드를 반환할 방법이 없기 때문에 더 어렵다.
- 후술할 함수들을 사용하여 처리할 수 있음
get_user
,put_user
page_fault
에서eax
를0xffffffff
로 설정하고 이전의 값을eip
로 복사하는 방법도 있음
- 잘못된 포인터는 page_fault 야기.
- 방법 1 : 유저가 제공하는 포인터의 타당성 검사
추가할 시스템 콜
void halt(void)
- Pintos 종료
void shutdown_power_off(void)
사용
void exit(int status)
- 프로세스를 나간다(exit)
void thread_exit(void)
사용- “Name of process: exit(status)”라는 메시지를 출력해야 함
pid_t exec (const char *cmd_line)
- 자식 프로세스를 만들고
cmd_line
에 상응하는 프로그램을 실행한다.
- 자식 프로세스를 만들고
int wait (pid_t pid)
- process id 가
pid
인 자식 프로세스의 종결을 기다린다.
- process id 가
Process Hierarchy(계층)
- 존재하는 프로세스를 프로세스 계층에 따라 상승시킨다.
- 부모 자식간 관계를 나타낸다.
- 부모 프로세스를 가리키는 포인터 :
struct thread*
- 형제 프로세스를 가리키는 포인터 :
struct list
- 자식 프로세스를 가리키는 포인터 :
struct list_elem
- 부모 프로세스를 가리키는 포인터 :
wait() system call
int wait (pid_t pid)
- 자식 프로세스
pid
가 exit하길 기다리고 exit status를 되찾아옴 pid
가 아직 있다면, 종결되길 기다린다.pid
가 exit하며 전달받은 status 반환pid
가exit
를 호출하지 않았지만 kernel이 종결시켰다면, -1을 반환- 부모 프로세스는 종결된 자식 프로세스를 기다리기 위해 호출할 수 있다.
- 종결된 자식 프로세스의 exit status 반환
- 자식이 종결된 이후, 부모는 자식의 process descriptor을 비할당화 해야한다.
wait
이 실패하고 -1을 반환하는 경우pid
호출 프로세스의 직접적인 자식을 가리키지 않을 때wait
을 호출한 프로세스가 이미pid
에wait
을 호출했었을
Correct implementation
- process_wait()
- child_tid를 사용하여 자식 프로세스의 descriptor를 찾는다.
- 자식 프로세스가 exit 하기 전까지 호출자를 막는다.
- 자식 프로세스가 exit하면, 자식 프로세스의 descriptor를 비할당화 하고 자식 프로세스의 exit status를 반환한다.
- Semaphore
- 쓰레드 구조체에 “wait”에 관한 세마포어 추가
- 쓰레드가 처음 생성될 때 세마포어는 0으로 초기화 된다.
- wait(
tid
)에서,tid
의 세마포어에 대한sema_down
을 호출한다. - 프로세스
tid
의 exit()에서,sema_up
을 호출한다. sema_down
과sema_up
의 올바른 자리가 중요하다
- Exit status
- 쓰레드 구조체에 exit status를 나타내는 field 추가
wait을 호출한 Parent와 child 의 흐름
그림
exec()
system call
pid_t exec(const *cmd_line)
cmd_line
을 실행하는 프로그램 가동- 쓰레드를 생성하고 가동한다. pintos에서
exec()
는 unix에서fork()+exec()
와 같음 - arguments를 실행될 프로그램에 전달
- 새로운 자식 프로세스의
pid
전달 - 프로그램을 load하거나 프로세스를 생성하는데 실패하면 -1 반환
exec
를 호출하는 부모 프로세스는 자식 프로세스가 생성되고 excutable을 완전히 load하기 전까지 기다려야 한다.
Kernel function for exec() : process_execute()
- 부모는 자식 프로세스가 성공적으로 생성되고 binary file이 완벽히 load 된걸 알 때까지 기다려야 한다.
-
세마포어
exec()
에 대한 세마포어를 쓰레드 구조체에 추가한다.- 쓰레드가 처음 생성될 때 세마포어는 0으로 초기화 된다.
sema_down
을 호출하여 자식 프로세스가 excutable file을 잘 load하길 기다린다.- excutable file이 load되면
sema_up
을 호출한다. sema_down
과sema_up
의 위치가 중요
- load status
- 쓰레드 구조체에서, 파일이 성공적으로 load되었는지를 나타내는 field가 필요함
exec를 호출한 부모와 자식의 흐름
그림
exit()
- 유저 프로그램을 종결시키고 커널에게 상태 반환
- 프로세스의 부모가 기다린다면, 이 함수가 반환될 status임
1
2
3
4
5
6
7
void exit (int status)
{
struct thread *cur = thread_current ();
/* 프로세스 descriptor에 exit status 저장 */
printf("%s: exit(%d)\n" , cur -> name , status);
thread_exit();
}
Kernenl function for exit() : thread_exit
- Exit status
- 프로세스의 status에 저장한다.
- 세마포어
- 현재 프로세스에
sema_up
호출
- 현재 프로세스에
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 스케쥴에서 현재 쓰레드를 제외하고(deschedule) 파괴한다. 호출자에게 반환되지 않는다. */
void
thread_exit(void)
{
ASSERT (!intr_context ());
#ifdef USERPROG
process_exit();
#endif
/* 모든 쓰레드 리스트에서 쓰레드를 제거하고, status를 dying으로 설정하고, 다른 프로세스를 스케쥴링한다. 그 프로세스는 thread_schedule_tail()를 호출할때 우릴 파괴한다..? */
intr_disable ();
list_remove (&thread_current()->allelem);
thread_current ()-> status = THREAD_DYING;
schedule ();
NOT_REACHED ();
}