Skip to content
Go back

[OSTEP]Virtualization-Process API

Edit page

UNIX 상에서 프로세스 관리를 위한 시스템 콜, API는 어떤 것을 사용할까.
fork(), exec()를 통해 프로세스를 생성하고, wait()를 통해 자식 프로세스가 끝날 때 까지 대기한다.

fork

fork() 함수는 현재 프로세스를 복사한다. 현재 프로세스(부모)에서 새로운 프로세스인 자식 프로세스를 만든다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("hello world (pid:%d)\n", (int)getpid());
    int rc = fork();
    if (rc < 0) {
        // fork 실패
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // 자식 프로세스
        printf("hello, I am child (pid:%d)\n", (int)getpid());
    } else {
        // 부모 프로세스
        printf("hello, I am parent of %d (pid:%d)\n", rc, (int)getpid());
    }
    return 0;
}

코드 실행 시 hello world (pid:~~)는 한번 밖에 나오지 않는다. 자식 프로세스는 fork()가 실행된 시점 부터 생성된다.

자식 프로세스와 부모 프로세스는 완전히 동일하지는 않다. 할당된 레지스터, 주소 공간, PC 값 등 다르다. 또한 부모 프로세스는 자식 프로세스 PID를 반환하지만 자식 프로세스는 0을 반환한다.

위 코드에서 프로세스 출력 결과 순서는 항상 동일하지 않다. CPU Scheduler 에 따라서 달라 질 수 있다. 비결정성(nondeterminism)으로 인해 멀티 쓰레드 프로그램시 문제가 발생한다.

wait

wait()함수는 말 그대로 대기를 한다. 부모 프로세스가 자식 프로세스가 끝내기 까지 기다리는 것이며 이를 통해 자식 프로세스가 항상 먼저 끝내게 할 수 있다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int
main(int argc, char *argv[])
{
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) {
        // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
	sleep(1);
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
        printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
	       rc, wc, (int) getpid());
    }
    return 0;
}

위 코드 실행 시 결과 확인 가능하다.

exec

exec()fork()와 달리 부모 프로세스가 자식 프로세스를 만들어주는 게 아니라 현재 프로세스가 외부 프로그램을 실행 시킬 때 사용된다. 외부 프로그램 실행 시 현재 프로세스는 외부 프로세스로 코드 세그먼트와 정적 데이터 부분을 덮어쓴다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int
main(int argc, char *argv[])
{
    printf("hello world (pid:%d)\n", (int) getpid());
    int rc = fork();
    if (rc < 0) {
        // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
        char *myargs[3];
        myargs[0] = strdup("wc");   // program: "wc" (word count)
        myargs[1] = strdup("p3.c"); // argument: file to count
        myargs[2] = NULL;           // marks end of array
        execvp(myargs[0], myargs);  // runs word count
        printf("this shouldn't print out");
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
        printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
	       rc, wc, (int) getpid());
    }
    return 0;
}

위 코드를 실행하면 printf("this shouldn't print out"); 이것이 출력되지 않을 것이다.

그럼 왜 이렇게 나눈건가?

프로세스 구성을 위한 특정한 API의 정의가 있는 것은 아니다. 하지만 위와 같이 API를 구성함으로 UNIX Shell을 사용함에 있어 유용하다.

ls -al > test.txt 와 같은 명령어를 실행한다 가정하자. fork로 새로운 프로세스를 만들고 ls -al 실행하기 전 표준 출력을 닫고, test.txt 파일을 연다. 표준 출력을 닫으면 자동으로 미사용 중인 파일을 찾아 연결된다. 그러면 ls -al을 실행하더라도 화면이 아닌 test.txt 파일에 저장된다.

외부 프로그램 실행 전 자식 프로세스를 통해 파일 디스크립터 등 다양한 설정을 할 수 있어 fork()로 자식 프로세스 생성 후 exec()를 실행하는 경우가 많다.

pipe

UNIX의 pipe() 시스템 콜도 위의 ls -al > test.txt 처럼 양쪽 파일 끝에서 연동되게 하는 기능이 있다. 주로 fork(), exec()와 함께 사용된다.

이외 명령어


Edit page
Share this post on:

Previous Post
[OSTEP]Virtualization-CPU Mechanisms 제한적 직접 실행 원리
Next Post
[OSTEP]Virtualization-Process