Pintos Project 2 User Progs

Park Choong Ho·2021년 10월 4일
0

Pintos Project2

핀토스 2번째 과제인 User Program이 시작되었습니다. 정글 Docs에 CSAPP 책 8.2 ~ 8.5와 연관되어 있다 해서 해당 부분 CSAPP을 정리해 보았습니다.

CSAPP 8.2 ~ 8.4

Process

프로그램을 동작시키면 마치 그 프로그램이 컴퓨터에 있는 자원(ex. CPU, 메모리)을 배타적으로 활용하는 것 같은 착각을 불러일으킵니다. 이러한 착각은 바로 프로세스라는 OS가 제공하는 추상화개념으로부터 기인합니다.

정의: An instance of running program

시스템상에 존재하는 모든 프로그램은 프로세스의 context에 기반해서 동작합니다. Context에는 많은 정보들이 들어가는데 code, data, stack, 각종 레지스터 값들, program counter, 환경변수, open filedescriptor 집합 등이 그 정보들입니다.

앞서 말했듯 프로세스가 제공하는 추상화는 크게 2가지입니다.

  1. 독립적인 logical control flow가 프로그램이 프로세서를 혼자서 사용하는 것 같은 착각을 불러일으킨다.

  2. 개별 주소 공간이 프로그램이 혼자서 메모리를 사용하는 것 같은 착각을 불러일으킨다.

Logical Control Flow

Program Counter는 executable object 파일상에 있는 코드를 런타임에서 계속 읽어 내려가고 이를 CPU가 실행합니다. 이러한 PC(Program Counter)상 값들의 연속을 logical control flow라고 합니다.(간단히 줄여서 logical flow라고 하겠습니다.)

위 그림을 보면 총 3개의 logical control flow가 있는 것을 확인할 수 있습니다. 각 프로세스가 실행될 때마다 마치 해당 프로세스가 CPU를 혼자서만 사용하는 것 같은 착각을 불러일으킵니다.

두 프로세스의 logical flow가 겹치면 두 프로세스는 concurrent하다고 합니다. Concurrent하다는 것은 X 프로세스가 시작하고 나서 Y 프로세스가 X 프로세스가 끝나기전에 시작했다면 이 둘을 concurrent하다고 합니다. 위 그림에서 A, B와 A, C는 concurrent합니다. 그러나 B, C는 concurrent하지 않습니다. 왜냐하면 B가 종료되고 난 후에 C가 시작되었기 때문입니다.

각 프로세스가 자신의 logical flow를 일정 부분 실행 했을때 진행된 시간을 time slice라고 합니다. 여기서 프로세스 A logical flow는 2개, B는 1개, C는 2개의 time slice로 구성되었다고 할 수 있습니다.

Private Address Space

프로세스는 개별 주소공간을 받음으로써 마치 프로그램이 시스템 메모리를 독점적으로 쓰는 것 같은 착각을 불러 일으킵니다. 프로세스는 각 프로그램마다 개별 주소 공간을 제공합니다. 개별이라는 단어의 뜻은 프로세스가 다른 프로세스의 주소 공간을 읽고 쓰는 것이 기본적으로 가능하지 않기 때문입니다. 각 주소 공간은 개별적으로 제공되지만 같은 형태를 띄고 있습니다.

모든 주소 공간이 위와 같은 형태를 띈다고 할 수 있습니다.(Linux x86-64기준) 주소 공간에서 유저 프로그램 부분은 code, data, heap, stack입니다. code는 항상 주소 0x400000에서 시작하빈다. 주소 공간 위에는 커널이 자리잡고 있습니다.

User & Kernel mode

user, kernel 모드는 CPU가 mode bit로 제어하는 부분입니다.(따라서 프로그래머가 프로그램을 정확히 짜야됨) user 모드에서 실행되는 프로그램은 기본적으로 커널 코드에 접근할 수 없습니다. 그리고 CPU를 멈추거나, mode bit를 설정하거나, I/O 동작을 초기화 하는 등의 인스트럭션 또한 실행할 수 없습니다. 만약 user mode로 돌아가는 프로세스가 위와 같은 작업을 시도한다면 protection fault가 나게 됩니다.

CPU는 mode bit이라는 제어 레지스터를 통해 프로세스의 mode를 변경합니다. mode bit이 0이면 user mode이고 1이면 kernel mode입니다.

프로세스가 user mode에서 kernel mode로 변환될 수 있는 유일한 방법은 exception뿐입니다. exception이 발생하면,

  1. 제어권을 exception handler가 가지고
  2. 프로세서가 user moder에서 kernel mode로 프로세스를 변경하고
  3. handler가 kernel mode로 동작하고
  4. 프로그램 코드로 돌아오고 나면 프로세서가 kernel mode를 user mode로 변경합니다.

Context Switch

운영체제는 여러개의 프로세스의 multitasking을 높은 레벨 단계의 exceptional control flowcontext switch로 구현했습니다.

커널은 각 프로세스의 context를 저장하고 있습니다. 프로세스는 프로세스를 재개할 때 필요한 상태를 의미합니다. 특정 어느 시점에서 커널은 현재 돌아가는 프로세스를 멈추고 전에 멈췄던 프로세스를 다시 재개하려고 합니다. 이러한 kernel의 행위를 scheduling이라 하며, 이는 커널 안에 있는 schduler라는 코드가 이를 수행합니다. 커널이 스케줄링을 완료하고 나면 현재 프로세스를 멈추고 제어권을 context switch를 통해 새롭게 동작하는 프로세스에게 넘깁니다.

Context Switch는 크게 3단계로 나뉘는데

  1. 현재 프로세스 context 저장
  2. 전에 멈췄던 프로세스의 저장되어 있던 context 가져오기
  3. 새롭게 동작하는 프로세스에게 제어권 넘기기

이렇게 구성됩니다.

Pintos Project 2 코드 흐름

Pintos Project2는 Project1에서 구현해두었던 스레드를 유저프로그램으로 올리는 코드입니다. 주요 흐름들을 살펴보도록 하겠습니다.

init.c

init.c의 main 함수를 살펴보면 run_actions (argv); 부분이 있어 여기서 유저 프로그램이 실행된다.

pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

이러한 명령어를 입력하면 핀토스에서는 run 그 다음 명령어가(여기서는 bad-read) 유저 프로그램이 된다. 위 예시에서는 args-single onearg가 유저 프로그램에 넘기는 인자가 되겠습니다.

run_actions 함수

static void run_actions (char **argv) {
	/* 넘겨준 인자에 run이 있으면 user program으로 인식 */
    /* run_task 함수를 호출 */
}

run_task 함수

static void run_task (char **argv) {
	/* argv[0]에는 "run", argv[1]에는 "args-single onearg"가 담겨있음 */
    /* 문자열 포인터 변수인 task에 argv[1]을 넣고 process_create_initd 함수에 넘겨줌 */
    /* process_wait 함수가 유저 프로세스가 생성되고 종료될때까지 기다려준다. */
    process_wait(process_create_initd(task));
}

process_create_initd 함수

tid_t process_create_initd (const char *filename) {
	char *fn_copy;
    /* fn_copy에 페이지 할당 */
    fn_copy = palloc_get_page(0);
    /* fn_copy에 filename 복사해서 놓기 */
    strlcpy (fn_copy, filename, PGSIZE);
	
    /* 스레드 생성 생성 후 ready_list에 넣은 후 initd 함수 실행*/
    tid = thread_create(filename, PRI_DEFAULT, initd, fn_copy);
}

initd 함수

static void initd (void *f_name) {
	...
    /* process_exec 함수 실행 */
    if (process_exec (f_name) < 0) PANIC("Fail to launch initd\n");
    NOT_REACHED();
}

process_exec 함수

int process_exec (void *f_name) {
	/* intr_frame 멤버 값을 유저 프로그램것으로 변경 */
    /* 물리 메모리에 로드 */
}

여기까지가 유저 프로그램이 메모리에 로드되고 동작하는 과정입니다.

Argument Passing

처음은 Argument Passing입니다. 넘어온 args-single onearg를 유저 프로그램에서 다루기 위해 이를 스택에다 쌓고 해당 데이터를 포인터로 가리켜야 합니다.

위 작업을 위해서 첫번째 필요한 것은 받아온 인자인 args-single onearg를 " " 공백 기준으로 파싱을 해주어야합니다. pintos 프로젝트 내에 있는 문자열 파싱 함수 중 strtok_r 함수를 활용하여 파싱했습니다.

profile
백엔드 개발자 디디라고합니다.

0개의 댓글