PintOS PJT2 - 부모와 자식 프로세스의 구분

김수환·2024년 11월 17일

PintOS

목록 보기
8/15

부모와 자식을 나누는 이유

운영체제에서 부모-자식 관계를 기반으로 프로세스를 나누는 것은 프로세스 관리와 시스템 효율성을 높이는 데 중요한 설계 원칙입니다. 다음은 그 이유를 설명한 내용입니다.


1. 프로세스 생성과 복제

운영체제에서 새 프로세스를 생성할 때, 기존 프로세스(부모)의 구조를 기반으로 복제하여 새로운 프로세스(자식)를 생성합니다.

  • 이유:
    • 새로운 프로세스를 처음부터 설정하는 것보다 기존 프로세스를 복제하는 것이 효율적입니다.
    • 부모 프로세스의 메모리, 파일 디스크립터, 환경 설정 등을 그대로 복사하여 초기 상태를 구성합니다.
    • 이는 프로세스 생성 속도를 높이고 초기화를 단순화합니다.

2. 프로세스 간 독립성과 협력

부모와 자식은 서로 독립적인 실행 흐름을 가질 수 있지만, 필요할 때 협력할 수도 있습니다.

  • 독립성:
    • 각 프로세스는 자신만의 메모리 공간과 실행 흐름을 가집니다. 따라서 자식이 종료되어도 부모에게 영향을 미치지 않습니다.
  • 협력:
    • 부모는 자식 프로세스의 상태를 확인하고, 필요하면 자식을 기다리거나 종료시킬 수 있습니다.
    • 이를 통해 프로세스 간 효율적인 작업 분배와 통신이 가능합니다.

3. 시스템 리소스 관리

부모와 자식 관계를 설정하면 리소스 관리가 용이합니다.

  • 부모 프로세스가 자식 프로세스를 생성했을 때, 자식이 사용하는 자원(메모리, 파일, 디스크 등)은 부모와 연관되므로 추적이 쉽습니다.
  • 프로세스 종료 시:
    • 자식 프로세스가 종료되면 부모는 해당 프로세스의 종료 상태를 확인할 수 있고, 운영체제는 자식이 사용하던 자원을 회수합니다.

4. 에러 처리와 복구

  • 부모와 자식 구조를 사용하면 프로세스에서 발생하는 문제를 체계적으로 처리할 수 있습니다.
  • 예를 들어, 자식 프로세스에서 오류가 발생해 종료되더라도 부모 프로세스가 계속 실행되면서 상태를 모니터링하거나 자식을 다시 생성할 수 있습니다.

5. 작업 분할과 병렬성

부모와 자식 프로세스를 분리하면 작업을 병렬적으로 처리할 수 있습니다.

  • 부모 프로세스는 자식이 수행하는 작업을 기다리거나 다른 작업을 병렬로 실행할 수 있습니다.
  • 예를 들어, 부모 프로세스는 사용자 입력을 처리하고, 자식 프로세스는 파일을 읽거나 네트워크 요청을 처리하는 등 역할을 나눌 수 있습니다.

6. 안전성과 보안

부모-자식 관계는 프로세스 간 메모리 격리를 유지하면서도 제한적인 데이터 공유를 허용합니다.

  • 메모리 격리:
    • 자식 프로세스는 복제된 메모리를 사용하므로, 부모의 메모리를 직접 변경할 수 없습니다.
    • 이로 인해 프로세스 간 충돌을 방지하고, 보안성을 유지할 수 있습니다.
  • 필요한 데이터만 공유:
    • 부모는 자식과 공유할 데이터(예: 환경 변수, 파일 디스크립터)만 전달할 수 있습니다.

운영체제에서의 활용 사례

  1. fork() 시스템 호출:
    • 부모 프로세스의 복사본으로 자식 프로세스를 생성합니다.
    • 유닉스/리눅스 기반 시스템에서 새로운 프로세스 생성에 사용됩니다.
  2. 웹 서버:
    • 부모 프로세스가 클라이언트 요청을 수신하고, 각 요청을 처리하기 위해 자식 프로세스를 생성합니다.
    • 이를 통해 다수의 클라이언트를 병렬로 처리할 수 있습니다.
  3. 테스트 환경:
    • 부모 프로세스는 자식을 생성하여 특정 작업을 테스트합니다. 자식 프로세스가 종료되더라도 부모는 안전하게 계속 실행됩니다.
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 = &parent->parent_if;
    bool succ = true;

    /* 1. Read the cpu context to local stack. */
    memcpy (&if_, parent_if, sizeof (struct intr_frame));
    if_.R.rax = 0;  // 자식 프로세스의 return값 (0)

    /* 2. Duplicate PT */
    current->pml4 = pml4_create();
    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
    // Page Table 통째로 복제 
    if (!pml4_for_each (parent->pml4, duplicate_pte, parent))  
        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.
     * TODO: Hint) 파일 객체를 복제하려면 include/filesys/file.h에서 `file_duplicate`를 사용하세요.
         이 함수가 부모의 리소스를 성공적으로 복제할 때까지 부모는 fork()에서 반환되어서는 안 됩니다. */
    if (parent->fd_idx >= FDCOUNT_LIMIT)
        goto error;

    /** #Project 2: Extend File Descriptor - fd 복제 */
    struct dict_elem dup_file_dict[DICTLEN];
    int dup_idx = 0;

    current->fd_idx = parent->fd_idx;  // fdt 및 idx 복제
    struct file *file;
    for (int fd = 0; fd < FDCOUNT_LIMIT; fd++) {
        file = parent->fdt[fd];
        if (file == NULL)
            continue;

        bool is_exist = false;

        for (int i = 0; i <= dup_idx; i++) {
            if (dup_file_dict[i].key == file) {
                current->fdt[fd] = file_duplicate(file);
                is_exist = true;
                break;
            }
        }

        if (is_exist)
            continue;

        if (file > STDERR)
            current->fdt[fd] = file_duplicate(file);
        else
            current->fdt[fd] = file;

        if (dup_idx < DICTLEN) {
            dup_file_dict[dup_idx].key = file;
            dup_file_dict[dup_idx++].value = current->fdt[fd];
        }
        /** -------------------------------------------------------------- */
    }

    sema_up(&current->fork_sema);  // fork 프로세스가 정상적으로 완료됐으므로 현재 fork용 sema unblock

    process_init ();

    /* Finally, switch to the newly created process. */
    if (succ)
        do_iret (&if_);  // 정상 종료 시 자식 Process를 수행하러 감

error:
    sema_up(&current->fork_sema);  // 복제에 실패했으므로 현재 fork용 sema unblock
    exit(TID_ERROR);
}
#ifndef VM
/* Duplicate the parent's address space by passing this function to the
 * pml4_for_each. This is only for the project 2. */
/* 이 함수를 pml4_for_each에 전달하여 상위 주소 공간을 복제합니다.
 * 이는 프로젝트 2에만 해당됩니다. */
static bool 
duplicate_pte (uint64_t *pte, void *va, void *aux) {
    struct thread *current = thread_current ();
    struct thread *parent = (struct thread *) aux;
    void *parent_page;
    void *newpage;
    bool writable;

    /* 1. TODO: If the parent_page is kernel page, then return immediately. */
    if (is_kernel_vaddr(va))
        return true;

    /* 2. Resolve VA from the parent's page map level 4. */
    parent_page = pml4_get_page (parent->pml4, va);
    if (parent_page == NULL)
        return false;

    /* 3. TODO: Allocate new PAL_USER page for the child and set result to
     *    TODO: NEWPAGE. */
    newpage = palloc_get_page(PAL_ZERO);
    if (newpage == NULL)
        return false;

    /* 4. TODO: Duplicate parent's page to the new page and
     *    TODO: check whether parent's page is writable or not (set WRITABLE
     *    TODO: according to the result). */
    memcpy(newpage, parent_page, PGSIZE);
    writable = is_writable(pte);

    /* 5. Add new page to child's page table at address VA with WRITABLE
     *    permission. */
    if (!pml4_set_page (current->pml4, va, newpage, writable)) {
        /* 6. TODO: if fail to insert page, do error handling. */
        return false;
    }
    return true;
}
#endif
profile
juniorDev

0개의 댓글