[PintOS] 2-2 System Call
2024. 6. 2. 16:58ㆍ크래프톤 정글 5기/공부
GIT
[process.c, process.h, syscall.c, syscall.h, thread.c, thread.h, exception.c]
영상 출처 : https://www.youtube.com/watch?v=sBFJwVeAwEk
- 현재 pintOS에는 system call handler 부분이 비어있음
- handler 구현 이후, system call 동작도 구현해야 함
- System Call : OS에서 제공하는 서비스를 위한 programming Interface
User mode program이 Kernel 기능을 사용할 수 있게 해줌
- System Call은 kernel mode에서 실행되고, 다시 user mode로 전환시킴
- syscall3 : 3개의 parameter가 존재하는 시스템콜이라는 뜻
- arg 2, 1, 0 (3개의 parameter) pushing
- pushing parameter number
- 0x30 (system call 나타내는 Interrupt number)
# TODO : 0x30을 전달받는 syscall_handler() 함수 비어있으니 고쳐야 함 !
- 각 systemcall number에 대한 내용은 syscall_nr.h 파일에 존재
- system call handler가 system call number를 사용하도록 / system call 호출하게 만들기
- User program이 호출한 system call이 유효한 주소를 입력받았는지 체크해야 함
- user program이 전달한 주소는 "User area" 이내로 한정되어야 함
- 유효한 주소가 아니라면, page fault가 발생되어야 함
- 주소 유효성 검사가 완료되면, User stack에 있는 arguments를 kernel stack으로
복사해야 함
- system call의 return value를 eas 레지스터에 저장해야 함
** Important thing
1. User는 Kernel에 유효한 User address를 제공해야 함
2. Kernel은 실행될 때 User area에 Access하면 안됨
3. User area에 존재하던 arguments들을 무조건 kernel로 옮겨와야 함
-> kernel이 User area로 접근할 일을 만들면 안됨
- 사용자가 systemcall 호출 시 '유효하지 않은 주소'를 전달할 경우 :
OS는 process를 종료시켜야 함(Kernel or 다른 process에 데미지를 주지 않게)
system call handler가 유효성을 검사할 수 있어야 함
- 유효성 검증 2가지 방법
1. User가 제공한 주소의 유효성을 검사하는 것
Page table로 확인하는 것 + 주어진 주소가 Mapping 되었는지 확인하는 것
pagedir.c / vaddr.h 파일에서 함수 사용해라
2. PHYS_BASE (코드에서는 USER_STACK) 이내의 주소값인지 확인하는 것
- process는 lock을 보유하거나 malloc을 요청함
- page fault가 발생하면, global value인 lock은 process에 의해 보류됨
- page fault가 발생하면, memory allocate는 받았지만 free하지는 못함
--> Page fault가 발생하면 resource leak 발생 가능
- 따라서 process exit 이전에 [lock release] [memory free] 동작이 필요함
유효성 검증 방법 1) : address 유효성을 확인한 이후에 [lock, malloc] 동작 이루어짐
유효성 검증 방법 2) : memory access에서 error code 반환할 방법이 없어서 어려움
- 유효성 검증 방법 2)의 문제를 해결하기 위한 2가지 함수가 있음
1) get_user
: 지정된 주소에서 1바이트를 읽음
2) put_user
: 사용자가 지정한 주소에 1바이트를 씀
1. halt : OS 종료함 / pintOS는 명시적으로만 종료되어야 함
2. exit : process 종료함 / process 종료 시 [process name + process status] 출력함
3. exec : 자식 process 생성한 후 프로그램을 실행 (UNIX의 exec 함수와 다름)
fork() + exec() 함수의 동작을 합친 것
4. wait : 입력받은 pid인 process의 종료를 기다림
- 계층구조를 형성해야 함
- 부모 / 자식 관계 + 형제 관계를 추상화하여 포인터로 지정해야 함
-> thread 구조체에 변화가 있어야 함
❗️ 부모가 모든 자식에 대한 포인터를 가지고 있어야 하는 게 아님
- 자식 process인 pid가 종료되기를 대기하고, 자식 process의 exit status를 검색
- 자식 process가 살아있다면, 종료될 때까지 기다림 / 종료되면 exit status 반환함
- 자식 process가 kernel에 의해 종료되면, -1 반환
- 이미 종료된 자식 process에 대해서도 wait 함수를 호출할 수 있음
- 자식 process가 종료된 이후, 부모 process는 해당 process descriptor 할당 해제해야 함
#TODO : return -1로 되어있는 부분을 발전된 형태로 구현해야 함
- 'child_tid' parameter 이용해서 child process의 descriptor 검색
- process_wait() 함수 호출자는 [child process 종료] 까지 block 되어야 함
- chile process가 종료되면, descriptor 할당 해제 및 child process의 exit status를 반환함
- 부모 process가 자식 process를 기다리고, 자식 process 간 동기화를 시켜줘야 함
- struct thread 구조체 안에 'wait을 위한 semaphore' 추가해야 함
- 해당 semaphore는 처음 생성될 때 0으로 초기화 되야 함
- wait()에서 sema down / exit()에서 sema up 해야 함
- struct thread 구조체 안에 'exit status 기록'할 수 있는 Field 추가해야 함
- process_wait() 안에서 sema_down()
- thread_exit() 안에서 sema_up()
- sema_down() 하면 호출자(부모 process)는 자식 process 종료까지 block됨
- sema_up() 하면 semaphore 값이 증가하길 기다리던 process들 모두 unblock됨
- pintOS에서 존재하는 exec()는 fork() + exec() (UNIX)
Function :
- thread 생성 + binary 파일 실행
- command line을 인자로 받고, command line을 실행하는 프로그램을 실행함
- 생성된 새로운 process의 pid를 반환함
- exec() 호출자(부모 process)는 자식 process가 동작을 완료할 때까지 대기함
-> 이건 exec system core가 할 일
- exec system core는 '실행 파일 생성 & load' 이후 반환됨
- 부모 process는 자식 process의 성공적 생성 여부 / binary file load의 성공 여부를 알아야 함
- 부모 process와 자식 process를 semaphore 사용으로 동기화 할거임
- struct thread 구조체에 'exec()을 위한 semaphore' 추가함
- 해당 semaphore value는 생성될 때 0으로 초기화
- 자식 process의 실행파일이 성공적으로 load 될때까지 기다리기 위해 sema_down 함
- 자식 process의 실행파일이 성공적으로 load 되면, sema_up 함
- struct thread 구조체에 '파일이 성공적으로 load 되었는지 여부' 확인을 위한 Field 필요
- 부모 process가 process_execute() 호출
- process_execute() 안에 있는 start_process() 호출 -> 자식 process 생성
- 자식 process는 thread를 생성하고 파일을 load 함
- 파일 load가 완료되면 sema_down() 호출
- sema_down() 하게되어 [부모 process + 자식 process] 병렬적 동작이 가능함
- sema_down()을 어디에 위치시켜야 하나 ? #TODO
- argument process의 종료 상태를 반환해야 함
- process가 exit()을 호출하면, OS는 current user program 종료 후 상태를 kernel에 반환함
- 부모 process가 wait 중이라면, user program 종료 후 상태를 반환함
- 종료 상태를 저장하는 동작을 추가해야 함
- 종료 상태와 thread 이름을 출력해야 함
- 이후 thread_exit() 호출
- thread의 exit status를 process status에 저장해야 함
- sema_up() 호출하여, 해당 thread 종료를 기다리던 process가 동작할 수 있게 함
- thread 목록을 조작할 때마다 intr_disable() 해줘야 함 !!!!
What I did
- user program에서 syscall 호출 시 전달하는 ‘address’ 유효성 검사를 1번 방식으로 하도록 결정
- 1번은 [syscall_handler() 함수 내에서, 각 syscall이 호출될 때마다 유효성 검사] 하는 동작
- mmu.c 파일 내의 ‘pml4_get_page’ 함수 + vaddr.h 파일 내의 ‘is_user_vadder) 사용하면 됨
1. method 1으로 address valid check 동작 추가하기
함수를 새로 만들거임 bool address_checker(void *address)
여기서 걸어줄 조건문은 [address가 NULL인지] [pml4_get_page 반환값이 NULL인지]
pml4_get_page() 진입 시 (is_user_vaddr) 여부를 확인함
이후 pml4e_walk를 통해서 입력된 virtual address를 테이블을 타고 타고 타고 타고 들어가서
PTE에 있는 physical address 가져옴
해당 동작을 했는데 NULL이 뜬거면, thread_exit 시키면 될 듯
2. syscall_handler 수정하기
기존 skeleton code에는 그냥 printf만 찍힘
입력으로 받아오는 interrupt frame에서 Register -> rax에서 syscall_number 받아옴
switch - case로 구분해서 [유효성 검사] + [syscall 동작 구현]해야 함
여기서 중요한 게, 유효성 검사를 하려면 [해당 syscall이 주소값(buffer) 전달하는지 여부가 중요]
syscall마다 전달하는 인자가 다름 (주소 안쓰는 애들도 있음)
그래서 syscall case마다 검사를 다 따로 달아줘야 함
-> 그래서 영상에서 '2번 방법은 코드의 간결성을 유지할 수 있다는 장점이 있다' 라고 한듯 ?
case로 들어가서, 인자를 가져오는 동작을 하려면 f->R로 들어가서 값을 가져와야 함
rdi / rsi / rdx / r10 / r8 / r9 순서
3. exit 구현
1) struct thread 구조체에 int exit_code 추가
2) case : exit 추가
3) thread_exit -> process_exit 내부에 printf ("%s: exit(%d)\n", ...); 추가
4) printf ("%s: exit(%d)\n", curr->name, curr->exit_code);
4. process_exit() 에서 printf 찍던거 exit() 안으로 옮김
0. 나랑 재혁님 서로 pass 뜨는게 다르지만, syscall.c 파일의 차이라고 판단하고 분업함
0523 2023 기준 git에 올린 파일(=수정된 파일) [thread.c / thread.h / process.c / syscall.c]
1. syscall 부르는 형식 바꿈 (switch - case 안에서 동작하지 않고, 함수 밖으로 빼서 호출하는 식)
2. fd 함수 하나 만들고, thread.h에서 [struct thread에 필드 하나 추가] [FDT_MAX 선언]
... 생략 ...
코드 수정 -> 이전 테스트 터짐 -> 코드 수정 복원 실패 -> 재도전 -> 코드 수정 -> 이전 테스트 터짐 -> ...
fork(), wait(), exec() 함수 이외는 모두 어찌저찌 성공
1. thread.h : thread 구조체 수정
struct file* fdt[FDT_MAX]; // 부모 FD
struct list_elem child_elem; // 자식 list elem
struct list child_set; // 자식 list
int next_fd; // 다음 fd 값
int exit_code; // 종료 status
bool loaded; // 적재 유무
bool exited; // 종료 유무
struct semaphore exit_sema; // exit semaphore
struct semaphore load_sema; // laod semaphore
struct thread *parent; // 부모 thread 포인터
2. init_thread : list_init(&t->child_set) 추가
3. thread_create :
/* customed 0524 */
t->parent = thread_current();
sema_init(&t->exit_sema, 0);
sema_init(&t->load_sema, 0);
t->loaded = false;
t->exited = false;
list_push_back(&thread_current()->child_set, &t->child_elem);
4. process_fork() -> __do_fork() -> duplicate_pte()
1. process_fork에서 __do_fork 동작할 자식 thread 생성
2. sema_down 호출 -> 자식 process load까지 대기
3. 자식 process는 __do_fork 동작 수행
4. 부모의 if_ 복제 / 자식 PTE 생성 / 부모 PTE 복제 / 부모 FDT 복제 / 부모 File 복제
5. 복제 다 했으면 sema 올리고 종료
📎 함수들 몇개
더보기
tid_t
process_fork (const char *name, struct intr_frame *if_) {
/* Clone current thread to new thread.*/
struct thread *parent = thread_current();
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, parent); // 현재 thread 복제하여 새로운 thread 생성
if (tid == TID_ERROR) // thread_create 동작이 완료되지 않으면, TID_ERROR 출력
{
return TID_ERROR;
}
struct thread *child = get_thread(tid); // all_list에서 자식 tid로 자식 thread 가져옴
sema_down(&child->load_sema); // 자식 process load 완료까지 대기
if (child->loaded == false) // 자식 process가 load되지 않으면 에러 반환
{
return TID_ERROR;
}
return tid;
}
static void
__do_fork (void *aux) {
struct intr_frame if_;
struct thread *parent = (struct thread *) aux;
struct thread *current = thread_current ();
struct intr_frame *parent_if = &parent->tf; // 부모의 interrupt frame 가져오기
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame)); // 부모 process의 interrupt frame 자식 process에게 복사
/* 2. Duplicate PT */
current->pml4 = pml4_create(); // 자식 process의 page table 생성
if (current->pml4 == NULL)
goto error;
process_activate (current);
#ifdef VM
supplemental_page_table_init (¤t->spt);
if (!supplemental_page_table_copy (¤t->spt, &parent->spt))
goto error;
#else
// pml4 전체를 순회하면서 각 유효한 PTE에 대해 PTE 복제하는 함수 호출
// return value가 false라면 error
if (!pml4_for_each (parent->pml4, duplicate_pte, parent))
goto error;
#endif
/* TODO: Your code goes here.
* TODO: Hint) To duplicate the file object, use `file_duplicate`
* TODO: in include/filesys/file.h. Note that parent should not return
* TODO: from the fork() until this function successfully duplicates
* TODO: the resources of parent.*/
if (parent->next_fd != FDT_MAX)
{
// 부모 process의 FDT -> 자식 process의 FDT 복제
for (int i = 2; i < parent->next_fd; i++)
{
if (parent->fdt[i] != NULL)
{
// 부모 process의 파일 -> 자식 process의 파일 복제
struct file *duplicate_file = file_duplicate(parent->fdt[i]);
if (duplicate_file == NULL)
{
succ = false;
break;
}
current->fdt[i] = duplicate_file;
}
}
current->next_fd = parent->next_fd;
}
else
{
succ = false;
}
// load 동작 완료 표시
sema_up(¤t->load_sema);
current->loaded = true;
/* Finally, switch to the newly created process. */
if (succ)
do_iret (&if_); // 자식 process 시작
error:
current->loaded = false;
sema_up(¤t->load_sema);
thread_exit (); // 자식 process 종료
}
static bool
duplicate_pte (uint64_t *pte, void *va, void *aux) {
struct thread *current = thread_current ();
struct thread *parent = (struct thread *) aux;
void *parent_page;
void *newpage;
bool writable;
/* 1. TODO: If the parent_page is kernel page, then return immediately. */
// va가 커널영역에 위치하면 return true
if (is_kernel_vaddr(va))
{
return true;
}
/* 2. Resolve VA from the parent's page map level 4. */
// 부모 page table에서 va에 대한 pa 반환
parent_page = pml4_get_page (parent->pml4, va);
/* 3. TODO: Allocate new PAL_USER page for the child and set result to NEWPAGE. */
// 자녀 process page 할당
newpage = palloc_get_page(PAL_USER);
/* 4. TODO: Duplicate parent's page to the new page and
* TODO: check whether parent's page is writable or not (set WRITABLE
* TODO: according to the result). */
memcpy(newpage, parent_page, PGSIZE); // 부모 page 내용을 자식 page에 복사
writable = is_writable(pte); // 쓰기 동작 가능 여부 저장
/* 5. Add new page to child's page table at address VA with WRITABLE
* permission. */
if (!pml4_set_page (current->pml4, va, newpage, writable)) { // 자식 process page table에 새 페이지 추가
/* 6. TODO: if fail to insert page, do error handling. */
palloc_free_page(newpage); // 실패할 경우 할당 페이지 해제
return false;
}
return true;
}
// pml4 전체를 순회하면서 각 유효한 PTE에 대해 주어진 func 함수를 호출
bool
pml4_for_each (uint64_t *pml4, pte_for_each_func *func, void *aux) {
// pml4에 있는 모든 entry 순회)
for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) {
// 현재 pml4 entry에 해당하는 PDPE(Page Directory Point Entry) 가져옴
// ptov : physical address -> kernel virtual address translate
uint64_t *pdpe = ptov((uint64_t *) pml4[i]);
// PDPE가 유효한지 확인 / PTE_P 비트가 설정되어 있으면 유효하다고 판단
if (((uint64_t) pdpe) & PTE_P)
// pdf_for_each : 현재 PDPE에 매핑된 모든 PD(Page Directory) 순회
// 순회하면서 func 함수 호출하는데, return value가 false라면 false return
if (!pdp_for_each ((uint64_t *) PTE_ADDR (pdpe), func, aux, i))
return false;
}
return true;
}
동기화 동작 추가 -> 이전 테스트 터짐 -> 코드 복원 실패 -> 재도전 -> 동기화 동작 수정 -> 이전 테스트 더터짐 -> ...
fork, wait, exec 동작 추가 직전 git에 올려둔 코드로 복원 후 다시 진행
1. thread struct 수정
struct thread *parent; // 부모 thread 포인터
struct list child_set; // 자식 list
struct list_elem child_elem; // 자식 list elem
int exit_code; // 종료 status
bool is_dead;
struct semaphore wait_sema; // wait semaphore
struct semaphore load_sema;
2. thread_create 수정
list_pushback(&thread_current()->child_set, &t->child_elem)
3. thread_exit 수정
list_remove(&thread_current()->a_elem);
if (thread_current()->parent != NULL)
list_remove(&thread_current()->child_elem);
thread_current()->is_dead = true;
// palloc_free_page(thread_current());
sema_up(&thread_current()->wait_sema);
do_schedule (THREAD_DYING);
NOT_REACHED ();
4. init_thread 수정
for (int i = 2; i < FDT_MAX; i++)
{
t->fdt[i] = NULL;
}
t->next_fd = 2;
t->is_dead = false;
t->parent = NULL;
list_init(&t->child_set);
sema_init(&t->wait_sema, 0);
sema_init(&t->load_sema, 0);
t->magic = THREAD_MAGIC;
-------- 여기까지는 fix해도 되고, process 수정하면 다터지는 현상 발생 --------
5. process_wait 수정
int process_wait (tid_t child_tid UNUSED) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
/* customed 0523 */
struct thread* child = get_child_process(child_tid);
if (child == NULL)
return -1;
if (child->is_dead)
return -1;
sema_down(&child->wait_sema);
if (child->is_dead)
return child->exit_code;
return -1;
}
-------- 여기까지는 fix해도 되고, fork ㄱㄱ --------
6. exception -> page_fault 수정
if (not_present || write || user)
{
exit(-1);
}
22개 fail 남아서, 일단 commit
1. sema_init(load_sema)를 다시 init_thread 에서 처리
2. process_fork 함수에서 sema_down 밑줄 / return tid 윗줄에 printf 찍어봄
-> sema down도 잘 되고 sema up도 잘 되는듯 ? + tid도 생성이 되는듯 ? (4로찍힘)
3. printf 찍으면 thread_current에서 ASSERT 걸림 (status 확인 과정에서)
-> 주석처리하면 잘찍힘
4. 준혁이형 코드랑 최대한 비슷하게 ㄱㄱ
1) thread struct에서 copied_if 추가
2) tf 주지 말고 copied_if 전달
3) file_deny, filesys_lock 제거
1. fork-multiple 이새끼 make check 하면 exit status 5 떠야 할 때 -1 뜨면서 틀리는데
개별실행 돌리면 정답 그대로 출력됨 뭐하는새끼임 ?
2. 갑자기 args 테스트들이 진행 안될 때가 있는데, 그냥 make clean 하고 다시 make check 하면 됨
3. fork-multiple / multi-recurse 둘이 되다 안되다 함
4. 확정적으로 안되는 건 rox 3문제 + syn-read + syn-write + multi-oom
1. filesys_lock 사용함
file_read / file_write 할때 앞뒤로
2. file_deny 사용함
open 할때 / close 할때 allow
3. syscall.c 에서 case / switch 들어갈 때, exec에서
break; 안걸어줌 레전드사건
4. multi-recurse + syn-read 과정에서 계속 backtrace 해보니
exec에서 뭔가 문제가 발생한 것 같음
1. file_deny 해놓고 돌리니까 5 fail 뜸
2. filesys lock 걸고 다시 ㄱㄱ
3. exit_sema라는 semaphore를 process_wait에서 down, process_exit에서 up
4. process_exit 할 때 file resource 초기화하는 과정이 없다 .?
sync-read 테스트 통과를 해결하지 못하고,
multi-oom 테스트 트라이 중이던 재혁님 코드를 이식해와서 마지막 문제 해결을 해보려 했으나
실패 ..
'크래프톤 정글 5기 > 공부' 카테고리의 다른 글
[PintOS] Project 3 키워드 정리 (File-backed page, Direct Memory Access) (1) | 2024.06.02 |
---|---|
[PintOS] 2-1 Arguments passing (0) | 2024.06.02 |
[PintOS] Project 2 키워드 정리 (User mode / Kernel mode, System Call, File descriptor, Atomic operation, Interrupt) (0) | 2024.05.20 |
[PintOS] 1-3 Advanced Scheduler (0) | 2024.05.18 |
[PintOS] 1-2 Priority Scheduler, Priority Donation (0) | 2024.05.18 |