[PintOS] Project 2 - User Programs (2) User memory access, System Call

novxerim·2022년 1월 11일
4

SW-Jungle

목록 보기
39/59
post-custom-banner

Introduction

시스템 호출의 일부로서 커널은 사용자 프로그램에 의해 제공되는 포인터를 통해 메모리에 접근해야 한다. 사용자가 null 포인터, 매핑되지 않은 가상 메모리에 대한 포인터 또는 커널 가상 주소 공간(KERN_BASE 위)에 대한 포인터를 전달할 수 있으므로 커널은 그렇게 하는 데 매우 주의해야 합니다. 이러한 모든 타입의 유효하지 않은 포인터들은 문제가 되는 프로세스를 종료하고 자원을 해제함으로써 커널이나 실행 중인 다른 프로세스에 해를 끼치지 않고 거부되어야 한다.

이 작업을 올바르게 수행하기 위한 최소 두 가지 합리적인 방법이 있습니다.

  • 첫 번째 방법은 사용자가 제공한 포인터의 유효성을 확인한 후 참조 해제하는 것입니다. 이 경로를 선택하는 경우, sread/mmu.c 및 include/threads/vaddr.h의 함수를 살펴볼 수 있습니다. 이것이 사용자 메모리 액세스를 처리하는 가장 간단한 방법입니다.
  • 두 번째 방법은 사용자 포인터가 KERN_BASE 아래를 가리키는지 확인한 후 참조 해제하는 것이다. 잘못된 사용자 포인터는 userprog/exception.c의 page_fault() 코드를 수정하여 처리할 수 있는 "페이지 폴트"를 발생시킵니다. 이 기술은 일반적으로 프로세서의 MMU를 이용하기 때문에 더 빠르며, 따라서 실제 커널(리눅스 포함)에서 사용되는 경향이 있다.

두 경우 모두 리소스를 "유출"하지 않도록 해야 합니다. 예를 들어, 시스템 호출이 잠금을 획득했거나 malloc()로 메모리를 할당했다고 가정합니다. 나중에 잘못된 사용자 포인터가 발견되더라도 잠금을 해제하거나 메모리 페이지를 해제해야 합니다.

사용자 포인터를 비참조하기 전에 확인하도록 선택한 경우 간단합니다. 잘못된 포인터로 인해 페이지 오류가 발생하면 메모리 액세스에서 오류 코드를 반환할 수 없기 때문에 처리하기가 더 어렵습니다. 따라서 후자의 기술을 사용하고자 하는 사람들을 위해 약간의 유용한 코드를 제공하겠습니다.

https://casys-kaist.github.io/pintos-kaist/project2/introduction.html


User memory access

사용자 메모리 액세스 구현

syscall을 구현하려면 사용자 가상 주소 공간에서 데이터를 읽고 쓸 수 있는 법을 제공해야 한다.

당신은 arguments을 받을 때 이 능력이 필요하지 않습니다. 그러나 시스템 호출의 인수로 제공된 포인터에서 데이터를 읽을 때는 이 기능을 통해 프록시를 수행해야 합니다.

이것은 조금 까다로울 수 있다:

void check_address(void *addr)

: 주소 값이 유저 영역에서 사용하는 주소 값인지 확인 하는 함수
유저 영역을 벗어난 영역일 경우 프로세스 종료(exit(-1))

User memory access 과제에서 해야하는 것은, 잘못된 메모리에 대한 접근을 막는 것이다. 즉, 1번에서 유저가 준 stack pointer의 위치가 잘못된 주소를 줬을 때이다.

-> /include/threads/vaddr.h

  1. 사용자가 잘못된 포인터 → Null Pointer이거나
  2. 커널 메모리에 대한 포인터 → Kernel VM을 가리키거나
    ( = KERN_BASE보다 큰 값일 때)
  3. 그 영역들 중 하나에 부분적으로 블록을 제공한다면
    ⇒ ptov.. cpu에서 pt거쳐서 v받아옴
    → mapping 되지 않은 VM을 가르킨다면(할당되지 않은 vm주소에 접근하면)

⇒ 프로세스를 종료해야 한다. exit(-1)
이 3가지 경우에는 메모리에 참조할 수 없게 해야한다.
그래서 우리는 syscall.c에 check_address 함수를 만들어야 한다

이러한 경우 사용자 프로세스(user program을 실행중인 프로세스와 쓰레드)를 종료하여 처리해야 한다.

해결하기

이 작업을 수행할 수 있는 두 가지 방법이 있다.

  1. 첫번째 방법은 유저가 넘긴 포인터를 검사한 뒤에 역참조 하는 것이다. 이 방법을 쓸 것이면 userprog/pagedir의 함수와 include/threads/vaddr.h를 봐라.

  2. 두번째 방법은 유저가 넘긴 포인터가 KERN_BASE 이전을 가리키는지만 확인하는 것이다. 잘못된 유저 포인터였다면 page fault를 일으킬 것이고, 이는 page_fault()로 수정할 수 있다. 이 방법은 MMU방식과 같기 때문에 빨리 수행할 수 있기 때문에 Linux같은 실제 커널에서 사용된다.

추가

thread/mmu.c 의 (mmu : Memory Management Unit) [2] mmu

pml4(Page Map Level 4) : 4단계 페이징 기법

https://sean.tistory.com/146

https://rohagru.notion.site/981fb071c485438382fbcc88573398cf?v=0547332b4c4046f49373c5dae42202e4&p=accc6862a66a42fcaa3e77b0ec28beb4


코드

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;

System Call

구조

  • 시스템 콜은 운영 체제가 제공하는 서비스에 대한 프로그래밍 인터페이스
  • 사용자 모드 프로그램이 커널 기능을 사용할 수 있도록 함
  • 시스템 콜은 커널 모드에서 실행되고, 처리 후 사용자 모드로 복귀됨
  • 시스템 콜의 핵심은 시스템 콜 호출 시, 하드웨어 인터럽트가 발생하여 실행모드의 우선순위가 특수모드로 상향조정 되는 것임

시스템 콜 호출 과정

리눅스


.

핀토스

핀토스의 시스템 콜은 intr_handler 함수를 통해 호출이 된다. 시스템 콜은 인터럽트의 한 종류이다. (내부 인터럽트를 일으키는 것이다.)


개요

과제

  • 과제 목표
    - 시스템 콜 핸들러 및 시스템 콜 (halt, exit, create, remove 등) 구현
  • 수정 파일
    - pintos/src/userprog/syscall.*
  • 과제 설명
    • Pintos는 시스템 콜 핸들러가 구현되어 있지 않아 시스템 콜이 호출될 수 없으므로 응
      용 프로그램이 정상적으로 동작하지 않는다.
    • Pintos의 시스템 콜 메커니즘을 이해하고 시스템 콜 핸들러를 구현한다.
    • 시스템 콜을 구현하고 시스템 콜 핸들러를 통해 호출
      한다.

System Call 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'가 있을 것이다.


함수 구현

void check_address(void *addr) (User memory access에서 구현 완료)

: 주소 값이 유저 영역에서 사용하는 주소 값인지 확인 하는 함수
유저 영역을 벗어난 영역일 경우 프로세스 종료(exit(-1))

void get_argument(void esp, int arg, int count)

: 유저 스택에 있는 인자들을 커널에 저장(복사)하는 함수

  • 스택 포인터를 참조하여 count(인자의 개수)만큼 스택에 저장된 인자들(데이터)을 4byte크기로 꺼내어 arg 배열에 순차적으로 저장(복사)
    • 인자가 저장된 위치(스택)가 유저 영역인지 확인 (check_address)

syscall_handler()

유저 스택에 저장된 시스템 콜 넘버를 통해 해당 시스템 콜 함수를 호출
하도록 구현.

  1. 스택 포인터가 유저 영역인지 확인

    저장된 인자 값이 포인터일 경우 유저 영역의 주소인지 확인

  2. 스택에서 시스템 콜 넘버 복사

  3. 시스템 콜 넘버에 따른 인자 복사 및 시스템 콜 호출

  • 구현
    • devices/shutdown.h 파일 존재하지 않음 (shotdown_power_off 존재X) → threads/init.c 에 있는 power_off 함수 사용

답안 github 모음

참고 블로그

Roha 개발 노트

bdbest https://velog.io/@bdbest72/pintOS-project2-User-Programs-system-call#threadh

답수님 https://dapsu-startup.tistory.com/entry/pintOS-Project-2-시스템콜-구현-fork-wait-read-write-seek-tell-close?category=515638

자세함 https://for-development.tistory.com/19


Denying Writes to Executables

실행 중인 파일에 쓰기 작업을 수행하면 예상하지 못한 결과를 얻을 수 있으니 이를 방지해야한다.

(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관련은 헤더파일에 추가한다.


Extend File Descriptor (Extra)

마지막으로 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:깜냥블로그]


Issues etc.

multi-oom FAIL → PASS !!

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

  • wait()은 현재 process_wait으로 구현했는데, wait()따로 만들어서 연결해도 될 듯. PPT, 승환오빠코드처럼.
  • load(process.c) - thread를 하나 만들어서 사용자 프로그램 (동적) 실행함 [User Space]
    exit - thread_exit - sema up [Kernel Space]

args-none 에러

에러 -> 시스템 콜 핸들러 가서 수정함.

thread_name이 함수인데 ()를 안붙였음. thread_name() 으로 수정함.


arg_single 에러

process.c 에서 process_create_inited에 *save_ptr char형 추가해주고
file_name = strtok_r(file_name, " ", &save_ptr); 추가해줌.


WIL 발표내용 (문제점 - 해결)

[기술 문제 외]

  • 시스템콜 실질적으로 어떻게 구현되는지가 어려웠음 → 인텔 메뉴얼을 보며 좀 더 깊게 이해해보려 노력함
  • 어셈블리어 중요하다 생각 듦 → 어셈블리어 더 공부해봤음.
  • 마지막 머지시 에러 깃 미숙. → 시행착오 겪고 확실히 알게 됨
  • 특정 부분 에러를 고치면 다른 부분에서 에러가 남.. → 문제 해결하려 계속 노력함. (일부 팀 해당 에러 의견 : overflow 문제였던 것 같음)
  • physical memory - virtual memory 추상화 과정 파악 어려웠음

[동료와 해결 외]

  • 시스템콜하면서 팀원과 똑같은 코드를 썼는데 디버깅하다보니 갑자기 파일시스템 안까지 들어가서 에러남 → 주변에 먼저 이해한 동료의 도움을 받음
  • 팀원 일부는 이론/흐름부터, 일부는 코드구현부터. 둘 다 잡고 싶어도 시간 부족 → 팀활동으로 밸런스 맞추려고 함
  • 명확하지 않고 불확실한 지식들 → 동기들과 함께 스터디하며 해결
  • 체력적인 문제 → 팀원들과 으쌰으쌰. 남은 날들을 위해 건강을 더 대비하려 함
profile
블로그 이전했습니다. https://yerimi11.tistory.com/
post-custom-banner

0개의 댓글