시스템 호출의 일부로서 커널은 사용자 프로그램에 의해 제공되는 포인터를 통해 메모리에 접근해야 한다. 사용자가 null 포인터, 매핑되지 않은 가상 메모리에 대한 포인터 또는 커널 가상 주소 공간(KERN_BASE 위)에 대한 포인터를 전달할 수 있으므로 커널은 그렇게 하는 데 매우 주의해야 합니다. 이러한 모든 타입의 유효하지 않은 포인터들은 문제가 되는 프로세스를 종료하고 자원을 해제함으로써 커널이나 실행 중인 다른 프로세스에 해를 끼치지 않고 거부되어야 한다.
이 작업을 올바르게 수행하기 위한 최소 두 가지 합리적인 방법이 있습니다.
두 경우 모두 리소스를 "유출"하지 않도록 해야 합니다. 예를 들어, 시스템 호출이 잠금을 획득했거나 malloc()로 메모리를 할당했다고 가정합니다. 나중에 잘못된 사용자 포인터가 발견되더라도 잠금을 해제하거나 메모리 페이지를 해제해야 합니다.
사용자 포인터를 비참조하기 전에 확인하도록 선택한 경우 간단합니다. 잘못된 포인터로 인해 페이지 오류가 발생하면 메모리 액세스에서 오류 코드를 반환할 수 없기 때문에 처리하기가 더 어렵습니다. 따라서 후자의 기술을 사용하고자 하는 사람들을 위해 약간의 유용한 코드를 제공하겠습니다.
https://casys-kaist.github.io/pintos-kaist/project2/introduction.html
사용자 메모리 액세스 구현
syscall을 구현하려면 사용자 가상 주소 공간에서 데이터를 읽고 쓸 수 있는 법을 제공해야 한다.
당신은 arguments을 받을 때 이 능력이 필요하지 않습니다. 그러나 시스템 호출의 인수로 제공된 포인터에서 데이터를 읽을 때는 이 기능을 통해 프록시를 수행해야 합니다.
이것은 조금 까다로울 수 있다:
: 주소 값이 유저 영역에서 사용하는 주소 값인지 확인 하는 함수
유저 영역을 벗어난 영역일 경우 프로세스 종료(exit(-1))
User memory access
과제에서 해야하는 것은, 잘못된 메모리에 대한 접근을 막는 것이다. 즉, 1번에서 유저가 준 stack pointer의 위치가 잘못된 주소를 줬을 때이다.
-> /include/threads/vaddr.h
- 사용자가 잘못된 포인터 → Null Pointer이거나
- 커널 메모리에 대한 포인터 → Kernel VM을 가리키거나
( = KERN_BASE보다 큰 값일 때)- 그 영역들 중 하나에 부분적으로 블록을 제공한다면
⇒ ptov.. cpu에서 pt거쳐서 v받아옴
→ mapping 되지 않은 VM을 가르킨다면(할당되지 않은 vm주소에 접근하면)
⇒ 프로세스를 종료해야 한다. exit(-1)
이 3가지 경우에는 메모리에 참조할 수 없게 해야한다.
그래서 우리는 syscall.c에 check_address 함수를 만들어야 한다
이러한 경우 사용자 프로세스(user program을 실행중인 프로세스와 쓰레드)를 종료하여 처리해야 한다.
이 작업을 수행할 수 있는 두 가지 방법이 있다.
첫번째 방법은 유저가 넘긴 포인터를 검사한 뒤에 역참조 하는 것이다. 이 방법을 쓸 것이면 userprog/pagedir
의 함수와 include/threads/vaddr.h
를 봐라.
두번째 방법은 유저가 넘긴 포인터가 KERN_BASE
이전을 가리키는지만 확인하는 것이다. 잘못된 유저 포인터였다면 page fault
를 일으킬 것이고, 이는 page_fault()
로 수정할 수 있다. 이 방법은 MMU방식과 같기 때문에 빨리 수행할 수 있기 때문에 Linux같은 실제 커널에서 사용된다.
thread/mmu.c
의 (mmu : Memory Management Unit) [2] mmu
⇒ pml4
(Page Map Level 4) : 4단계 페이징 기법
void check_address(void* uaddr) {
struct thread *cur = thread_current();
if (uaddr == NULL || is_kernel_vaddr(uaddr) || pml4_get_page(cur->pml4, uaddr) == NULL) {
exit(-1);
}
}
what if the user provides an invalid pointer, a pointer into kernel memory, or a block partially in one of those regions?
3. 그 영역들 중 하나에 부분적으로 블록을 제공한다면 (a block partially in one of those regions)
→ mapping 되지 않은 VM을 가르킨다면(할당되지 않은 vm주소에 접근하면) ⇒ 프로세스를 종료 (exit(-1)
if (pte && (*pte & PTE_P))
return ptov (PTE_ADDR (*pte)) + pg_ofs (uaddr); —segmeent
return NULL;
.
핀토스의 시스템 콜은 intr_handler
함수를 통해 호출이 된다. 시스템 콜은 인터럽트의 한 종류이다. (내부 인터럽트를 일으키는 것이다.)
user program이 exec를 호출하면 바로 Kernel의 system call이 발동되는 게 아니라 lib/user/syscall.c에 있는 exec()가 호출되고, 이 함수는 define 되어 있는 syscall1을 부른다. 그 syscall1은 위에서 #define syscall1로 정의되어 있다.
그 #define syscall1을 잘 보면 int $0x30을 호출한다. 이 명령어를 통해 userprog/syscall.c에 있는 (kernel) syscall handler를 호출하고, esp를 통해서 내가 어떤 system call을 썼는지와 그 system call을 실행하는 데 필요한 매개변수를 전달한다.
f->esp가 명령어가 있는 주소를 가리킨다고 생각하면 된다. 가령 echo x를 했다면 현재 f->esp에는 'echo'가 있을 것이다.
: 주소 값이 유저 영역에서 사용하는 주소 값인지 확인 하는 함수
유저 영역을 벗어난 영역일 경우 프로세스 종료(exit(-1))
: 유저 스택에 있는 인자들을 커널에 저장(복사)하는 함수
유저 스택에 저장된 시스템 콜 넘버를 통해 해당 시스템 콜 함수를 호출
하도록 구현.
스택 포인터가 유저 영역인지 확인
저장된 인자 값이 포인터일 경우 유저 영역의 주소인지 확인
스택에서 시스템 콜 넘버 복사
시스템 콜 넘버에 따른 인자 복사 및 시스템 콜 호출
threads/init.c
에 있는 power_off 함수 사용Roha 개발 노트
bdbest https://velog.io/@bdbest72/pintOS-project2-User-Programs-system-call#threadh
자세함 https://for-development.tistory.com/19
실행 중인 파일에 쓰기 작업을 수행하면 예상하지 못한 결과를 얻을 수 있으니 이를 방지해야한다.
(ex:현재 fd=5인 파일을 읽고 있다고 가정할 때, 읽는 중에 파일의 아직 읽지 않은 아랫 부분이 변경되거나 하면 안된다 → 현재 load된 파일은 write를 할 수 없게 처리해줘야 한다. )
실행 파일로 사용 중인 파일에 대한 쓰기를 거부하는 코드를 추가한다.
대부분의 OS는 프로세스가 디스크에서 변경 중인 코드를 실행하려고 할 때 예측할 수 없는 결과를 얻기 때문에 이 작업을 수행한다. 이것은 프로젝트3에서 가상 메모리가 구현되면 특히 중요하지만, 지금은 문제 되지 않는다.
file_deny_write()
를 사용하여 열려 있는 파일에 write 하는 것을 방지할 수 있다. 파일에서 file_allow_write()
를 호출하면 (해당 파일이 다른 오프너에 의해 write가 거부되지 않는 한) 해당 파일이 다시 활성화된다. → 파일의 쓰는 작업을 막아준다.
파일을 닫으면 쓰기도 다시 활성화된다. 따라서 프로세스의 실행 파일에 대한 쓰기를 거부하려면 프로세스가 실행 중인 동안 열린 상태로 유지해야 한다.
load 후에 file_deny_write를 처리해주고, 마찬가지로 close를 하게되면 다시 풀어주면 된다.
sturct thread에 현재 running 중인 파일을 추가해주면 좋다. ( process.c → load() )
→ load 함수에 file_deny_write, t→running 추가해준다. 파일을 load에서 실행시키니까 load함수.
process_exit 에 running 추가, running관련은 헤더파일에 추가한다.
마지막으로 EXTRA 과제는 동일한 파일을 여러번 open하거나, 복사 (dup)을 하게 될 경우 어떻게 처리를 해야하나에 관한 문제이다. 현재 구현된 방식으로는 duplicate를 하거나 open을 한다면 계속해서 파일이 추가되기만 할 뿐이다. 즉 같은 파일이 fd 2 5 7 8 순으로 계속 생겨날 수 있는데, 우리가 원하는 것은 동일한 파일이 몇개인지 count하고 다른 fd지만 같은 file 구조체를 바라보게 하는 것이다!!
요지는 thread 구조체에는 stdin_count, stdout_count를 추가하고 (stdin, stdout이 여러개인지, 혹은 0이 되어버렸는지 확인하기 위함이다) file 구조체에 dup_Count를 추가한다. (이는 해당 파일이 몇개의 복제를 가지고 있는지 확인하는 것이다.) 예를 들면 dup_Count가 0이라면 우리가 했던 일반적인 경우이다. 때문에 Close를 할 경우 그냥 close를 하면 된다. 그러나 dup_Count > 0 이라면 복제된 파일이 존재하는 것이기 때문에 close가 호출돼도 파일을 닫으면 안된다!그래서 dup_Count -- 를 해주고, 이런 식으로 duplicate된 파일의 수를 control한다. [cc:깜냥블로그]
multi-oom FAIL은 Sync가 먼저 맞아야하고, 메모리누수있어서 생기는 fail.
malloc, palloc 등 사용 후 free되지 않은 부분이 있는 것임.
or
file exit하기 전에 열려있는 fd에 대해 다 close 해줘야 한다
[cc : https://medium.com/@minjw1026/pintos-2-2-cdcfb84bfdf]
.
syscall.c부터 process.c, thread.c 등등 온갖 파일의 palloc-free, file-close를 찾아보다가
syscall.c
파일의 void close()
안에 해당 코드를 추가해주고 해결하였다.
// extra 추가 -->> multi-oom PASS 해결!!!
struct thread *cur = thread_current();
if (fd == 0 || fileobj == STDIN){
cur->stdin_count--;
}
else if (fd == 1 || fileobj == STDOUT){
cur->stdout_count--;
}
// extra
에러 -> 시스템 콜 핸들러 가서 수정함.
thread_name이 함수인데 ()를 안붙였음. thread_name() 으로 수정함.
process.c 에서 process_create_inited에 *save_ptr
char형 추가해주고
file_name = strtok_r(file_name, " ", &save_ptr);
추가해줌.
[기술 문제 외]
[동료와 해결 외]