[PintOS] Project 2 - User Programs (1) Argument passing

novxerim·2022년 1월 11일
0

SW-Jungle

목록 보기
38/59

- Keyword

User mode vs. Kernel mode

Process

- Process Environment block (PEB)
- Process identifier (PID)

User Stack

x86_64 calling convention

Register vs. Memory

argument vector → argv

Executable Linkable Format (ELF) & loader

system call (syscall)

**!!CAUTION!! some system call’s semantic may differ from POSIX standards**

- filesys related
    - open, close, create, read, write, seek, tell, ...
- process related
    - halt, exit, exec, fork, wait ...

file descriptor

- file descriptor table
- dup2 syscall

- Introduction

Gitbook Contents

Intro

  • 만약 추가 과제의 테스트케이스를 도전하고 싶으면 userprog/Make.vars를 수정하라.

  • TODO가 없는 코드는 수정할 필요가 없다.

We allow more than one process to run at a time. Each process has one thread (multithreaded processes are not supported).

  • 다중 프로세스로 구성되어 있다.

User programs are written under the illusion that they have the entire machine.

  • UP는 전체 장치들이 있다는 가정하에 작성한다.

From now on, we will test your operating system by running user programs.

  • 이번 프로젝트에서는 제출한 PintOS를 유저 프로그램을 작동시켜봄으로서 평가해본다.

You must make sure that the user program interface meets the specifications described here, but given that constraint you are free to restructure or rewrite kernel code however you wish.

  • 커널 코드를 자유롭게 작성해보라.

All of your codes should never located in block that enclosed by #ifdef VM

  • ifdef VM 안에 코드를 작성하지 마라 (여기는 프로젝트 3때 다루는 구역이다)

We strongly recommend you to read synchronization and virtual addrees before you start.

- 시작하기 전에 synch와 가상 메모리를 읽어라.


Using File System

You will need to interface to the file system code for this project, because user programs are loaded from the file system and many of the system calls you must implement deal with the file system.

  • 작성한 시스템 콜들은 파일 시스템을 다루며, 파일 시스템은 UP(유저 프로그래밍)을 로드하기 때문에 파일 시스템에 대한 인터페이스가 필요하다.

Proper use of the file system routines now will make life much easier for project 4

- 파일 시스템 루틴을 적절히 이용하면 project 4가 수월해진다.

- 다음 제한 사항을 가진다.

  • No internal synchronization. Concurrent accesses will interfere with one another. You should use synchronization to ensure that only one process at a time is executing file system code.

ㆍ내부 sync가 없음 : 동시 접속은 서로에게 영향을 줄 것이다. 한번에 한 파일 시스템 코드만 수행할 수 있도록 sync를 사용하라.

  • File size is fixed at creation time. The root directory is represented as a file, so the number of files that may be created is also limited.

ㆍ파일 크기는 만들 때 크기로 고정된다. 루트 디렉토리가 파일 형태이기 때문에 만들어질 파일들 또한 제한된다. (무엇이?)

  • File data is allocated as a single extent, that is, data in a single file must occupy a contiguous range of sectors on disk. External fragmentation can therefore become a serious problem as a file system is used over time.

ㆍ파일들은 연속적인 디스크 섹터를 가진다. (분산되서 저장되지 않음) 그렇기 때문에 외부 단편화가 문제가 될 수 있다.

  • No subdirectories

ㆍ서브디렉토리 없음

  • File names are limited to 14 characters.

ㆍ파일명은 14음절로 제한된다.

  • A system crash mid-operation may corrupt the disk in a way that cannot be repaired automatically. There is no file system repair tool anyway.

ㆍ작업 중의 시스템 충돌은 디스크를 오염시킬 수 있으며, 자동으로 복구되어지지 않는다.

One important feature is included:

중요한 사항이 있음

Unix-like semantics for filesys_remove() are implemented. That is, if a file is open when it is removed, its blocks are not deallocated and it may still be accessed by any threads that have it open, until the last one closes it. See Removing an Open File for more information.

  • filesys_remove() 를 위한 Unix-like semantics 가 내장되어있다. 만약 실행되고 있는 파일이 삭제된다면, 이 블록은 반환되지 않으며, 이 파일을 열려는 스레드가 모두 닫히기 전에는 접속할 수 있게 되버린다. Removing an open files를 참고하라.

How User Programs Work

메모리가 여유로우며 작성한 시스템 콜만 사용하는 한, PintOS는 일반 C 프로그램을 수행할 것이다. 이번 프로젝트에서는 메모리 할당이 필요하지 않기 때문에 malloc()은 수행되지 않는다. 커널이 스레드를 바꿀 때, 프로세서의 floating-point를 저장하거나 복구하지 않기 때문에, pintos는 floating point operation이 있는 프로그램을 수행하지 않는다.

pintos는 userprog/proces.c 안에 로더와 함께 ELF excutable을 로드할 수 있다. ELF는 Linux나 solaris 등 에서 사용되는 파일 포맷이다.


Virtual Memory Layout

가상 메모리는 두 영역으로 나눠진다 : 유저 가상 메모리와 커널 가상 메모리이다. 유저 가상 메모리의 영역은 가상 주소 0부터 KERN_BASE 까지이며 (incldue/threads/vaddr.h 안에 정의되어있다. 기본값은 0x800400000) 커널 가상 메모리는 나머지를 차지하고 있다.

유저 가상 메모리는 per-process이다. 커널이 프로세스를 바꿀 때, processor page directory base register를 바꿈으로서 유저 가상 주소 공간도 바꾼다. 스레드는 프로세스 page table 포인터를 가지고 있다.

커널 가상 메모리는 전역으로 사용된다. 유저 프로세스나 커널 스레드에 구애받지 않고 맵핑 가능하다. 핀토스에서는, 가상메모리는 물리적 메모리에 1:1 대응하고있다. 가상 주소 KERN_BASE는 물리 주소 0에 대응하고, 가상 주소 KERN_BASE + 0x1234는 물리주소 0x1234에 대응한다.

유저프로그램은 유저 가상 메모리에 접속가능한다. 커널 가상 메모리에 접속하려하면, userprog/exception.c 안의 page_fault()에 의해 page fault가 발생하고, 프로세스는 제거될 것이다. 배정받지 않은 유저 가상 메모리에 접속하려고 하면 page fault가 일어난다.


Typical Memory Layout

이번 프로젝트에서 유저 스택은 고정된 크기를 가지고 있다.

pintos에서 코드 영역은 유저 가상 주소 0x400000에서 시작하며 대략 128MB정도로, 주소 공간 아랫쪽에 위치한다.

linker는 말 그대로 메모리의 유저 프로그램의 layout을 만든다. info ld로 접속가능한 linker manul에서 script를 읽으면 linker script에 대해서 알 수 있다.


Accessing User Memory

시스템 콜의 한 부분으로서, 커널은 유저 프로그램이 제공한 포인터로 메모리에 접속해야한다. 이 때, 유저가 null pointer나 가상 메모리에 맵핑되어있지 않은 포인터를 넘겨주거나, 커널 가상 주소로의 포인터를 넘겨줄 수도 있다. 이러한 포인터들은 offending process를 제거하고, process의 자원을 폐기함으로써 포인터를 제거한다.

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

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

자원이 새지(leak)않도록 하라. 예를 들어, 시스템 콜이 malloc()으로 메모리를 할당받았거나 어떤 lock을 가졌다고 해보자. 이 다음에 잘못된 유저 포인터를 사용한다면, 반드시 lock이나 메모리를 반환해야한다. 만약 역참조하기 전에 유저 포인터를 검사했다면 잘못될 일이 없을 것이다. 만약 잘못된 유저 포인터가 page fault를 일으킨다면 에러 코드나 반환값을 받을 길이 없기 때문에 더욱 다루는 게 어려워진다. 그러므로 두번째 방법을 쓰고 싶은 사람은 아래 코드를 사용해봐라.


/* Reads a byte at user virtual address UADDR.
 * UADDR must be below KERN_BASE.
 * Returns the byte value if successful, -1 if a segfault
 * occurred. */
static int
get_user (const uint8_t *uaddr) {
    int result;
    asm ("movl $1f, %0; movzbl %1, %0; 1:"
         : "=&a" (result) : "m" (*uaddr));
    return result;
}
 
/* Writes BYTE to user address UDST.
 * UDST must be below KERN_BASE.
 * Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte) {
    int error_code;
    asm ("movl $1f, %0; movb %b2, %1; 1:"
    : "=&a" (error_code), "=m" (*udst) : "q" (byte));
    return error_code != -1;
}

이 함수들은 KERN_BASE아래에 유저 주소가 있다고 가정한다. 또한, 커널에서 page_fault가 일어났을 때, rax to -1 에서 일어나도록 하고, 이전 값을 %rip에 복사하도록 page_fault()를 수정했다고 가정한다.



Words

ELF

  • userprog/process.c 에서 제공되는 로더
  • 오브젝트 파일, 공유 라이브러리, 실행 파일을 위해 사용되는 파일 형식이다.
  • Linux, Solaris 및 기타 여러 운영 체제에서 개체 파일, 공유 라이브러리 및 실행 파일에 사용하는 파일 형식
  • x86-64 ELF 실행 파일을 출력하는 컴파일러와 링커를 사용하여 Pintos용 프로그램을 생성할 수 있다

.bss

.bss(Block Started by Symbol): 컴파일러와 링커에서 쓰이는 용어

컴퓨터 프로그래밍에서 .bss 또는 bss는 / 수많은 컴파일러와 링커가 처음에 0 값의 비트로 표현되는 정적으로 할당된 변수를 포함하는 데이터 세그먼트의 한 부분으로 사용한다. "bss 섹션"(bss section), "bss 세그먼트"(bss segment)라고도 부른다. 즉, 초기화되지 않은 전역 데이터를 위한 영역이다.

cc : [https://dreamlog.tistory.com/91]

일반적으로 데이터가 없는 bss 섹션의 길이만이 오브젝트 파일에 저장된다. 프로그램 로더는 프로그램을 로드할 때 bss 섹션을 위한 메모리를 할당하고 초기화한다. 운영 체제는 zero-fill-on-demand라는 기술을 사용하여 bss 세그먼트를 효율적으로 구현한다. (McKusick & Karels 1986) 임베디드 소프트웨어에서 bss 세그먼트는 main()에 들어가기 전에 C 런타임 시스템에 의해 0으로 초기화되는 메모리로 매핑된다.

일부 컴퓨터 아키텍처에서 ABI 또한 조그마한 데이터에 대한 sbss 세그먼트를 지원한다. 일반적으로 이러한 데이터 항목들은 특정한 범위의 주소에만 접근할 수 있는 더 짧은 명령을 이용하여 접근할 수 있다.


메모리 구조, 영역

개념적으로 각 프로세스는 자신의 사용자 가상 메모리를 자유롭게 배치할 수 있습니다. 실제로 사용자 가상 메모리는 다음과 같이 배치됩니다.

USER_STACK +----------------------------------+
           |             user stack           |
           |                 |                |
           |                 |                |
           |                 V                |
           |           grows downward         |
           |                                  |
           |                                  |
           |                                  |
           |                                  |
           |           grows upward           |
           |                 ^                |
           |                 |                |
           |                 |                |
           +----------------------------------+
           | uninitialized data segment (BSS) |
           +----------------------------------+
           |     initialized data segment     |
           +----------------------------------+
           |            code segment          |
 0x400000  +----------------------------------+
           |                                  |
           |                                  |
           |                                  |
           |                                  |
           |                                  |
       0   +----------------------------------+

code영역은 프로그램의 코드, data영역은 global 변수나 static 변수, heap 영역(gross upward)은 동적으로 할당받은 메모리 영역, stack 영역은 함수 호출 시 생성되는 지역변수나 매개변수, return 값들이 들어간다.

이 프로젝트에서는 사용자 스택의 크기가 고정되어 있지만 프로젝트 3에서는 확장이 허용됩니다. 전통적으로 초기화되지 않은 데이터 세그먼트의 크기는 시스템 호출로 조정할 수 있지만 이를 구현할 필요는 없습니다.

Pintos의 코드 세그먼트는 사용자 가상 주소 0x400000에서 시작하며 주소 공간의 맨 아래에서 약 128MB입니다. 이 값은 우분투에서 일반적인 값으로 큰 의미는 없습니다.

링커는 다양한 프로그램 세그먼트의 이름과 위치를 알려주는 "링커 스크립트"의 지시에 따라 메모리에서 사용자 프로그램의 레이아웃을 설정합니다. 를 통해 액세스할 수 있는 링커 설명서의 "스크립트" 장을 읽으면 링커 스크립트에 대해 자세히 알아볼 수 있습니다 info ld.

특정 실행 파일의 레이아웃을 보려면 -p옵션 과 함께 objdump를 실행하십시오 .

rdi; // 목적지(destinaion) 인덱스 레지스터. arg의 갯수를 저장

rsi; // arg[0](file name)의 char이 저장되기 시작한 주소를 저장


rsp, rbp, rsi, rdi.. 명령어 레지스터

<interrupt.h>

cc : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=pjt3591oo&logNo=220395406400


- Projects

개요

  • 지난 주는 test파일에 작성된 정적인 코드를 돌린 것을 test한 것이지만 이번 주차는 user programs을 동작시키기 위한 구현 과제이다. 일단 현재 아무 것도 하지 않은 base code상태에서는 user program을 로딩하고 실행시키는 것은 지원하지만, I/O나 인터랙티브는 가능하지 않은 상태이다. → 지금 딱 실행까지만 된다는 것, 입출력 안됨.

    인터랙티브란 사람들로부터 입력을 받는 것을 의미.
    사용자와의 상호작용은 대개 텍스트 기반 또는 그래픽 사용자 인터페이스 둘 다에 사용된다.

    그래서 이번 project 2에서는 system calls를 통해 programs이 OS와 interact할 수 있도록 하는 것이다.
  • 이전에 했던 것은 커널에 직접적으로 test code를 컴파일하기 때문에 커널 내에 특정한 함수를 요구했다. 하지만 지금은 user program을 로드하고 실행시키면서 더 자유성이 있지만 kernel code를 수정하는데 있어 제약사항이 주어진다.

Argument passing

구조

프로그램의 실행

1 → 2 → 3 → 4 → 5 → 4 → 3 → 63 → 2 → 1

  1. init.c - main()
    • main() 실행
      int main(void) {
      	...
      	run_action(argv);
      	shutdown_power_off();
      	...
      }
  2. run_action() [2→3→4→5]
    • run옵션(응용프로그램 실행)일 경우 run_task() 호출
      static void run_action (char **argv) {
      	static const struct action
      actions[] =
      	{
      		{"run", 2, run_task},
      		...
      	};
      }
  3. run_task()
    • 유저 프로세스가 실행될 수 있도록 프로세스 생성을 시작하고 프로세스 종료를 대기 process_wait(process_excute(argv));
      static void run_task(char **argv) {
      	...
      	process_wait(process_excute(argv);
      	...
      }
  4. process_excute() - 프로그램 이름 파싱
    • 프로세스(스레드)생성 함수를 호출하고 tid 리턴 ex) process_excute("echo"); → echo 실행 load(char* str) 을 호출
      tid_t process_excute (const char *file_name) {
      	...
      	tid = thread_create (,start_process,);
      	...
      	return tid;
      }
  5. thread_create() [5→4→3→6]
    • (커널) 스레드 생성 후 run queue(ready-list)에 추가
      tid_t thread_create (const char *name, 
      int priority, thread_func *function, void *aux)
      {
      	struct thread *t;
      	struct kernel_thread_frame *kf; -> 주석처리함
      	...
      	t = palloc_get_page(PAL_ZERO);
      	...
      	kf->function = function;
      	...
      	/* Add to run queue. */
      	thread_unblock (t);
      	return tid;
      }
  6. process_wait() [6→3→2→1] Scheduled? - Yes : start_process(), No : 종료
    • 자식 프로세스가 종료될 때 까지 대기
      int process_wait (tid_t child_tid UNUSED) {
      	return -1;}

PintOS의 프로그램 실행 모델

start_process()

start_process() : 프로그램을 메모리에 적재 load() 후 프로그램 시작

인터럽트 프레임 초기화

static void start_process (void *file_name_) {
	char *file_name = file_name;
	struct intr_frame if_;
	bool success;
	...
	success = load (file_name, &if_.eip, &if_.esp);
	if (!success)
		thread_exit();
	/* Start the user process */
	asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g"
			(&if_) : "memory");
}

success : 메모리 적재 성공(load() → Success) - 유저 프로그램 시작


load()

load() : 메모리를 할당받고 사용자 프로그램을 메모리에 적재 return success

bool load (const char *file_name, void (**eip) (void), void **esp)
{
	...
	struct file *file = NULL;
	,,,
	/* Set up stack. */
	if (!setup_stack (esp))
	...
	success = true;
	return sucess;
}


thread_exit()

thread_exit() : 스레드 종료

void thread_exit (void) {
	...
	process_exit();
	intr_disable();
	list_remove(&thread_current()->allelem);
	thread_current()->status = THREAD_DYING;
	schedule();
}

해야할 것 (Argument passing)

command line에 한 줄 입력이 들어오면 띄어쓰기(” “)를 기준으로 parsing을 해서 file_name과 arguments를 받도록 하는 작업을 해준다.

현재는 1. file_name을 parsing하는 작업과 2. parsing한 arugments를 어딘가에 저장해놓는 코드가 구현이 안되어있다.

  1. process_exec 함수 내용에 parsing 작업 코드를 추가한다.
  2. argument_stack 함수를 생성한다. ( 둘 다 process.c 파일)

Argument passing 참고 블로그

https://velog.io/@shinhojung814/WEEK-09-PintOS-Project-2-User-Programs-Argument-Passing#implementations

https://straw961030.tistory.com/262?category=957768


코드 (github)

https://github.com/Yerimi11/pintos-kaist-team09/tree/yerim_Project_2

profile
블로그 이전했습니다. https://yerimi11.tistory.com/

0개의 댓글