728x90
  • 과제 관련 설명
 

[Krafton Jungle | TIL_22.12.27] Project 2. System Calls 구현 (1) - 개요

1. [Git book] User Memory 시스템 콜을 구현하기 위해서는 가상 주소 공간에 데이터를 읽고 쓰는 방법을 제공해야한다. 인수를 가져올 때는 이러한 기능이 필요하지 않다. 그러나 시스템 콜의 인수로

olive-su.tistory.com

 

  • Process related: halt, exit, exec, wait, fork
  • File related: create, remove, open, filesize, read, write, seek, tell, close

 


🎯 Goal

  • Modified files
    • threads/thread.h
    • threads/thread.c
    • userprog/syscall.c
    • userprog/process.c

 

📝 Functions List

  1. get_child_process

 

  1. fork
  2. wait
  3. exec

 

 


Ⅰ. Hierarchical Process Structure

  • 현재 핀토스에는 프로세스간 부모-자식 관계를 명시하는 정보가 없다.
    → 부모-자식의 구분이 없어서 자식 프로세스의 정보를 알지 못해, 자식의 시작 or 종료 전에 부모 프로세스가 종료되는 현상이 발생할 수 있다.
  • ⭐ 프로세스의 정보에 부모와 자식 필드를 추가해야한다!
  • 💡 핀토스의 프로세스는 한 개의 스레드로 구성되기 때문에 struct thread 에 필요한 필드를 정의 한다.

 

  • 기존 핀토스의 user program 실행 상황

  • 새 스레드를 생성하고 준비 큐에 들어간 뒤, process_wait로 넘어가면 return -1 로 바로 리턴되게끔 되어 있다.
  • ∴ process_wait를 수정해줘야 한다.

 

  • 부모 프로세스에서 자식 프로세스를 관리하는 방법은 리스트를 이용한다.
    • 핀토스의 리스트 = 연결 리스트 방식

 

  • 인터럽트 프레임 : 인터럽트 호출시, 이전 작업하던 컨텍스트 정보를 인터럽트 스택에 저장하는 구조체

 

  • 부모 프로세스와 자식 프로세스 간의 실행관계

  • 부모 프로세스는 자식 프로세스를 생성하고 실행한다.
  • 자식-부모 스레드 관계를 구현하기 위해서는 fork 가 선행되어야한다.
  • 실행될 프로그램으로 인자들을 넘겨준다.
  • 새 자식 프로세스의 pid 를 리턴한다.
  • 만약 프로그램 로드 또는 프로세스 생성 실패시, -1을 리턴한다.
  • ⭐ 부모 프로세스가 exec 시스템 콜을 호출하면, 부모 프로세스는 자식 프로세스가 생성되고 완전히 실행가능한 상태로 로드될 때까지 기다려야한다.

 

  • 세마포어를 이용해, 부모 프로세스는 자식 프로세스의 생성과 로드를 기다린다.
    • 자식 프로세스의 실행을 위해 sema_down (잠시 대기)
    • 자식 프로세스의 생성, 실행 이후 다시 sema_up (부모 프로세스 다시 실행)

 

 


Ⅱ. 유저 프로그램 실행 흐름

1. main : 메인 함수 실행

/* threads/init.c */

/* Pintos main program. */
int
main (void)

 

2. run_actions : 커맨드 라인으로 들어온 명령 처리

/* threads/init.c -> main */

/* Run actions specified on kernel command line. */
run_actions (argv);

 

3. run_task : argv를 파싱해서 첫 번째 인자가 “run”(프로그램 실행)이면 run_task 수행

/* threads/init.c -> run_actions */

static const struct action actions[] = {
    {"run", 2, run_task},
    ...
}

 

4. process_create_initd : 유저 프로그램이면 process_wait 수행

/* threads/init.c -> run_task */

const char *task = argv[1]; // argv[0] = "run"
...
process_wait (process_create_initd (task));

 

5. thread_create : 실행 파일을 이름으로 한 새 스레드 생성

  • 파일이름으로 로드된 유저 프로그램을 시작한다. 새 스레드는 process_create_initd() 가 리턴되기 전에 스케줄 되거나 종료될 수 있다. 스레드가 정상적으로 생성되었을 때는 생성된 스레드의 id를, 실패했을 때는 TID_ERROR(-1)을 리턴한다. 해당 함수는 반드시 한번만 실행되어야한다.
/* userprog/process.c -> process_create_initd */

/*
 * 파일 이름으로 기존에는 커맨드 라인 전체로 들어오는데, 이때 커맨드라인을 파싱하여
 * 공백을 기준으로 첫 번째 문자열(실행 파일)로 스레드가 생성되도록 바꿔준다.
*/

tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
return tid; // process_wait의 인자로 전달된다.

 

6. initd : 유저 프로세스를 처음 실행할 때 수행된다.

/* userprog/process.c -> initd */

process_init (); // 프로세스 초기화

if (process_exec (f_name) < 0) // 해당 프로세스 실행
    PANIC("Fail to launch initd\n");

 

7. process_wait : 4.의 process_create_initd 으로 부터 생성된 tid 값을 가지고 wait를 수행한다.

/* threads/init.c -> run_task */

const char *task = argv[1]; // argv[0] = "run"
...
process_wait (process_create_initd (task)); // process_create_initd (task) returns tid.

 

 


Ⅲ. 프로세스 계층 구조 구현

  • 부모-자식 프로세스를 표현하기 위해 struct thread 를 수정해준다.
/* threads/thread.h */

struct thread {

    ...

#ifdef USERPROG

    ...

    // Ref_92p. Hanyang Univ
    struct intr_frame parent_if; // 부모 프로세스의 인터럽트 프레임
    struct list child_list; // 자식 프로세스 리스트
    struct list_elem child_elem; // 자식 프로세스 리스트의 element

    struct file *running; // 현재 실행 중인 파일
    int exit_status; // 프로세스의 종료 유무 확인

    struct semaphore fork_sema; // fork가 완료될 때 sema_up 수행
    struct semaphore free_sema; // 자식 프로세스가 종료될 때까지 부모 프로세스는 대기
    struct semaphore wait_sema; // 자식 프로세스가 종료될 때까지 대기. 종료 상태 저장


#endif

    ...

};
  • parent_if : 자식 프로세스의 로드 및 실행 후, 다시 부모 프로세스를 실행시키기 위한 부모 프로세스의 컨텍스트 정보를 담는다.
  • child_list : 자식 프로세스들의 정보를 리스트에 담는다.
  • running : 현재 실행중인 파일을 저장한다.
  • exit_status : 자식 프로세스의 종료 상태를 저장한다.
  • fork_sema : process_fork 에서 자식 프로세스의 로드를 기다릴 때 사용한다.
  • free_sema : process_wait 에서 자식 프로세스의 종료 상태를 받기 위해 사용한다.
  • wait_sema : process_wait 에서 자식 프로세스가 종료되길 기다릴 때 사용한다.

 

/* threads/thread.c */

static void
init_thread (struct thread *t, const char *name, int priority) {

    ...

    t->exit_status = 0;
    t->running = NULL;

    /* 자식 리스트 및 세마포어 초기화 */
    list_init(&t->child_list);
    sema_init(&t->wait_sema,0);
    sema_init(&t->fork_sema,0);
    sema_init(&t->free_sema,0);
}
  • 수정한 struct thread 에 대한 초기화 작업을 수행한다.
  • ⭐ 이때 세마포어의 값이 “0” 임을 유의한다!
    → 부모 프로세스에서 먼저 호출하기 때문에 부모 프로세스를 대기(wait 상태) 시키기 위해선 “0”으로 만들어둬야한다.

 


Ⅳ. Help Functions(추가 함수)

1. get_child_process

👉 자식 리스트를 검색하여 해당 프로세스 디스크립터를 리턴한다.

 

/* userprog/process.c */

struct thread *get_child_process(int pid){
    struct thread *curr = thread_current();
    struct list *child_list = &curr->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);
        if (t->tid == pid) // 해당 pid가 존재하면 프로세스 디스크립터 리턴
            return t;
    }
    return NULL; // 리스트에 존재하지 않으면 NULL
}
  • process_fork 를 통해 부모 프로세스에서 자식 프로세스를 복제하며 부모 프로세스의 child_list 에 자식 프로세스의 element 값을 넣어준다.
  • 이후, process_waitprocess_forkload 과정 이전에 tid로 받은 프로세스가 실제 부모의 자식 프로세스가 맞는지 검증하는 과정을 거친다.
  • [Hanyang Univ] ‘get_child_process’ 함수

 


Ⅴ. 프로세스 관련 시스템 콜 구현

  • 기존 프로세스 구조는 부모/자식 구분 x → 자식 시작 종료 전에 부모 프로세스가 종료되는 현상 발생

 

  • 📢 프로세스 계층 구조 구현 필요!
    • struct thread에 부모와 자식 필드를 추가하고 이를 관리하는 함수를 구현한다.
    • 부모 프로세스를 가리키는 포인터 추가
    • 자식 프로세스 : 리스트로 구현
    • 자식 리스트에서 원하는 프로세스를 검색, 삭제하는 함수 구현

 

1. fork

  • 현재 프로세스의 복제본으로 THREAD_NAME이라는 이름을 가진 새 프로세스를 생성한다.
  • ⭐ 자식 프로세스에 부모 프로세스의 %RBX, %RSP, %RBP, %R12 ~ %R15 레지스터 값을 복사한다.
  • 부모 프로세스는 자식 프로세스의 pid를 리턴한다.
  • 자식 프로세스는 0을 리턴한다.
  • 자식 프로세스는 부모 프로세스의 파일 디스크립터와 가상 메모리 공간을 복제해야한다.
    • ⭐ 부모 → 자식 : 파일 디스크립터 테이블, 페이지 테이블 복제
  • 부모 프로세스는 자식 프로세스가 성공적으로 복제되었는지를 알기 전까지 리턴되면 안된다.
    • 자식 프로세스가 복제를 실패하면 TID_ERROR(-1)을 리턴한다.
  • threads/mmu.c 에 구현된 pml4_for_each() 는 페이지 테이블 구조를 포함한 전체 사용자 메모리 공간을 복사하지만 전달된 함수 중 pte_for_each_func 의 누락된 부분을 채워야한다.

 

1. fork(system call)

/* userprog/syscall.c */

pid_t fork (const char *thread_name, struct intr_frame *f) {
    check_address(thread_name);
    return process_fork(thread_name, f);
}
  • ⭐ “Gitbook”과 “lib/user/syscall.h”에 정의된 fork 시스템 콜의 원형은
    pid_t fork (const char *thread_name); 형태이지만 “userprog/process.c” 에 구현된 process_fork 의 형태가
    tid_t process_fork (const char *name, struct intr_frame *if_);이므로 fork의 두 번째 인자로 intr_frame 를 추가해준다.

 

2. process_fork

/* userprog/process.c */

tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
    /* Clone current thread to new thread.*/

    struct thread *curr = thread_current();

    memcpy(&curr->parent_if, if_, sizeof(struct intr_frame)); // 전달받은 intr_frame을 parent_if필드에 복사한다.
    // ↳ '__do_fork' 에서 자식 프로세스에 부모의 컨텍스트 정보를 복사하기 위함(부모의 인터럽트 프레임을 찾는 용도로 사용)

    tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, curr); // __do_fork를 실행하는 스레드 생성, 현재 스레드를 인자로 넘겨준다.
    if (tid == TID_ERROR)
        return TID_ERROR;

    struct thread *child = get_child_process(tid);

    sema_down(&child->fork_sema); // 자식 프로세스가 로드될 때까지 부모 프로세스는 대기한다.
    if (child->exit_status == TID_ERROR)
        return TID_ERROR;

    return tid; // 부모 프로세스의 리턴값 : 생성한 자식 프로세스의 tid
}
  • curr : 현재 실행 중인 프로세스(부모)
  • memcpy
    • 현재 실행 중인 프로세스의 컨텍스트 정보를 담은 intr_frame를 parent_if로 복사한다.
    • 🙋‍♂️ 부모 프로세스(curr)의 필드 중 parent_ifif_ 를 복사한다?
      thread_create 에서 넘겨주는 4번째 인자(가 curr 인데, 이는 다시 __do_fork 의 첫 번째 인자로 사용된다.
      → 해당 함수(__do_fork) 에서 memcpy로 복사한 &curr->parent_if 를 꺼내와서 다시 자식 프로세스의 인터럽트 프레임에 복사한다.(아래 설명 참조)

 

😵‍💫 실행 순서가 제대로 이해가 안돼서 코드 한 줄 한 줄 어떤 식으로 진행되는지 함수 타고 들어가기를 반복했다.

⭐ 실행 순서 함수명 보고 잘 따라오기!!

 

3. thread_create

🎪 우선 thread_create 함수 내부로 진입한다.

/* threads/thread.c */

tid_t
thread_create (const char *name, int priority, thread_func *function, void *aux) {
    struct thread *t;
    tid_t tid;

    ...

    /* 현재 스레드의 자식 리스트에 새로 생성한 스레드 추가 */
    struct thread *curr = thread_current();
    list_push_back(&curr->child_list,&t->child_elem);

    /* Call the kernel_thread if it scheduled.
     * Note) rdi is 1st argument, and rsi is 2nd argument. */
    t->tf.rip = (uintptr_t) kernel_thread;
    t->tf.R.rdi = (uint64_t) function;
    t->tf.R.rsi = (uint64_t) aux;
    t->tf.ds = SEL_KDSEG;
    t->tf.es = SEL_KDSEG;
    t->tf.ss = SEL_KDSEG;
    t->tf.cs = SEL_KCSEG;
    t->tf.eflags = FLAG_IF;

    ...

    return tid;
}
  • 실질적인 자식 프로세스를 생성하는 부분으로, 현재 fork 를 호출한 프로세스(running thread)가 부모 프로세스이므로 해당 프로세스의 자식 리스트에 새로 생성한 자식 프로세스(struct thread *t)를 추가한다.
  • 자식프로세스를 생성하고 난 이후의 작업을 수행하기 위해 레지스터 %rdi%rsi 값을 설정해준다.
    • 💡 RSI(Extended Source Index) / RDI(Extended Destination Index) : 확장 소스 인덱스, 확장 목적지 인덱스 레지스터. 각각 메모리 출발지와 목적지를 나타낸다.

 

process_fork 내부의 동작과정

tid_t tid = thread_create(name, PRI_DEFAULT, __do_fork, curr);
  1. thread_create 로 자식 프로세스를 생성하고 tid를 리턴받는다.
    • Point 1. 이때, 인자로 __do_fork 함수와 curr 을 함께 넘겨줬다.
    • Point 2. thread_create 내부에서 %rdi__do_fork 를 지정해줬다.
    🙌🏻 tid를 성공적으로 리턴받으면, 자식 프로세스 생성까지 완료된 상태이다.
  2. thread_create 이후 부분들을 수행한다.
  3. sema_down(&child->fork_sema);
    • 해당 자식 프로세스가 성공적으로 로드될 때까지 부모 프로세스는 대기한다.
    • threads/thread.c -> init_thread 에서 fork_sema 의 값을 0으로 설정해줬으므로 부모 프로세스는 wait_list로 들어가게된다.
    • ⭐ 이후 자식 프로세스가 로드된 후, sema_up을 한 뒤에서야 해당 부모 프로세스가 Ready 상태로 전환된다!
  4. 실행중인 프로세스가 자식 프로세스로 전환이 됐으므로, 이제서야 __do_fork 를 수행한다.
    • %rsicurr(aux, 부모 프로세스)를, %rdi__do_fork (function)를 넣어준 결과이다.
      curr 프로세스가 실행을 마친 뒤, __do_fork 가 실행된다.

 

  • 실제로 print문을 넣어, TC ‘fork-read’를 돌려보면서 함수의 실행 순서를 파악해봤다.
Executing 'fork-read':
thread_create calls!
(fork-read) begin
(fork-read) open "sample.txt"
process_fork calls!                   # 1. process_fork 호출
thread_create calls!                  # 2. thread_create 호출
before sema_down(fork_sema)!          # 3. sema_down 수행
__do_fork calls!                      # 4. __do_fork 호출
(fork-read) child run
(fork-read) Child: pintos is funny!
(fork-read) end
child: exit(0)
(fork-read) Parent success
(fork-read) end
fork-read: exit(0)
Execution of 'fork-read' complete.

 

4. __do_fork

/* userprog/process.c */

static void
__do_fork (void *aux) {
    struct intr_frame if_;
    struct thread *parent = (struct thread *) aux; // 부모 프로세스
    struct thread *current = thread_current (); // 새로 생성된 자식 프로세스
    /* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
    struct intr_frame *parent_if;
    bool succ = true;

    parent_if = &parent->parent_if; // process_fork에서 복사 해두었던 intr_frame
    /* 1. Read the cpu context to local stack. */
    memcpy (&if_, parent_if, sizeof (struct intr_frame));

    if_.R.rax = 0; // fork 시스템 콜의 결과로 자식 프로세스는 0을 리턴해야하므로 0을 넣어준다.

    /* 2. Duplicate PT */
    current->pml4 = pml4_create(); // 부모의 pte를 복사하기 위해 페이지 테이블을 생성한다.
    if (current->pml4 == NULL)
        goto error;

    process_activate (current);
#ifdef VM
    supplemental_page_table_init (&current->spt);
    if (!supplemental_page_table_copy (&current->spt, &parent->spt))
        goto error;
#else
    // "pml4_for_each" : Apply FUNC to each available pte entries including kernel's.
    if (!pml4_for_each (parent->pml4, duplicate_pte, parent)) // "duplicate_pte" : 페이지 테이블을 복제하는 함수(부모 -> 자식) 
        goto error;
#endif

    /* TODO: Your code goes here.
     * TODO: Hint) To duplicate the file object, use `file_duplicate`
     * TODO:       in include/filesys/file.h. Note that parent should not return
     * TODO:       from the fork() until this function successfully duplicates
     * TODO:       the resources of parent.*/
    /*
     * 파일 객체를 복제하려면 'file_duplicate'를 사용하라.
     * 이 함수가 부모의 리소스를 성공적으로 복제할 때까지 부모 프로세스는 fork로 부터 리턴할 수 없다.
    */
    if (parent->next_fd == FDCOUNT_LIMIT)
        goto error;

    // 부모의 fdt를 자식의 fdt로 복사한다.
    for (int fd = 2; fd < FDCOUNT_LIMIT; fd++) {
        struct file *file = parent->fdt[fd];
        if (file == NULL) // fd엔트리가 없는 상태에는 그냥 건너뛴다.
            continue;
        current->fdt[fd] = file_duplicate (file);
    }

    current->next_fd = parent->next_fd; // 부모의 next_fd를 자식의 next_fd로 옮겨준다.
    sema_up(&current->fork_sema); // fork가 정상적으로 완료되었으므로 현재 wait중인 parent를 다시 실행 가능 상태로 만든다. 

    /* Finally, switch to the newly created process. */
    if (succ)
        do_iret (&if_);
error: // 제대로 복제가 안된 상태 - TID_ERROR 리턴 
    sema_up(&parent->fork_sema);
    exit(TID_ERROR);
    // thread_exit (); // origin_code
}
  • __do_fork 의 인자로 thread_create 를 하면서 4번째 인자로 넣은 curr (aux) 가 들어가게 된다.

 

  1. 자식 프로세스에 부모의 인터럽트 프레임(실행 컨텍스트)를 복사해서 넣어준다.
  2. 부모 프로세스의 페이지 테이블을 자식 프로세스의 페이지 테이블로 복제한다.
    (duplicate_pte _”filesys/filesys.h”) 사용
  3. 부모의 fdt와 next_fd를 복사하여 자식 프로세스에게 설정해준다.
  4. 이후, 부모 프로세스에서 자식 프로세스로의 모든 복사 과정이 완료되었으므로 sema_up 을 수행한다.
    • ⭐ 반드시 process_fork 에서의 세마포어와 동일한 세마포어에 대해 Up 연산을 수행해주어야한다!
  5. do_iret (&if_); : sema_up 을 진행해서 부모 프로세스가 다시 Ready 상태가 되었으므로 컨텍스트 스위치를 수행한다.

 


2. wait

  • 자식 프로세스(pid)를 기다리고 자식의 종료 상태(exit_status)를 가져온다.
    • 만약 아직 자식(pid)이 살아있으면 종료될 때까지 기다린다.
  • 만약 자식(pid)이 exit()를 호출하지 않고 커널에 의해 종료된다면 wait는 -1을 리턴해야한다.
  • 부모 프로세스가 이미 종료된 자식 프로세스를 기다리는 건 합당하지만 커널은 부모에게 자식의 종료 상태 또는 커널에 의해 종료되었다는 사실을 알려야한다.

 

  • wait 가 -1을 리턴해야하는 상황
    1. 자식(pid)는 호출하는 부모 프로세스의 직속 자식을 참조하지는 않는다.
      • fork 호출 후, 성공적으로 pid를 반환받은 경우에만 직속 자식이다.
      • 자식들은 상속되지 않는다.
      • 만약 부모 자식 관계가 아래와 같다면 (’p’ : parent, ‘c’ : child)
      • A(p) → B(c)
      • B(p) → C(c)
        A(p) → C(c)는 성립하지 않는다.
      • ⭐ 프로세스 A가 wait(C)를 호출하는 건 실패해야한다.*
      • ⭐ 프로세스 B가 죽은 경우에도 C를 A에 할당하지 않는다!*
    2. 한 프로세스는 어느 주어진 자식에 대해 최대 한번만 wait 를 할 수 있다.

 

  • 프로세스는 자식을 얼마든지 낳을 수 있고 자식들은 어떤 순서로도 wait 할 수 있다.자식 전부에 대해 기다리지 않고 종료하기도 한다.
  • wait 가 발생할 수 있는 모든 방법에 대해 고려하고 설계해야한다.
  • 모든 프로세스의 자원들(eg. struct thread)은 부모가 해당 프로세스를 기다리던지, 부모가 먼저 또는 나중에 종료되던지에 관계없이 해제되어야한다.

 

  • 초기 프로세스가 종료되기 전에 pintos가 종료되지 않도록 해야한다.
    • 💡 wait 구현전에는 무한루프로 구현하여, 프로세스가 종료되지 않도록한다.

 

1. wait (systemcall)

/* userprog/syscall.c */

int wait (tid_t pid){
    process_wait(pid);
}
  • 대기하려는 자식 프로세스의 pid를 인자로 넘겨준다.

 

2. process_wait

/* userprog/process. */

process_wait (tid_t child_tid UNUSED) {
    struct thread *child = get_child_process(child_tid);

    if(child == NULL) // 해당 자식이 존재하지 않는다면 -1 리턴
        return -1;

    sema_down(&child->wait_sema); // 자식 프로세스가 종료할 때까지 대기한다.
    // 컨텍스트 스위칭 발생

    int exit_status = child->exit_status; // 자식으로 부터 종료인자를 전달 받고 리스트에서 삭제한다.
    list_remove(&child->child_elem);

    sema_up(&child->free_sema); // 자식 프로세스 종료 상태를 받은 후 자식 프로세스를 종료하게 한다.

    return exit_status;
}
  • 해당 자식 프로세스가 성공적으로 종료될 때까지 부모 프로세스는 대기한다.
  • threads/thread.c -> init_thread 에서 wait_sema 의 값을 0으로 설정해줬으므로 부모 프로세스는 wait_list로 들어가게된다.
  • ⭐ 이후 자식 프로세스가 종료(process_exit)된 후, sema_up을 한 뒤에서야 해당 부모 프로세스가 Ready 상태로 전환된다!

 

  • 자식 프로세스가 성공적으로 종료됐다면 부모 프로세스의 자식 프로세스 리스트에서 해당 자식 프로세스를 제거한다.

 

  • free_sema 는 자식 프로세스의 종료 상태를 받고 자식 프로세스를 종료해야하므로 세마포어를 이중적으로 걸어줬다고 보면 된다.

 

/* userprog/process. */

void
process_exit (void) {
    struct thread *curr = thread_current ();

    for (int i = 0; i < FDCOUNT_LIMIT; i++) // 프로세스 종료 시, 해당 프로세스의 fdt의 모든 값을 0으로 만들어준다.
        close(i);
    palloc_free_multiple(curr->fdt, FDT_PAGES); // fd table 메모리 해제

    file_close(curr->running); // 현재 프로세스가 실행중인 파일을 종료한다.    

    process_cleanup ();

    sema_up(&curr->wait_sema); // 부모 프로세스가 자식 프로세스의 종료상태를 확인하게 한다.
    sema_down(&curr->free_sema); // 부모 프로세스가 자식 프로세스의 종료 상태를 받을때 까지 대기한다. 
}
  • 자식 프로세스의 종료 시, 부모 프로세스에게 자식 프로세스의 종료를 알릴 수 있게끔 세마포어 연산을 수행한다.

 


3. exec

  • 현재 실행중인 프로세스를 cmd_line 에 지정된 실행 파일로 변경하고 인수들을 전달한다.
  • return
    • 성공 시, 리턴 x [Hanyang Univ : pid_t return]
    • 실패 시, -1
  • 해당 함수는 exec 를 호출한 스레드의 이름을 변경하지는 않는다.
  • 파일 디스크립터는 exec 호출 시, 열린 상태로 존재한다.

 

1. exec(system call)

/* userprog/syscall.c */

int exec (const char *cmd_line){
    check_address(cmd_line);

    int size = strlen(cmd_line) + 1; // 파일 사이즈(NULL 포함하기 위해 +1)
    char *fn_copy = palloc_get_page(PAL_ZERO);

    if (fn_copy == NULL)// 메모리 할당 불가 시
        exit(-1);
    strlcpy(fn_copy, cmd_line, size);

    if (process_exec(fn_copy) == -1) // [process_exec] 'load (file_name, &_if);' -> load 실패 시
        return -1;

    return 0;
}

 

2. process_exec

/* userprog/process.c */

int
process_exec (void *f_name) {
    char *file_name = f_name; // 커맨드 라인 전체가 인자로 들어온다.
    bool success;

    struct intr_frame _if;
    _if.ds = _if.es = _if.ss = SEL_UDSEG;
    _if.cs = SEL_UCSEG;
    _if.eflags = FLAG_IF | FLAG_MBS;

    int argc = 0;
    char *argv[128]; // 64bit computer(uint64_t : 8byte)

    /* We first kill the current context */
    process_cleanup ();

    /* 커맨드 라인을 파싱한다. */
    argument_parse(file_name, &argc, argv);

    /* And then load the binary */
    success = load (file_name, &_if);

    /* If load failed, quit. */
    if (!success){
        palloc_free_page (file_name);
        return -1;
    }

    argument_stack(argc, argv, &_if); // argc, argv로 커맨드 라인 파싱
    // hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true); // 메모리에 적재된 상태 출력

    /* Start switched process. */
    do_iret (&_if);
    NOT_REACHED ();
}
  • 커맨드 라인 전체를 인자로 받았으므로, 커맨드 라인 문자열에 대한 파싱을 진행해, 실행파일(argv[0])을 분리해낸다.

 

/* userprog/process.c */

static bool
load (const char *file_name, struct intr_frame *if_) {
    struct thread *t = thread_current ();
    struct ELF ehdr;
    struct file *file = NULL;
    off_t file_ofs;
    bool success = false;
    int i;

    ...

    t->running = file;

    file_deny_write(file); // 현재 오픈한 파일에 접근 못하게 함

    ...

    return success;
}
  • exec를 통해 파일을 실행하게 되면 해당 프로세스에 해당하는 파일은 열린 상태로 있으므로 struct thread 의 running 필드에 해당 파일을 추가한다.
    • process_exit 에서 해당 파일에 대해 닫아주는 작업을 수행한다.
      • 또, 현재 실행하기위해 오픈한 파일에 대해 접근하지 못하게 하기 위해 file_deny_write를 이용해 접근 제한을 걸어준다.

 

 

728x90