본문 바로가기
정글/pintos

[Project2] User Program(1) -Background

by 위대한초밥V 2023. 6. 4.

Part2: User Programs

  • 목표: 사용자 프로그램과 커널 프로그램을 시스템 콜을 통해서 소통하여 돌아가도록 한다.
  • 주의할 점
    • userprog에서 작업을 시작해라.
    • project1을 모두 끝내고 시작해라.
    • #ifdef VM 안에 코드가 추가되면 안된다. → Project3의Virtual Memory Subsystem을 활성화하고 시작된다.
  • Topic
    • Parameter Passing
    • System Call infrastructure
    • File manipulation

Background

Project1에서 진행한 과정은 실행한 코드가 운영 체제 커널의 일부이다. 지난 과제에서 테스트 코드는 커널의 일부로 동작한 것으로, system의 권한 부분까지 접근할 수 있다. 만약 User Program을 OS에서 실행한다면 이 방식은 가능하지 않다. 이번 과제는 User Program을 돌리는 상황을 학습한다.

Pintos는 한번에 하나의 프로세스가 동작하며, 각각의 프로세스는 하나의 스레드를 갖는다.(멀티 스레드는 지원 X) User Program은 하나의 PC를 지니고 있다는 착각을 하고 있다. 따라서 한번에 여러 프로세스를 load하고 run할 때, 메모리, 스케쥴링 및 기타 상태를 올바르게 관리해야 한다.

이전 프로젝트에서 테스트 코드를 직접 컴파일하는 방식으로 진행하여, 커널 코드와 함수 인터페이스가 필요했다. 이번 과제부터는 User Program을 실행하여 운영체제를 테스트하므로, 사용자 프로그램 인터페이스만 만족한다면 커널 코드를 자유롭게 수정할 수 있다.

더 읽어봐야할 키워드

  • synchronization
  • virtual address

Source Files(userprog/)

  • process.c , process.h
  • syscall.c, syscall.h
    • 커널 기능에 접근할 때 시스템 콜을 호출한다.
  • syscall-entry.S
    • assembly 코드. system call handler를 불러온다. → 내용을 꼭 이해할 필요 없다.
  • exception.c, exception.h
    • user process가 권한이 있거나 금지된 작업을 하면 예외 또는 오류가 커널에 exception 또는 fault 로 트래핑된다. 이 파일은 예외를 처리한다. 대부분 예외는 단순히 메시지를 출력하고 종료한다. 프로젝트 2의 일부 과제는 파일에서 page_fault()를 수정해야한다.
  • gdt.c , gdt.h
    • x86-64 세그먼트 아키텍처이다. 이 파일은 GDT(Global Descriptor Table)를 설정한다. GDT는 세그먼트를 설명한다. → 파일을 수정하면 안된다.
  • tss.c, tss.h
    • Task-State Segment(TSS)는 x86-64에서 사용된다. x86-64에서 작업 전환이 더 이상 사용되지 않지만, 전환 중에 스택 포인터를 찾기 위해 TSS가 존재한다. → 파일은 수정하면 안된다.

Using the File System

filesys 경로에 위치한다. 이 부분은 수정할 필요 없다.

User program이 file system으로부터 로딩되기 때문에 file system을 다루는 것이 필요하다. 하지만 이 프로젝트는 file system에 초점을 둔 것이 아니므로, filesys 디렉토리에 간단하지만 완벽한 file system을 추가했다. file system 사용 방법과, 제한 사항을 이해하려면 filesys.hfile.h 인터페이스를 살펴보면 좋다.

모든 test 프로그램이 커널 이미지에 있기 때문에, user space에서 동작하는 테스트 이미지를 Pintos 가상 머신에 넣어야 한다.

  1. file system partition으로 simulated disk를 생성한다.⇒ 2MB 크기의 Pintos file system partition을 가진 filesys.dsk 라는 simulated disk를 생성한다.
    • userprog/build 폴더에서 pintos-mdisk filesys.dsk 2 를 실행한다.
  2. disk를 특정한다.
    • `pintos --fs-disk filesys.disk -- KERNEL\_COMMANDS ...`
    • \-f -q 를 사용하여 파일 시스템 파티션을 포맷한다.
      • \-f : 파일 시스템을 포맷한다.
      • \-q : 포맷이 완료되면 즉시 Pintos를 종료한다.
  3. 파일을 Pintos 파일 시스템으로 복사한다.
    • pintos -p file -- -q
    • \-p : put , VM에서 파일을 복사하는 명령은 비슷하지만 -g를 사용한다.

 

요약해보자. 다음 shell은 아래의 두 동작을 시행한다.

  • 10MB 크기의 filesys.dsk를 만든다.
  • 파일 시스템을 포맷하고, userprog/args-single 프로그램을 새 디스크에 복사하고, 인자 onearg 를 전달하여 실행한다.
pintos-mkdisk filesys.dsk 10
pintos --fs-disk filesys.dsk -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

파일 시스템을 따로 보관하고 싶지 않다면, 핀토스 실행 기긴동안만 시스템 파이션을 생성하는 명령어를 사용하면 된다.

pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

How User Program Work

메모리 사이즈가 맞고, 사용자가 구현한 시스템 호출만 사용하는 일반 C 프로그램을 실행할 수 있다. 다만 메모리 할당을 허용하는 시스템 호출이 없어 malloc() 을 구현할 수 없다. 스레드를 전환할 때, 커널이 프로세서의 부동 소수점 단위를 저장/복원하지 않아 부동 소수점 연산을 사용하는 프로그램을 실행할 수 없다.


Virtual Memory Layout

  • 핀토스에서 가상 메모리는 아래의 두 영역으로 구분된다.
    • user virtual memory
    • kernel virtual memory

User virtual memory는 0 ~ KERN_BASE 까지다. include/threads/vaddr.h 안에 있다. Default 값은 0x8004000000 으로, Kernel Virtual Memory가 virtual address의 나머지 부분을 지닌다.

User Virtual memory는 프로세스 단위이다. 다른 프로세스로 전환될 때 page directory base register를 변경하여, 가상 주소 공간도 전환된다.

 

Kernel Virtual Memory는 전역(global)이다. 실행 중인 user process와 kernel thread에 상관없이 동일한 방식으로 맵핑된다. user program은 자신의 user virtual memory에만 접근 가능하다.

User program은 User Virtual memory에만 접근할 수 있다. 만약 Kernel Virtual Memory에 접근하려고 하면, userprog/exception.c 에서 page_fault() 가 처리하는 페이지 오류가 발생해 프로세스가 종료된다.

커널 스레드는 Kernel Virtual Memory와 User Process가 실행 중인 경우 실행 중인 프레스의 User Virtual Memory에 모두 접근할 수 있다. Kernel도 마찬가지고 매핑되지 않은 User Virtual Address로 Memory에 접근한다면 페이지 오류가 발생한다.


typical memory layout

현재 프로젝트에서 user stack은 고정된 크기이다.(project3에서 크기가 바뀐다!)

초기화되지 않은 data segment는 system call에 의해 사이즈가 바뀔 수 있지만 기능을 구현할 필요는 없다.

pintos의 코드 세그먼트는 가장 주소 0x400000에서 시작한다. 대략 가장 아래에서 128MB이며, 이것은 Ubuntu에서 일반적인 크기이고 큰 의미는 없다.


Accessing User Memory

System call의 일부로, 커널은 종종 사용자 프로그램이 제공하는 포인터로 메모리에 접근한다.

  • 매핑되지 않은 가상 메모리에 대한 포인터나 커널 가상 주소 공간(KERN_BASE 위)에 포인터를 잘못 전달할 수 있으니 주의해야 한다.
  • 유효하지 않은 포인터 타입은 커널이나 실행 중인 다른 프로세스에 해를 끼치지 않도록 해야 한다. 문제가 되는 프로세스는 종료하고 해당 리소스를 해제한다.

위 문제 해결할 수 있는 방법은 다음과 같다.

  • User로부터 받은 포인터의 유효성을 확인하고 포인터를 참조 해제한다.
  • 사용자 포인터가 KERN_BASE 아래를 가리키는지 확인하고 참조 해제한다. 잘못된 사용자 포인터는 ‘페이지 오류’를 발생시켜, page_fault() 코드를 수정하여 처리할 수 있다. 이 과정은 프로세스의 MMU(Memory Management Unit)를 활용하여 더 빠르므로 실제 커널(Linux 포함)에서 사용되는 경향이 있다.

위 과정에서 리소스가 leak되는 것을 주의해야 한다.

만약 malloc()을 통해 잠금을 획득하거나, 메모리를 할당했다고 하자. 만약 잘못된 사용자 포인터로 접근해도 반드시 잠금을 해제하고, 메모리 페이지를 해제해야 한다.

만약 잘못된 포인터로 페이지 오류가 발생한다면 메모리 접근에서 오류 코드를 반환하는 방법이 없기 때문이다.


Reference

반응형