[WEEK 09] PintOS - Project 2: User Programs (System Calls)

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

Today I Learned

목록 보기
51/89

프로세스 계층구조의 과제의 목표는 프로세스 간의 부모와 자식 관계를 구현하고, 부모가 자식 프로세스의 종료를 대기하는 기능을 구현하는 것이다.

기존의 PintOS 운영체제는 프로세스 구조체에 부모와 자식 관계를 명시하는 정보가 없다.

부모와 자식의 구분이 없고, 자식 프로세스의 정보를 알지 못하기 때문에, 자식의 시작/종료 전에 부모 프로세스가 종료되는 현상이 발생하고 프로그램이 실행되지 않는다.

  1. 프로세스 디스크립터(struct thread)에 부모와 자식 필드르 추가하고, 이를 관리하는 함수를 구현한다.

1-1. 부모 프로세스를 가리키는 포인터를 추가한다.

1-2. 자식 프로세스를 리스트로 구현한다.

1-3. 자식 리스트에서 원하는 프로세스를 검색하고 삭제하는 함수를 구현한다.

  1. exec(), wait() 함수를 세마포어를 이용하여 구현한다.

유저 프로그램이 실행 되지 않는 원인은 init 프로세스가 유저 프로그램이 실행 되기 전에 종료되기 때문이다.

  1. 유저 프로세스가 생성된다.

  2. 프로세스가 생성된 후 ready_list에 추가된다.

  3. process_wait() 함수가 호출된다.

  4. PintOS가 종료된다.

기존 PintOS의 실행 흐름은 init 프로세스가 생성되고 run_action()와 run_task()이 실행된 다음 process_execute()가 실행되는 과정에서 schedule() 함수로 인해 유저 프로세스로 전환되고 컨텍스트 스위치를 통해 process_wait()이 호출되고 전원을 끄면서 PintOS가 종료된다.

기존의 상태에서 process_wait()과 sema_down()을 호출하여 유저 프로세스를 시작하고 유저 프로그램을 실행시킨다. 유저 프로그램의 사용이 끝나면 exit()을 통해 쓰레드를 종료하고 sema_up()을 통해 exit_status를 반환하고 전원을 끔으로써 PintOS를 종료한다.

PintOS의 프로세스는 하나의 쓰레드로 구성되어 있다.

PintOS에서의 쓰레드는 생성되어 ready 상태가 되고 스케줄링되면 실행되어 running 상태가 된다. 이벤트를 기다려야 하는 경우 blocked 상태로 전환되고 이벤트가 발생하면 다시 ready 상태가 된다.

프로세스 계층 구조의 설계는 부모 프로세스와 자식 프로세스를 구분하는 것이다.

부모는 자식 프로세스 디스크립터들을 리스트를 이용하여 관리한다.

프로세스 계층 구조를 구현하기 위해 프로세스 디스크립터(struct thread)에 정보를 추가한다.

프로세스의 생성 성공 여부를 확인하는 플래그를 추가한다.

프로세스의 종료 유무를 확인하는 필드를 추가한다.

프로세스의 종료 상태를 나타내는 필드를 추가한다.

자식 프로세스의 생성/종료 대기를 위한 세마포어를 추가한다.

자식 프로세스 리스트 필드를 추가한다.

부모 프로세스 디스크립터를 가리키는 필드를 추가한다.

struct thread 구조체에 부모 프로세스의 디스크립터, 자식 리스트와 자식 리스트 element, 프로세스의 프로그램 메모리 적재 유무, 프로세스의 종료 유무 확인, exit 세마포어, load 세마포어, exit 호출 시 종료 status 등 프로세스의 정보를 추가한다.

자료구조를 초기화한다. 쓰레드 생성 시 자식 리스트를 초기화한다.

void memset (void ptr, int value, size_t num);
ptr: 채우고자 하는 메모리의 시작 포인터(시작 주소)
value: 메모리에 채우고자하는 값. int형이지만 내부에서는 unsigned char(1 byte)로 변환되어서 저장된다.
num: 채우고자 하는 바이트의 수. 즉, 채우고자 하는 메모리의 크기

ptr이 가리키는 주소부터 num 바이트 만큼의 메모리를 value로 메모리에 채운다.
쓰레드 t의 시작 주소부터 쓰레드 t의 크기만큼 0으로 메모리에 채운다.

size_t strlcpy(char dest, const char src, size_t size);
dest: 복사가 진행될 목적지이다. void *의 형태로 string뿐 아니라 다른 값도 들어올 수 있다.
src: 우리가 복사를 해야하는 값이 들어있는 포인터이다.
dstsize: 최대 size - 1만큼만 복사를 진행한다. (\0값을 넣기 위해)

쓰레드의 이름에 name과 \0을 더하여 복사한다.

thread_create() 함수가 자료구조를 초기화하도록 한다.
생성된 프로세스 디스크립터의 정보를 초기화한다.
부모 프로세스의 자식 리스트에 추가한다.

get_child_process(): 자식 리스트를 pid로 검색하여 해당 프로세스 디스크립터를 반환한다.

/* 자식 리스트 child_list에 접근하여 프로세스 디스크립터 검색 */
	for (struct list_elem *e = list_begin(child_list); e != list_end(child_list); e = list_next(e)) {
		struct thread *t = list_entry(e, struct thread, child_elem);

자식 리스트 child_list에 접근하여 프로세스 디스크립터를 검색한다.

/* 해당 pid가 존재하면 프로세스 디스크립터 반환 */
		if (t -> tid == pid)
			return t;

해당 pid가 존재하면 프로세스 디스크립터를 반환한다.

/* 리스트에 존재하지 않으면 NULL 리턴 */
	return NULL;

자식 리스트에 존재하지 않으면 NULL을 리턴한다.

process_wait() 함수에서 list_remove(&child -> &child_elem) 포함

  • exec() 시스템 콜 구현: pid_t exec(const *cmd_line)
  • 자식 프로세스를 생성하고 프로그램을 실행시키는 시스템 콜
  • 프로세스 생성에 성공 시 생성된 프로세스에 pid 값을 반환, 실패 시 -1 반환
  • 부모 프로세스는 생성된 자식 프로세스의 프로그램이 메모리에 적재 될 때까지 대기
  • cmd_line: 새로운 프로세스에 실행할 프로그램 명령어
  • pid_t: int 자료형

exec() 시스템 콜에서 process_execute()를 호출하여 thread_create()을 통해 자식 프로세스를 생성한다. sema_down()이 실행되면 자식 프로세스가 인터럽트 스택을 초기화한다. load()를 통해 메모리에 적재를 완료한다. sema_up()을 하면 유저 공간에서 사용자 프로그램이 실행된다. 자식 프로세스의 PID를 반환하고 부모 프로세스가 다시 진행된다.

세마포어공유 자원에 접근할 수 있는 프로세스의 수를 제한한다.

공유 자원에 접근 가능한 프로세스 수를 초과한 경우 프로세스는 대기 상태에 진입한다.

세마포어 구조체 struct semaphore현재 공유 자원에 접근할 수 있는 프로세스의 수 value공유 자원 접근을 대기 중인 프로세스의 리스트인 waiters를 멤버로 갖는다.

프로세스가 대기 상태로 진입, 이탈할 수 있도록 세마포어를 사용한다.

  • sema_init() 함수는 세마포어의 value 값을 초기화한다.

  • sema_down() 함수는 세마포어의 value가 0일 경우 현재 쓰레드를 THREAD_BLOCK 상태로 변경한 후 schedule()을 호출한다.

  • sema_up() 함수는 대기 리스트에 쓰레드가 존재하면 리스트 맨 처음에 위치한 쓰레드를 THREAD_READY 상태로 변경한 후 schedule()을 호출한다.

exec()은 자식 프로세스를 생성하고 프로그램을 실행시키는 시스템 콜이다.

프로세스 생성에 성공할 시 생성된 프로세스에 pid 값을 반환한다.

부모 프로세스는 자식 프로세스의 응용 프로그램이 메모리에 적재될 때 까지 대기한다.

자식 프로세스가 메모리에 적재 완료 시 세마포어를 이용해 부모 프로세스를 다시 진행한다.

현재 process_wait()은 -1을 리턴한다.

process_wait()이 자식 프로세스가 모두 종료될 때까지 대기하는 기능을 구현한다.

자식 프로세스가 올바르게 종료 됐는지 확인한다. wait() 시스템 콜 구현 process_wait() 함수를 호출한다.

  1. 부모 프로세스에서 자식 프로세스 생성

  2. 자식 프로세스에서 ready_list에 추가

  3. 부모 프로세스가 wait() 호출

  4. wait 리스트 추가

  1. 자식 프로세스 실행

  2. 자식 프로세스의 종료를 알림

  3. 부모 프로세스가 wait 리스트에서 이탈

  4. 부모 프로세스를 ready_list에 추가

wait()은 자식 프로세스가 수행되고 종료될 때까지 부모 프로세스를 대기한다.

프로세스의 정보를 알기 위해 프로세스 디스크립터를 검색한다.

자식 프로세스가 종료되지 않았으면 부모 프로세스는 대기한다.

유저 프로세스가 정상적으로 종료될 시 exit status를 반환한다.

프로세스가 종료될 시 thread_exit() 함수를 호출한다.

유저 프로세스가 종료되면 부모 프로세스는 대기 상태 이탈 후 진행한다.

프로세스 디스크립터에 프로세스가 종료 됨을 표시한다.

thread_schedule_tail() 함수가 프로세스 디스크립터를 삭제하지 않도록 수정한다.

palloc_free_page() 함수 부분을 삭제한다.

프로그램 종료 시 exit() 시스템 콜을 호출한다.

프로그램이 정상적으로 종료 됐는지 확인하기 위해 exit_status를 저장한다.

struct list_elem 구조체는 리스트에 속한 이전 원소 prev와 다음 원소 next로 구성된다.

struct list 구조체는 리스트의 첫 번째 원소인 head와 마지막 원소인 tail로 구성된다.

PintOS의 모든 쓰레드는 리스트로 관리된다.

쓰레드를 관리하는 리스트 관련 함수는 <list.h> 파일에 작성되어 있다.

list_init() 함수는 리스트를 인자로 입력받은 list 자료 구조를 초기화한다.

list_push_back() 함수는 인자로 입력받은 list_elem 원소를 다른 인자인 list 리스트의 끝에 삽입한다.

list_entry() 함수는 list_elem이 멤버로 member로 포함된 struct의 포인터를 반환한다.

init_thread() 함수는 쓰레드 구조체 thread와 쓰레드의 이름 name과 우선 순위 priority를 인자로 입력 받으며 프로세스 디스크립터를 초기화한다.

thread_create() 함수는 쓰레드의 이름 name과 우선순위 priority, 수행할 함수 function과 aux를 입력 받으며 쓰레드를 생성하며 함수를 수행한다.

thread_exit() 함수는 인자를 입력 받지 않으며 현재 실행 중인 쓰레드를 종료한다.

start_process() 함수는 인자로 입력 받은 file_name의 프로그램을 메모리에 적재하고 응용 프로그램을 실행한다.

process_wait() 함수는 자식 프로세스의 쓰레드 아이디 child_tid를 인자로 입력 받으며 자식 리스트를 검색하여 해당 tid를 가진 프로세스 디스크립터의 주소를 리턴한다.

remove_child_process() 함수는 cp를 인자로 입력 받으며 프로세스 디스크립터를 자식 리스트에서 제거한 후 메모리를 해제한다.

exec() 시스템 콜은 자식 프로세스를 생성하고 인자로 입력 받은 cmd_line의 프로그램을 실행시킨다.

wait() 시스템 콜은 인자로 입력 받은 tid를 가진 자식 프로세스가 종료될 때 까지 대기한다.

thread_schedule_tail() 함수는 이전 쓰레드 prev를 인자로 입력 받으며 프로세스를 스케줄링한다.

0개의 댓글