95개의 테스트 케이스 중 15개를 실패한 코드입니다. 주의해서 참고하세요.
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
pass tests/userprog/close-twice
pass tests/userprog/close-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
pass tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/filesys/base/lg-create
FAIL tests/filesys/base/lg-full
FAIL tests/filesys/base/lg-random
FAIL tests/filesys/base/lg-seq-block
FAIL tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
FAIL tests/filesys/base/sm-full
FAIL tests/filesys/base/sm-random
FAIL tests/filesys/base/sm-seq-block
FAIL tests/filesys/base/sm-seq-random
FAIL tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
pass tests/userprog/no-vm/multi-oom
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
15 of 95 tests failed.
시스템 콜(System call)
- 운영 체제가 제공하는 서비스에 대한 프로그래밍 인터페이스
- 사용자 모드의 프로그램이 커널 기능을 사용할 수 있도록 한다
- 시스템 콜은 커널 모드에서 처리된 후, 사용자 모드로 복귀한다
유저 프로그램이 함부로 OS에 접근한다면, 보안부터 시작해서 여러가지 문제를 야기할 수 있다. 또한, 유저 프로그램이 하드웨어 단까지 내려가서 작업하는 것은 매우 복잡하다. 이를 위해서 운영체제를 제공했지만 운영체제 내부도 매우 복잡했다.
따라서 OS의 중요한 부분은 숨기고 추상화해 인터페이스만 제공한 것이 시스템 콜이다. 시스템 콜을 통해 유저 프로그램은 커널 기능을 쉽게 이용할 수 있게 된다. 유저 프로그램은 평상시엔 커널 영역에 접근하지 못 하다가 커널 영역에 접근해야 할 때 시스템 콜을 호출한다. 그러면 커널 영역에서 작업을 수행하고, 반환값을 유저 프로그램에 전달한다.
아래는 유저 프로그램에서 운영체제에 write() 시스템 콜을 요청한 상황이다. 작성한 값을 출력 장치(모니터)에 띄워야 할 수도 있고, 디스크에 저장해야 할 수도 있다. 두 과정 모두 커널의 도움이 필요하다. 따라서 write() 자체가 시스템 콜이며, write()에 넣은 인자가 syscall로 넘어간다.
1. 유저 프로그램에서 write() 시스템 콜을 호출한다.
2. write() 함수는 받은 인자를 유저 스택에 넣은 후 커널로 진입한다. 이 때 스택 포인터는 인자가 들어간 주소를 가리킨다.
3. Interrupt Vector table에 가면, 각 주소별로 어떤 인터럽트를 실행해야 하는지 매핑되어 있다. 0x30은 시스템 콜 핸들러를 호출하는 주소이다. 따라서 syscall_handler를 호출한다.
4. 이제 도달한 syscall_handler는 우리가 구현해야 할 함수이다. 유저 스택에 number가 있는데, 이 number는 system call number이다. 각 system call은 매핑되는 system call number를 가지고 있으며, number에 맞는 system call이 호출된다.
우리는 이 작업을 수행할 수 있도록 system call handler를 구현해야 한다.
syscall 명령은 x86-64에서 system call을 호출하는 가장 일반적인 수단이다. ( 이 때, syscall은 어셈블리어 명령어다 )
PintOS에서도 사용자 프로그램은 system call을 호출하기 위해 syscall을 수행한다.
이 때, %rax 레지스터에는 system call number가 있으며 인자는 각각 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 전달된다.
%rdi, %rsi 등등은 어떠한 형식의 값을 항상 의미하는 건 아니며, system call 함수를 호출할 때 들어오는 순서일 뿐이다. 즉, 첫번째 인자는 반드시 %rdi이지만 이 안에 어떤 인자가 있을지는 각 system call마다 다르다.
syscall_handler를 구현할 때, 인자로써 인터럽트 프레임이 들어온다. 인터럽트 프레임 안에 레지스터 정보 등 우리가 원하는 값이 모두 들어있기 때문에 인터럽트 프레임은 매우 중요하다. 인터럽트 프레임은 "커널 스택"에 존재하며, caller 함수는 인터럽트 프레임에 callee에게 줄 인자를 전달하기 위해 액세스할 수 있다.
syscall-nr.h
syscall-nr.h 파일이다. 이 함수에는 system call과 system call number의 매핑정보가 저장되어 있다.
halt부터 0, exit는 1, fork는 2 이런 식으로 증가한다. 이번 project2 에서는 13번 CLOSE까지만 진행하면 된다.
syscall.c
입력받은 주소가 유저 영역인지 체크하는 함수이다.
유저 영역이 아니거나(커널 영역이거나), 주소 자체가 NULL이거나, 현재 스레드의 페이지 테이블에 해당 주소가 매핑되어 있지 않다면 프로그램을 종료한다.
사용자 프로그램은 커널 영역에 접근할 수 없다. system call을 통해서도 말이다.
syscall.c
우선 syscall_handler의 뼈대부터 작성한다. 맨 밑에 있던 printf는 테스트 케이스 통과를 위해선 지워줘야 한다. 출력때문에 테스트에 통과하지 못 하기 때문이다.
첫 부분에서 인터럽트 프레임의 rax에 저장된 (f->R.rax) system call number를 가져와서 해당 number에 맞는 system call을 호출하게 된다.
return 값이 있는 system call이라면 다시 f->R.rax에 리턴값을 저장한다. %rax 레지스터는 본래 리턴값을 저장하는 레지스터이기 때문이다.
또한 인자는 반드시 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 들어간다. 인자 안에 뭐가 들었는지와는 관계 없이.
thread.h
struct thread, 스레드 구조체이다.
여기에 필요한 필드들을 미리 선언해두자.
syscall.c
halt는 pintos를 종료시키는 함수이다.
간단하게 power_off() 함수를 실행시키기만 하면 된다.
syscall.c
현재 돌고 있는 프로세스를 종료시키는 시스템 콜이다.
스레드의 exit_status를 설정한 후 스레드 이름, status와 함께 종료 메시지를 출력한다.
thread.c
init_thread 함수에서 exit_status를 초기화해 준다.
syscall.c
파일을 생성하는 시스템 콜이다.
파일명과 size를 인자로 받아서 filesys_create 함수로 파일을 만든다.
파일 관련 함수를 사용하기 전에 lock을 얻고 끝나면 해제한다.
파일에 여러 프로세스가 동시에 접근한다면 다양한 문제가 발생할 수 있다. 또한 lock을 걸지 않으면 통과하지 못하는 테스트들도 많다.
따라서 file에 대한 lock은 필수사항이다.
syscall.h
syscall.h 헤더 파일에 filesys_lock을 추가해 주자.
syscall.c
syscall_init 함수에서 filesys_lock 초기화도 진행해 주자.
syscall.c
파일 이름을 입력받아 파일을 삭제하는 시스템 콜이다.
create와 거의 흡사하고 함수명만 다르다.
syscall.c
write()는 buffer에 있는 값을 size만큼 출력하는 시스템 콜이다.
fd가 표준출력 (1)일 경우 putbuf 함수를 이용해 그대로 출력한다.
fd가 표준입력 (0)일 경우 출력하지 않고 -1을 return한다.
fd가 파일(2이상)일 경우 해당 파일에 값을 쓴다.
PintOS에서는 표준에러(2)를 사용하지 않기 때문에 0, 1만 처리해주면 된다.
write()함수는 PintOS 테스트 케이스에서도 이용한다. 아래 사진처럼 출력을 비교해서 테스트 케이스 pass 여부를 검사하는데, 이때 write()가 사용된다.
따라서 write()함수를 구현하지 않으면 어떤 테스트도 통과할 수 없다.
process.c
그리고 argument passing에서 추가하지 않았던 코드를 추가해야 한다.
char *parsing;
strtok_r(file_name, " ", &parsing);
process_exec 뿐만 아니라 여기서도 파싱해주지 않으면 명령어 인자까지 그대로 thread_create 함수로 들어가기 때문에 오류가 생길 수 있다.
thread.h
스레드 구조체에 fd_table과 fd_idx를 추가해 준다. 두 필드는 파일 디스크럽터를 위한 필드이다.
운영체제는 프로세스별로 파일 디스크립터를 따로 관리한다. PintOS에서는 멀티스레딩이 지원되지 않으므로 프로세스가 곧 스레드이기에, 스레드 구조체에 파일 디스크럽터를 관리하는 필드를 생성해 주면 된다.
스레드마다 갖고 있는 fd_table 배열은, 현재 스레드에 의해 열린 파일들을 관리한다. 각 리스트 내 요소는 struct file을 가리키는 포인터이며, 스레드는 이 포인터를 이용해 해당 파일의 정보를 추적하게 된다.
syscall.c
파일명을 입력받아 파일을 open하는 시스템 콜이다.
open한 파일은 스레드의 fd_table에 추가되는데, 만약 fd table이 꽉 찼을 경우엔 파일을 닫는다.
파일을 성공적으로 open하고 fd table에 추가했을 경우엔 할당된 fd 번호를 리턴하며, 아닐 경우 -1을 리턴한다.
syscall.c
위 open 시스템 콜에서 사용된 add_file_to_fdt 함수이다.
스레드의 fd table에서 적절한 위치를 찾아 파일을 할당하고 fd number를 리턴한다.
thread.c
생략.....
thread_create 함수에서 표준입력, 표준출력에 대한 fd table을 미리 할당해 둔다.
유닉스 시스템에서는 0, 1, 2번이 각각 표준입력, 표준출력, 표준에러를 의미한다. 하지만 PintOS에서는 표준에러를 사용하지 않기 때문에 0, 1번에만 미리 값을 할당해 주면 된다.
[0]과 [1]에 넣은 값 1, 2는 특별한 의미가 있는 값은 아니다.
thread.h
위 함수들에서 사용된 매크로는 thread.h에 선언해 두면 된다.
이렇게 선언하고 사용함으로써, fd 번호는 최대 1535가 된다. ( 0 ~ 1535 )
syscall.c
filesize를 반환하는 시스템 콜이다.
find_file_by_fd 함수를 이용해 fd 기준으로 파일을 찾은 후, file_length 함수로 파일의 크기를 구하여 리턴한다.
syscall.c
find_file_by_fd 함수이다.
현재 스레드의 fd table에서 fd에 해당하는 파일을 리턴한다.
syscall.c
표준입력 또는 파일을 읽어오는 read 시스템 콜이다.
표준입력(0)일 경우 키보드 입력을 읽어오고 파일(2이상)일 경우 file_read 함수를 이용해 파일 내용을 읽어온다.
읽어온 값은 인자로 받은 buffer에 넣으며, 마지막엔 읽어온 바이트 크기를 리턴한다.
syscall.c
seek 시스템 콜이다.
파일을 읽거나 쓸 위치를 지정하는 역할을 한다. 기본적으로 파일을 읽거나 쓸 땐 파일의 시작 위치부터 시작하게 되는데, 이 위치를 seek를 통해 변경할 수 있다. fd값을 갖는 파일에 대한 시작 위치를 position 값으로 변경한다.
file_seek 함수를 이용하면 간단하다.
syscall.c
tell 시스템 콜이다.
위 seek 함수에서는 시작 위치를 변경해 주었다. tell은 현재 시작 위치를 리턴하는 함수이다.
마찬가지로 file_tell 함수를 이용하면 간단하다.
syscall.c
파일을 닫는 close 시스템 콜이다.
find_file_by_fd 함수로 fd에 해당하는 파일을 찾고, remove_file_from_fdt 함수로 fd table에서 파일을 지워준다.
마지막으로 file_close 함수로 파일을 닫아준다.
syscall.c
fd table에서 fd에 해당하는 파일을 지워주는 함수이다.
fd에 해당하는 자리를 NULL로 바꿔준다.
===========================
나머지 함수는 다음 글에서 이어서 작성하도록 하겠다.