[WEEK 09] PintOS - Project 2: User Programs (Argument Passing)

신호정 벨로그·2021년 10월 7일
1

Today I Learned

목록 보기
48/89

커맨드 라인에 입력된 명령어를 실행하는 것이 Argument Passing 과제의 목표이다.

예를 들어 ls -l .c라는 명령어가 입력 되었을 때, 파일명 .c와 -l와 같은 다른 인자를 스택에 저장하여 프로세스를 생성하고 실행하도록 해야 한다.

PintOS에서의 프로그램의 실행

먼저 run 옵션(응용 프로그램 실행)일 경우 run_task() 함수를 호출한다.

인자 함수 process_execute(argv)를 통해 유저 프로세스를 생성한 후 process_wait() 함수를 사용하여 자식 프로세스가 종료될 때까지 대기한다.

process_execute() 함수를 통해 프로세스(쓰레드)를 생성하는 함수를 호출하고 tid를 반환한다.

thread_create() 함수를 호출하여 쓰레드를 생성한 후 생성된 쓰레드를 ready list에 추가한다.

PintOS에서의 프로그램 실행 모델

startprocess() 함수에 파일명 file_name을 입력하여 프로그램을 메모리에 적재한 후 프로그램을 시작한다.

프로그램을 실행하는 과정에서 load() 함수를 통해 프로그램을 메모리에 적재한다.

프로그램을 메모리에 적재하는 것을 성공하지 못하면 thread_exit()을 통해 쓰레드를 종료하고, 성공하면 유저 프로그램을 시작한다.

  1. main() -> 2. run_action() -> 3. run_task() -> 4. process_execute() -> 5. thread_create(): tid 리턴 -> 6. process_execute(): tid 리턴 -> 7. process_wait() -> 8. process_wait() -> 9. run_action() -> 10. shutdown_power_off() -> 11. PintOS 종료

main() 함수에서 시작하여 커맨드 라인에 입력된 명령어가 run 옵션(응용 프로그램 실행)일 경우 run_action() 함수를 호출한다.

run_task() 함수를 호출하여 유저 프로세스가 실행될 수 있도록 프로세스 생성을 시작(process_execute())하고 자식 프로세스가 종료될 때까지 대기(process_wait())한다.

process_waite() 함수는 자식 프로세스가 종료될 때까지 대기한다.

프로세스(쓰레드) 생성 함수 process_execute()을 호출하고 tid를 반환한다.

thread_create() 쓰레드를 생성한 후 ready list에 추가한다.

응용 프로그램 실행의 흐름

  1. process_execute() 프로그램 이름을 파싱한 후 커맨드 라인에서 프로세스 이름을 확인해야 한다.

  2. start_process() 인터럽트 프레임을 초기화하고 load() 메모리를 할당 받아 사용자 프로그램을 적재하기 전에 Argument Passing 기능을 추가하여 커맨드 라인을 Parsing하여 인자를 확인해야 한다.

  3. load() 사용자 프로그램을 메모리에 적재한 후 argument_stack() 인자들을 스택에 삽입해야 한다.

process_execute()

  • tid_t process_execute(const char *str);
  • 예를 들어 process_execute("echo") 호출하면 echo를 실행한다.
  • load(char *str)을 호출

thread_create()

  • 해당 함수를 수행하는 커널 쓰레드를 생성한다.
  • 프로세스 디스크립터(struct thread) 생성 및 초기화
  • 페이지 테이블을 저장하기 위한 메모리 할당
  • 커널 스택 할당 후 커널 쓰레드가 수행할 함수를 등록
  • 커널 쓰레드를 ready list에 추가

프로세스 탑재

  • 프로그램을 메모리에 적재하고 실행하는 함수
  • load(): file_name의 프로그램을 메모리에 적재
  • 메모리 적재 성공 시 응용 프로그램 실행, 실패 시 쓰레드 종료(thread_exit())

load()

  • bool load(const char *file_name, void (eip)(void), void esp);
  • User process의 페이지 테이블 생성
  • 파일을 open하고 ELP 헤더 정보를 메모리로 읽어 들임
  • 각 세그먼트의 가상 주소 공간 위치를 읽어 들임
  • 각 세그먼트를 파일로부터 읽어 들임
  • 스택 생성 및 초기화 (esp: 스택 포인터 주소/eip: text 세그먼트 시작 주소)

  • pagedir_create(): 유저 프로세스의 페이지 테이블을 생성
  • process_activate(): PDBR(cr3) 레지스터 값을 실행 중인 쓰레드의 페이지 테이블 주소로 변경

쓰레드의 페이지 디렉토리에 유저 프로세스의 페이지 테이블을 생성한다.

ELF 파일의 헤더 정보를 읽어서 저장한다.

배치 정보를 읽어 저장한다.

load_segment() 함수를 통해 배치 정보를 통해 파일을 메모리에 적재한다.

스택 포인터가 가리키는 스택을 초기화한다.

문자열을 파싱하고 유저 스택에 인자 값이 저장될 수 있도록 기존의 process_execute() 함수와 start_process() 함수를 수정한다.

문자열 토큰화 함수를 이용해 커맨드 라인의 첫 번째 토큰을 thread_create() 함수에 쓰레드 이름으로 전달한다.

argument_stack() 함수를 이용해 스택에 토큰들을 저장한다.

process_execute() 함수를 수정한다.

thread_create() 함수를 호출하여 프로그램을 실행할 쓰레드를 생성한다.

쓰레드 이름 (file_name), 쓰레드 우선순위 (PRI_DEFAULT), 생성된 쓰레드가 실행할 함수를 가리키는 포인터 (start_process), start_process() 함수를 수행할 때 사용하는 인자 값 (fn_copy)를 인자로 입력한다.

start_process() 함수를 수정하여 실행 파일 로드 기능을 추가한다.

strtok_r() 함수를 이용해 인자들을 토큰화하여 토큰의 개수를 계산한다.

실행 파일 이름을 load() 함수의 첫 번째 인자로 전달한다.

파싱된 토큰을 유저 스택에 저장하는 argument_stack() 함수를 추가한다.

argument_stack() 함수는 프로그램 이름과 인자가 저장되어 있는 메모리 공간 (parse), 인자의 개수 (count), 스택 포인터를 가리키는 주소 (esp)를 인자로 입력한다.

argument_stack() 함수를 호출할 시 인자 값을 스택에 오른쪽에서 왼쪽 순으로 저장한다.

Return AddressCaller(함수를 호출하는 부분)의 다음 수행 명령어 주소를 의미한다.

Callee(호출 받은 함수)의 리턴 값은 eax 레지스터에 저장된다.

기존 PintOS 시스템에서는 유저 스택에 프로그램 인자 값이 삽입되지 않기 때문에 유저 프로그램을 실행할 시 유저 스택은 비어 있는 상태다.

따라서 인자들을 스택에 삽입하는 기능을 추가해야 한다.

예를 들어 '$pintos run 'echo x'라는 커맨드 라인을 입력할 시, 유저 스택의 상단에는 'echo'와 'x'라는 인자가 저장되고 중단에는 각 인자들의 주소값 argv[0], argv[1], *argv[2]가 아래에서 부터 위로 저장된다. 하단에는 인자의 갯수 argc와 중단에 저장된 주소값들의 주소값 **argv가 저장되고 마지막으로 함수를 호출하는 부분의 다음 수행 명령어 주소인 return address가 저장된다.

  • esp 스택 포인터

esp: 스택 포인터를 가리키는 주소값 (void **esp); 스택 포인터는 스택 주소를 감소시키면서 (높은 메모리 주소에서 낮은 메모리 주소로 이동하면서) 인자를 스택에 삽입한다.

parse: 프로그램 이름은 이름과 인자가 저장되어 있는 메모리 공간 (char **parse)

  • argument_stack() 함수는 스택에 프로그램 명과 함수 인자를 저장한다.

argument_stack() 함수는 프로그램 이름과 인자가 저장되어 있는 메모리 공간 parse, 인자의 개수 count, 스택 포인터를 가리키는 주소값 esp를 인자로 입력한다.

유저 프로그램이 실행되기 전에 argument_stack() 함수를 호출하여 스택에 인자를 저장한다. (start_process() 함수에 포함)

다시 읽고 이해하기

다시 읽고 이해하기

다시 읽고 이해하기

인터럽트 프레임(if_)에 저장된 유저 프로그램 컨텍스트를 유저 스택으로 복사한 후 'intr_exit' 위치로 실행 흐름을 변경한다.

유저 스택에 저장된 유저 프로그램 컨텍스트를 각 레지스터에 세팅한다.

  • movl %0, %%esp: 유저 스택(esp)에 인터럽트 프레임(if_)을 복사
  • jmp intr_exit: intr_exit 위치로 실행 흐름을 변경
  • intr_exit: 유저 스택에 저장된 각 레지스터의 값들을 각 레지스터에 저장

다시 읽고 이해하기

다시 읽고 이해하기

다시 읽고 이해하기

  • hex_dump() 함수는 start_process() 함수에 포함된다.
  • PintOS에서 제공하는 디버깅 툴
  • 메모리 내용을 16진수로 화면에 출력
  • 유저 스택에 인자를 저장 후 유저 스택 메모리 확인

다시 읽고 이해하기

다시 읽고 이해하기

다시 읽고 이해하기

'$pintos -v -- run 'echo x''가 입력되었을 때,

00 00 00 00 거짓 return address, 02 00 00 00 인자의 갯수 argc, ec ff ff bf 인자의 값, 65 63 68 6f argv[0] echo의 주소값, 78 argv[1]의 주소값이 출력된다.

  1. process_execute() (const char *file_name): 프로그램을 실행 할 프로세스 생성

  2. startprocess() (void *file_name): 프로그램을 메모리에 적재하고 응용 프로그램 실행

  3. argument_stack() (char parse, int count, void esp): 함수 호출 규약에 따라 유저 스택에 프로그램 이름과 인자들을 저장

1개의 댓글

comment-user-thumbnail
2024년 3월 20일

안녕하세요, could you please share the slides?Thank u very much:)

답글 달기