리눅스 시스템 및 네트워크 프로그래밍/시스템 프로그래밍

리눅스 프로세스 생성과 관리: fork, exec, wait의 동작 원리와 활용

ROBL 2025. 2. 3.
728x90
반응형

리눅스에서 프로세스는 프로그램을 실행하는 독립적인 실행 단위입니다. 시스템의 모든 작업은 프로세스를 통해 수행되며, 새로운 프로세스를 생성하고 관리하는 것은 시스템 프로그래밍에서 필수적인 개념입니다.

이번 글에서는 리눅스에서 프로세스를 생성하고 관리하는 주요 시스템 콜(fork, exec, wait, exit 등)의 동작 원리를 설명하고, 이를 활용하는 방법을 코드와 함께 다뤄보겠습니다.


1. 프로세스 개념

프로세스(Process)는 실행 중인 프로그램을 의미하며, 운영체제에서 CPU와 메모리를 할당받아 독립적으로 동작합니다.

  • PID(Process ID): 각 프로세스는 고유한 ID를 가집니다.
  • 부모 프로세스와 자식 프로세스: 한 프로세스가 다른 프로세스를 생성하면, 원래의 프로세스를 부모 프로세스, 새로 생성된 프로세스를 자식 프로세스라고 합니다.
  • 프로세스 상태: 실행(Running), 대기(Waiting), 종료(Terminated) 등의 상태를 가질 수 있습니다.

리눅스에서는 fork(), exec(), wait(), exit() 등의 시스템 콜을 통해 프로세스를 생성하고 관리합니다.


2. 프로세스 생성: fork()

fork() 시스템 콜은 현재 실행 중인 프로세스를 복제하여 새로운 프로세스를 생성합니다.

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

int main() {
    pid_t pid = fork();

    if (pid > 0) {
        printf("부모 프로세스: 자식 PID = %d\n", pid);
    } else if (pid == 0) {
        printf("자식 프로세스 실행 중\n");
    } else {
        perror("fork 실패");
    }
    return 0;
}

fork()의 동작 방식

  1. fork()를 호출하면 부모 프로세스의 메모리를 복사하여 자식 프로세스를 생성합니다.
  2. 부모와 자식 프로세스는 각각 독립적인 실행 흐름을 가집니다.
  3. fork()의 반환 값:
    • 부모 프로세스는 자식의 PID를 반환받습니다.
    • 자식 프로세스는 0을 반환받습니다.
📌 fork()를 여러 번 호출하면 트리 형태로 여러 개의 프로세스가 생성될 수 있습니다.

3. 새로운 프로그램 실행: exec()

자식 프로세스는 exec()를 호출하여 새로운 프로그램을 실행할 수 있습니다.

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

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        execl("/bin/ls", "ls", "-l", NULL);
        perror("exec 실행 실패");
    } else {
        printf("부모 프로세스 실행 중\n");
    }
    return 0;
}

exec()의 동작 원리

  1. exec()는 현재 프로세스를 새로운 프로그램으로 완전히 교체합니다.
  2. 실행이 성공하면 기존의 메모리는 제거되고 새로운 프로그램이 해당 프로세스를 차지합니다.
  3. 실행이 실패할 경우만 exec() 다음 코드가 실행됩니다.
📌 exec()는 여러 변형이 있으며, execl(), execv(), execvp() 등이 존재합니다.

4. 자식 프로세스의 종료 대기: wait()

부모 프로세스는 wait()을 호출하여 자식 프로세스의 종료를 대기할 수 있습니다.

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

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        printf("자식 프로세스 실행 중\n");
        sleep(2);
        printf("자식 프로세스 종료\n");
    } else {
        printf("부모 프로세스: 자식 프로세스 종료 대기 중\n");
        wait(NULL);
        printf("부모 프로세스: 자식 종료 확인\n");
    }
    return 0;
}

wait()의 동작 원리

  1. 부모 프로세스가 wait()을 호출하면 자식 프로세스가 종료될 때까지 대기합니다.
  2. 자식이 종료되면 자식의 종료 상태를 반환받고 실행을 계속합니다.
  3. waitpid()를 사용하면 특정 자식 프로세스만 대기할 수도 있습니다.
📌 wait()를 호출하지 않으면 좀비 프로세스(Zombie Process)가 발생할 수 있습니다.

5. 프로세스 종료와 좀비 프로세스 방지

5.1 exit()를 사용한 종료

프로세스는 exit()를 호출하여 명시적으로 종료할 수 있습니다.

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

int main() {
    printf("프로세스 종료\n");
    exit(0);  // 정상 종료
}

5.2 좀비 프로세스 방지

좀비 프로세스(Zombie Process)는 자식 프로세스가 종료되었지만 부모 프로세스가 wait()을 호출하지 않아 프로세스 테이블에서 제거되지 않은 상태를 의미합니다.

✅ 해결 방법: SIGCHLD 시그널을 이용한 자동 수거

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void handle_sigchld(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    signal(SIGCHLD, handle_sigchld);  // SIGCHLD 핸들러 등록

    if (fork() == 0) {
        printf("자식 프로세스 실행\n");
        sleep(2);
        printf("자식 프로세스 종료\n");
        exit(0);
    }

    while (1) {
        sleep(1);
        printf("부모 프로세스 실행 중...\n");
    }
    return 0;
}
📌 SIGCHLD 시그널이 발생할 때마다 waitpid()를 호출하여 좀비 프로세스를 제거합니다.

6. 프로세스 간 통신(IPC) 개요

프로세스 간 데이터를 주고받는 방법에는 여러 가지가 있습니다.

기법 설명  예제
파이프(Pipe) 부모-자식 간 데이터 전달 pipe()
FIFO(Named Pipe) 독립적인 프로세스 간 통신 mkfifo()
메시지 큐(Message Queue) 커널을 통한 메시지 교환 msgget(), msgsnd()
공유 메모리(Shared Memory) 프로세스 간 메모리 공유 shmget(), shmat()
소켓(Socket) 네트워크 프로세스 간 통신 socket()
📌 자세한 내용은 IPC(Inter-Process Communication) 관련 글에서 다룰 예정입니다.

2025.02.03 - [리눅스 시스템 및 네트워크 프로그래밍/시스템 프로그래밍] - 리눅스 IPC(프로세스 간 통신) 기법: Pipe, FIFO, 메시지 큐, 공유 메모리, 소켓의 원리와 활용

 

리눅스 IPC(프로세스 간 통신) 기법: Pipe, FIFO, 메시지 큐, 공유 메모리, 소켓의 원리와 활용

리눅스에서 IPC(Inter-Process Communication, 프로세스 간 통신)은 서로 다른 프로세스가 데이터를 주고받고 협력할 수 있도록 하는 기술입니다.운영체제에서 프로세스는 독립적인 주소 공간을 가지므

roblogs.tistory.com

 


7. 정리

  • fork()를 사용하면 새로운 프로세스를 생성할 수 있습니다.
  • exec()를 사용하면 현재 프로세스를 새로운 프로그램으로 변경할 수 있습니다.
  • wait()를 사용하면 자식 프로세스가 종료될 때까지 대기할 수 있습니다.
  • exit()는 프로세스를 명시적으로 종료합니다.
  • 좀비 프로세스를 방지하려면 SIGCHLD 시그널을 활용해야 합니다.

이해한 내용을 기반으로 직접 프로세스를 생성하고 실행하는 실습을 해보면 더욱 도움이 될 것입니다.

728x90
반응형

댓글

💲 추천 글