UNIX 프로세스 생성 관련 API

Next-Step·2023년 5월 23일
0
  • UNIX 는 프로세스 생성을 위해 fork() 와 exec() 시스템 콜을 사용한다.
  • wait()은 프로세스가 자신이 생성한 프로세스가 종료되기를 기다리기 원할 때 사용된다.

fork() 시스템 콜


  • 프로세스 생성에 fork() 시스템 콜이 사용된다.

  • fork() 함수는 호출 시점에서 실행 중인 프로세스의 상태를 복제해 자식 프로세스를 생성한다.

  • 두 프로세스는 동일한 코드와 상태를 공유하지만, 각각의 고유한 프로세스 ID를 가지고 있어 독립적으로 실행을 계속하며, 각각의 다른 작업을 수행할 수 있다.

  • 부모 프로세스는 fork() 호출 이후 자식 프로세스의 PID를 반환받고, 자식 프로세스는 '0'을 반환받는다.

    // 코드예시
    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        pid_t pid = fork();
    
        if (pid == -1) {
            // fork() 호출에 실패한 경우
            fprintf(stderr, "fork failed\n");
            return 1;
        } else if (pid == 0) {
            // 자식 프로세스인 경우
            printf("child process\n");
        } else {
            // 부모 프로세스인 경우
            printf("parent process. Child process ID: %d\n", pid);
        }
    
        return 0;
    }

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 실패 시 종료
            fprintf(stderr, “fork failed\n ”);
            exit(1);
        } else if (rc == 0) { // 자식 프로세스
            printf(“hello, I am child (pid:%d)\n ”, (int) getpid());
        } else {  // 부모 프로세스
            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() 시스템 콜은 자신의 복사본을 생성하여 실행하지만, 복사본이 아닌 다른 프로그램을 실행해야 하는 경우에는 exec() 시스템 콜을 사용해야 한다.

  • exec() 시스템 콜은 실행 파일의 이름과 인자가 주어지면 해당 실행 파일의 코드와 정적 데이터를 읽어 들여 현재 실행 중인 프로세스를 덮어 쓴다. 이 때, 힙과 스택 및 프로그램은 다른 주소 공간들로 새로운 프로그램의 실행을 위해 다시 초기화된다. 그런 다음 운영체제는 프로세스의 argv 같은 인자를 전달하여 프로그램을 실행시킨다.

    #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 실패
          fprintf(stderr, “fork failed\n ”);
          exit(1);
      } else if (rc == 0) { // 자식 프로세스
          printf(“hello, I am child (pid:%d)\n ”, (int) getpid());
          char *myargs[3];
          myargs[0] = strdup(“wc”); // 프로그램
    
          myargs[1] = strdup(“p3.c ”); // 인자
          myargs[2] = NULL; // 배열의 끝 표시
          execvp(myargs[0], myargs); //“wc” 실행
          printf(“this shouldn't print out ”); // 이부분은 exec 가 실행된 후여서 실행되지 않는다.
      } else { // 부모 프로세스
          int wc = wait(NULL);
          printf(“hello, I am parent of %d (wc:%d) (pid:%d)\n ”,
          rc, wc, (int) getpid());
      }
      return 0;
    }
  • 위의 코드 예시 외에도 exec() 시스템 콜은 execl(), execle(), execlp(), execv(), execve()가 존재한다.


이러한 API를 사용하는 이유


  • UNIX 쉘을 구현하기 위해서는 fork()와 exec()을 분리해야 한다. 그래야만 쉘이 fork()를 호출하고 exec()를 호출하기 전에 코드를 실행할 수 있다.

  • 대부분의 경우 쉘은 파일 시스템에서 실행 파일의 위치를 찾고 명령어를 실행하기 위하여 fork()를 호출하여 새로운 자식 프로세스를 만든다. 그런 후 exec()의 변형 중 하나를 호출하여 프로그램을 실행시킨 후 wait()를 호출하여 명령어가 끝나기를 기다린다. 자식 프로세스가 종료되면 쉘은 wait()로부터 리턴하고 다시 프롬프트를 출력하고 다음 명령어를 기다린다.

profile
Step by Step

0개의 댓글