leebaek

[UNIX프로그래밍] 7강 - 프로세스 생성과 실행 본문

수업 정리/UNIX 프로그래밍

[UNIX프로그래밍] 7강 - 프로세스 생성과 실행

leebaek 2024. 10. 19. 19:51

  • fork 시스템 호출 ( Process의 생성 )
  • exit 시스템 호출
  • exec 시스템 호출
  • exec와 fork를 함께 사용
  • 프로세스의 동기화
  • zombie와 너무 이른 퇴장

fork 시스템 호출 ( Process 생성 )

fork() : 수행하던 process(부모)의 복사본 process(자식) 생성

- fork() 바로 다음 문장부터 부모 process와 자식 process 동시에 실행

 

- 자식 process는 부모 process의 복제

더보기
  • 모든 변수 값이 그대로 복제
  • fork() 후에 변경된 값은 복제되지 않음 ( 독립적인 메모리 공간을 가짐 )
  • file descriptor도 복제 ( 부모 process에서 오픈한 file 자식 process에게도 open ( 동시에 접근 가능 )
  • 부모와 자식이 file을 공통으로 사용 가능 ( file desciptor가 복제된 경우, 한쪽에서 파일의 위치를 변경하면 다른 쪽 프로세스에도 영향을 미칠 수 있음 ) 

return 값

  • 성공
    • 0 : 자식 프로세스
    • 0보다 큰 값 : 부모 프로세스( process id )
  • 실패
    • -1
    • 실패 원인 : [ 시스템 전체 process의 수 제한 ] [ 한 process가 생성할 수 있는 process 수 제한 ]

> 헤더파일

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

int pid;
pid = fork();

if (pid == 0) {
	// 자식 프로세스
} else if (pid > 0){
	// 부모 프로세스
}

 exit 시스템 호출 ( Process 종료 )

exit() : process 정지 -> open된 file 닫기 -> clean-up-action( 메모리와 기타 자원 정리 )

- 종료 코드 확인: 터미널에서 echo $? 명령을 사용하여 exit()에 전달된 status 값 확인 가능

ex. exit(3);로 종료 한 경우 -> echo $? 입력시, 3 출력

 

> 헤더 파일

#include <stdlib.h>
void exit(int status); // status: 0 :  정상 종료 | 0이 아닌 값 : 오류가 발생했음

exit(0);  // 정상 종료

atexit() : 프로세스가 종료될 때 실행될 함수 등록

- 클린업 함수 등록: 매개변수가 없고 반환값이 없는 함수의 포인터를 받음

- 클린업 순서: 등록된 함수들은 등록된 순서의 역순으로 실행됨

 

> 헤더파일

#include <stdilb.h>
int atexit(void(*func)(void));

void cleanup1() {
    printf("Clean-up action 1\n");
}

void cleanup2() {
    printf("Clean-up action 2\n");
}

atexit(cleanup1);
atexit(cleanup2);

exit(0);  // 정상 종료, exit 시 cleanup2 -> cleanup1 순서로 실행

exec 계열 함수 시스템 호출 ( 새 프로그램 수행 )

- 새로운 프로그램을 현재 실행 중인 프로세스의 주소 공간에 적재하는데 사용

- 기존의 프로세스는 없어지고, 새 프로그램이 기존 프로세스의 프로세스 id를 유지하며 실행됨

 

> 헤더파일 

#include <unistd.h>

 

  execl() : 절대 경로를 사용하여 실행

int execl(const char*path, const char *arg0, ..., const char *argn, (char*) 0);

execl("/bin/ls", "ls", "-l", (char *) 0);

 

  execlp() : PATH 환경 변수에서 경로를 검색하여 실행

int execlp(const char *file, const char *arg0, ..., const char *argn, (char*) 0);

execlp("ls", "ls", "-l", (char *) 0);

 

  execv() : 인수를 배열로 전달하여 실행

int execv(const char *path, char *const argv[]);

char *args[] = {"ls", "-l", NULL};
execv("/bin/ls", args);

 

  execvp() : PATH 환경 변수에서 경로를 검색하고 인수를 배열로 전달

int execvp(const char *file, char *const argv[]);

char *args[] = {"ls", "-l", NULL};
execvp("ls", args);

 

더보기

공통점 )

  • 새로운 프로그램 적재 : 호출된 프로세스의 메모리 공간이 새로운 프로그램으로 덮어씌워짐
    • 호출한 프로세스는 더 이상 존재 X, 새로운 프로그램이 처음부터 실행
  • 프로세스 id 유지: 기존 프로세스 id를 이어 받음
  • 성공 시 반환값 없음: 실패하면 -1 반환
  • fork()와의 차이점 : fork()는 새로운 프로세스를 생성하여 두 프로세스가 병렬로 실행되는 반면, exec 함수는 새로운 프로그램을 기존 프로세스 위에 덮어씌우기 때문에 병렬수행이 아님
더보기

차이점 )

  • 경로 지정 방식:
    • path: execl과 execv는 실행할 프로그램의 절대 경로 또는 상대 경로를 명시
    • file: execlp와 execvp는 프로그램의 파일 이름만 명시 가능
      • $PATH 환경 변수에 지정된 경로들에서 프로그램 검색
      • ex. execvp("ls", ...)은 ls 명령어가 위치한 경로를 $PATH에서 찾아 실행
  • 인수 전달 방식:
    • arg0, arg1, ..., argn: execl과 execlp는 프로그램에 전달될 인수를 각각의 인수로 나열하여 전달
      • arg0: 일반적으로 실행할 프로그램의 이름(경로 없이)을 지정
      • 인수 목록의 마지막: NULL 포인터를 명시하여 끝을 알림
    • argv[]: execv와 execvp는 인수를 배열로 받아 처리
      • 인수들을 배열 형태로 전달
      • 배열의 마지막 요소로 NULL 포인터가 포함되어야 함

 exec와 fork를 함께 사용

#include <unistd.h> 

int main() {
    pid_t pid;
    switch ( pid = fork() ) {
        case -1:
                perror("fork failed");
                exit(1);
                break;
        case 0: 
                execl("bin/ls", "ls", "-1", (char*) 0);
                perror("exec failed");
                exit(1);
                break;
        defalut: 
                wait((int*) 0);
                printf("ls completed\n");
                exit(0);
    }
}

 


wait 시스템 호출 ( 프로세스의 동기화 )

wait() : 하나 이상의 자식 process 수행 시 아무나 하나가 종료되면, 그 자식의 종료 상태를 얻음

- return 값:

  • 성공: 종료된 자식의 id
  • 실패: -1 ( 살아있는 child process가 없는 경우 )

- stauts: 자식 process의 종료 상태를 반환하는 포인터

  • 매크로 함수 사용하여 분석 가능
    • WIFEXITED(status) : 자식 프로세스가 정상 종료했는지 여부를 확인 - statue 값의 하위 8비트가 0이면 정상 종료 ( 종료 여부 )
    • WEXITSTATUS(status) : 자식 프로세스가 exit() 호출로 종료되었을 때, 종료 시 전달된 값을 반환 - status 값의 상위 8비트에 저장됨 ( 종료 코드 )
  • 하위 8비트는 자식 프로세스가 특정 시그널에 의해 종료되었는지, 또는 비정상적인 이유로 종료되었는지 등을 나타냄
  • 상위 8비트는 자식 프로세스가 exit()로 반환한 종료 코드를 나타냅니다.

> 헤더파일

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); // 자식 프로세스의 종료 상태를 알기 위함

int main() {
    pid_t pid;
    int status, exit_status;
    // 새로운 프로세스 생성 (fork)
    if ((pid = fork()) < 0) {
        perror("fork failed");
        exit(1);
    }
    
    if (pid == 0) {  // 자식 프로세스
        sleep(4);  // 4초간 대기
        exit(5);   // 종료 시 상태 5 반환
    } else {       // 부모 프로세스
        if (wait(&status) == -1) {  // 자식 프로세스가 종료될 때까지 기다림
            perror("wait failed");
            exit(2);
        }
        
        // 자식 프로세스가 정상 종료되었는지 확인
        if (WIFEXITED(status)) {
            exit_status = WEXITSTATUS(status);  // 자식이 반환한 종료 코드
            printf("Child exited with status: %d\n", exit_status);
        }
    }
    return 0;
}

waitpid(): 부모 process가 대기할 특정 자식 process의 id를 지정 / -1일 경우 아무 자식 process를 기다림

- return 값:

  • 성공: 종료된 자식의 id
  • 실패: -1 ( 특정 자식 process가 없는 경우 )

- pid: 부모 프로세스가 대기할 특정 자식 프로세스 id 지정

- stauts: 자식 process의 종료 상태를 저장할 포인터

- options: 추가적인 동작을 제어할 수 있는 플래그

  • WNOHANG : 자식 프로세스가 종료되지 않았더라도 즉시 반환 ( with no hang ) / 종료하지 않았으면 0을 return
  • 비동기적으로 자식의 종료를 처리할 수 있음

 

> 헤더파일

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

int main() {
    pid_t pid;
    int status;

    if ((pid = fork()) < 0) {
        perror("fork failed");
        exit(1);
    }

    if (pid == 0) {  // 자식 프로세스
        sleep(4);  // 4초간 대기
        exit(5);   // 종료 시 상태 5 반환
    } else {       // 부모 프로세스
        while (1) {
            // 자식 프로세스가 종료되었는지 확인
            pid_t result = waitpid(pid, &status, WNOHANG);
            if (result == 0) {
                printf("Child process still running\n");
                sleep(1);  // 1초 후 다시 확인
            } else if (result == -1) {
                perror("waitpid failed");
                exit(2);
            } else {
                if (WIFEXITED(status)) {
                    int exit_status = WEXITSTATUS(status);
                    printf("Child exited with status: %d\n", exit_status);
                }
                break;
            }
        }
    }

    return 0;
}

 Zombie와 너무 이른 퇴장

□ Zombie

: 부모 프로세스가 wait를 수행하지 않고 있는 상태에서 자식 프로세스가 퇴장 -> 자식은 zombie가 됨

 

□ 너무 이른 퇴장

:하나 이상의 자식 프로세스가 수행되고 있는 상태에서 부모 프로세스가 퇴장 -> 자식은 init(pid=1)의 자식으로 남게 됨