system

[시스템 프로그래밍] execve() 시스템 콜로 이해하는 프로세스 교체 원리

숨usm 2025. 9. 8. 19:55

오늘은 fork()와 함께 사용되는 또 다른 핵심 시스템 콜인 execve()

- execve()는 현재 프로세스를 완전히 다른 프로그램으로 바꿔버리는 강력한 기능으로, 유닉스/리눅스 시스템에서 새로운 프로그램을 실행하는 핵심 메커니즘


execve() 시스템 콜이란?

execve()는 현재 실행 중인 프로세스를 완전히 다른 프로그램으로 교체하는 시스템 콜

출처: linux-in-practice GitHub 위의 소스코드를 분석한 내용을 정리하였습니다!

예제 코드 분석

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

static void child()
{
    char *args[] = { "/bin/echo", "hello" , NULL};
    printf("I'm child! my pid is %d.\n", getpid());
    fflush(stdout);
    execve("/bin/echo", args, NULL);
    err(EXIT_FAILURE, "exec() failed");
}

static void parent(pid_t pid_c)
{
    printf("I'm parent! my pid is %d and the pid of my child is %d.\n",
           getpid(), pid_c);
    exit(EXIT_SUCCESS);
}

int main(void)
{
    pid_t ret;
    ret = fork();
    if (ret == -1)
        err(EXIT_FAILURE, "fork() failed");
    if (ret == 0) {
        // child process came here because fork() returns 0 for child process
        child();
    } else {
        // parent process came here because fork() returns the pid of newly created child process (> 1)
        parent(ret);
    }
    // shouldn't reach here
    err(EXIT_FAILURE, "shouldn't reach here");
}

함수 원형

int execve(const char *pathname, char *const argv[], char *const envp[]);
  • pathname: 실행할 프로그램 경로 (예: "/bin/echo")
  • argv[]: 프로그램에 전달할 인자 배열
  • envp[]: 환경 변수 (NULL 가능)

fork() + execve() 조합

코드 분석

static void child()
{
    char *args[] = { "/bin/echo", "hello" , NULL};
    printf("I'm child! my pid is %d.\n", getpid());
    fflush(stdout);  // 버퍼 강제 출력
    execve("/bin/echo", args, NULL);
    err(EXIT_FAILURE, "exec() failed");  // 이 줄은 실행되지 않음
}

실행 과정

  1. fork() 호출: 부모/자식 프로세스 생성
  2. 자식 프로세스: child() 함수 실행
  3. printf 출력: "I'm child! my pid is 22913."
  4. execve() 호출: 자식 프로세스가 /bin/echo 프로그램으로 완전 교체
  5. echo 실행: "hello" 출력 후 종료

왜 err() 줄이 실행되지 않을까?

execve()가 성공하면:

  • 기존 프로그램 코드가 완전히 사라짐
  • 새로운 프로그램(/bin/echo)이 메모리에 로드됨
  • 원래 코드의 다음 줄은 존재하지 않게 됨

즉, execve() 성공 시 리턴하지 않습니다!


실행 결과 비교

fork()만 사용한 경우:

I'm parent! my pid is 21301 and the pid of my child is 21302.
I'm child! my pid is 21302.

fork() + execve() 사용한 경우:

I'm parent! my pid is 22912 and the pid of my child is 22913.
I'm child! my pid is 22913.
hello

핵심 개념 정리

fork() vs execve()

  • fork(): 프로세스 복제 (같은 프로그램이 2개)
  • execve(): 프로세스 교체 (다른 프로그램으로 변환)

프로세스 생명주기

부모 프로세스
    ↓ fork()
부모 + 자식 프로세스 (같은 프로그램)
    ↓ execve() (자식만)
부모 프로세스 + 새로운 프로그램을 실행하는 자식

왜 이런 설계를 사용할까?

Unix/Linux에서 새 프로그램을 실행하는 표준 방법:

  1. fork(): 새 프로세스 공간 확보
  2. execve(): 원하는 프로그램으로 교체

이 방식의 장점:

  • 프로세스 관리의 일관성
  • 부모-자식 관계 유지
  • 파일 디스크립터 상속 가능

strace로 확인하기

strace ./fork-and-exec

시스템 콜 레벨에서 clone()과 execve()가 어떻게 호출되는지 확인할 수 있습니다.


주요 함수들

  • fflush(stdout): 출력 버퍼를 즉시 비워 화면에 출력
  • EXIT_SUCCESS: 성공적 종료를 의미하는 상수 (값: 0)
  • EXIT_FAILURE: 실패를 의미하는 상수 (값: 1)

마무리

execve()는 fork()와 함께 유닉스/리눅스 시스템의 프로세스 생성과 실행의 핵심을 이루는 시스템 콜.

이 두 시스템 콜의 조합을 이해하면 쉘이 어떻게 명령어를 실행하는지, 데몬 프로세스가 어떻게 생성되는지 등 시스템 프로그래밍의 많은 개념을 이해할 수 있음.