프로세스 fork() 및 exec()

이번 포스팅에서는 fork()와 exec() 계열의 함수를 사용하여 새로운 프로세스를 수행하는 방법을 알아보겠습니다.
기본적으로 하나의 프로그램에서 새로운 프로세스를 수행할 수 있는 방법은 다양합니다.

대표적으로는 system()과 exec() 함수가 있고 system shell command를 수행할 수 있는데, 두 함수 간에 차이가 존재합니다.
system() 함수의 경우 fork process를 만들고, return 값을 기다립니다.
exec() 함수의 경우 어떤 값도 return하지 않으며, 단순히 그 명령어를 수행합니다.

Qt에서는 QProcess라는 Class를 지원하기도 합니다.
QProcess는 동작 상으로 볼 때 system() 함수와 유사하게 동작한다고 볼 수 있습니다.

다시 본론으로 돌아와서 system() 함수는 Main Loop에서 호출한 프로세스가 끝날 때까지 기다리므로 절차지향적 구성에 포함되고,
exec() 함수는 동시에 Main Loop와 호출한 Child Process를 병렬적으로 수행하는 개념이 됩니다.
물론, exec() 함수 실행 후에 waitpid() 함수 수행 시 세번째 옵션에 따라 Child Process에 대한 기다림의 여부를 설정할 수도 있습니다.

보통 fork() 후 exec()를 사용하여 Child Process를 생성하였을 때, 기존 Parent Process와 Child Process는 다른 프로세스로 서로 간에 통신을 필요로 합니다. 이것을 Inter-Process Communication, IPC라고 합니다.
IPC에는 파이프, 메세지 큐, 공유 메모리 등 다양한 방법이 존재하는데, 
예제에서는 공유 메모리를 사용하여 프로세스 간 통신을 하도록 구현하였습니다. 

Parent 프로그램은 공유 메모리를 생성하고 Child Process를 생성하여 공유 메모리로 부터 0xFFFF 값을 읽을 때까지 무한히 수행되도록 하였습니다.
Child 프로그램은 Parent에서 미리 생성된 공유 메모리에 연결한 후 해당 공유 메모리에 0xFFFF 값을 쓰는 프로그램입니다.
아래에 fork() 함수 및 exec() 계열 함수에 대한 설명과 전체 코드 첨부하겠습니다.

 

fork() 함수

fork() 호출은 기본적으로 현재 프로세스의 복사본을 거의 모든 방식으로 동일하게 만듭니다. 
모든 것이 복사되지는 않지만 (ex. 일부 구현에서 자원 제한) 사본을 최대한 비슷하게 만드는 것이 목표입니다.

새 프로세스 (Child Process)는 다른 프로세스 ID (PID)를 가져오고 이전 프로세스 (Parent Process)의 PID를 상위 PID (PPID)로 갖습니다. 
두 프로세스가 이제 정확히 동일한 코드를 실행하고 있기 때문에 fork()의 리턴 코드를 통해 어떤 프로세스가 어떤 것인지 알 수 있습니다. 
하위는 0이되고 상위는 하위의 PID를받습니다. 
위 동작은 fork() 호출이 작동한다고 가정했을 경우이고, 그렇지 않으면 Child Process는 생성되지 않고 Parent Process가 오류 코드를 얻습니다.

exec() 호출은 기본적으로 현재 프로세스 전체를 새로운 프로그램으로 대체하는 방법입니다. 
프로그램을 현재 프로세스 공간으로 로드하고 진입점에서 실행합니다.

따라서 fork()와 exec()는 현재 프로세스의 Child Process로 실행되는 새 프로그램을 얻기 위해 순서대로 사용됩니다. 
쉘은 일반적으로 find와 같은 프로그램을 실행하려고 할 때마다 쉘 fork()를 실행 한 다음 Child Process가 find 프로그램을 메모리에 Load하여 모든 명령 행 인수, 표준 I/O 등을 설정합니다.

두 가지 함수를 함께 사용할 필요는 없습니다. 
예를 들어, 프로그램에 Parent 코드와 Child 코드가 모두 포함되어있는 경우 (ex. 각 구현에 제한이 있을 수 있으므로 주의를 기울여야 함) 

프로그램이 실행되지 않고 자체적으로 fork() 할 수 있습니다. 
단순히 TCP 포트에서 수신 대기하고 Parent가 수신 대기로 돌아가는 동안 특정 요청을 처리하기 위해 자신의 사본을 fork()하는 데몬에 상당히 많이 사용됩니다.

마찬가지로, 완료되었다는 것을 알고 다른 프로그램을 실행하려는 프로그램은 분기, 실행 및 Child를 기다릴 필요가 없습니다. 
Child Process를 프로세스 공간에 직접로드 할 수 있습니다.

exec() 계열 함수

exec()라는 system call은 존재하지 않습니다. 
단지, 일반적으로 모든 exec() 계열의 함수를 그룹으로 나타내는데 사용하고, exec() 계열의 함수들은 실질적으로 똑같은 일을 합니다.
새로운 프로그램을 현재 프로세스에 로딩하고, 인자와 환경 변수를 제공합니다. 
각 함수별 차이는 프로그램을 찾는 방법, 인수를 지정하는 방법 및 PATH 환경 변수 등 입니다.

execv()

이름이 v인 호출은 배열 매개 변수를 사용하여 새 프로그램의 argv[] 배열을 지정합니다. 
인수의 끝은 NULL을 포함하는 배열 요소로 표시됩니다.

execl()

이름이 l인 호출은 새 프로그램의 인수를 가변 길이 인수 목록으로 함수 자체에 사용합니다. 

인수의 끝은 (char *) NULL 인수로 표시됩니다. 
NULL은 정수 상수가 될 수 있기 때문에 항상 캐스트 유형을 포함해야 하며, 가변형 함수를 호출 할 때 기본 인수 변환은 포인터로 변환하지 않습니다.

exece()

이름이 e인 호출은 새로운 프로그램의 환경을 제공하기 위해 추가 인수 (또는 l의 경우 인수)를 취합니다. 

그렇지 않으면 프로그램은 현재 프로세스의 환경을 상속합니다. 

이것은 argv 배열과 같은 방식으로 제공됩니다. execve()에 대한 배열, execle()에 대한 별도의 인수.

execp()

이름에 p가있는 호출은 PATH 환경 변수를 검색하여 디렉토리가 없는 경우 (즉, / 문자가 포함되어 있지 않은 경우) 프로그램을 찾습니다. 
그렇지 않으면 프로그램 이름은 항상 실행 파일의 경로로 취급됩니다.

 

execp(), execP()

FreeBSD 5.2는 또 다른 변형 인 execvP (대문자 P 포함)를 추가했습니다. 
이것은 execvp()와 비슷하지만 PATH 환경 변수에서 검색 경로를 얻는 대신 함수에 대한 명시적인 매개 변수입니다.

 

전체 코드

Parent 프로그램

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <QCoreApplication>
#include <unistd.h>
#include <wait.h>
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
 
#define  KEY_NUM   1234
#define  MEM_SIZE  4096
 
using namespace std;
 
int shmid;
static int SharedMemoryCreate();
static int SharedMemoryRead(char *sMemory);
static int SharedMemoryFree(void);
 
int     g_ngetpid;
pid_t   g_pid;
 
int main()
{
    int call_status = 0;
    SharedMemoryCreate();\
 
    g_pid = fork();
 
    if(g_pid == 0)
        call_status = execl("/home/build-child_program-Desktop_Qt_5_8_0_GCC_64bit-Debug/child_program"NULLNULL);
    else
        waitpid(g_pid, &call_status, WNOHANG);
 
    char buffer[MEM_SIZE] = {0,};
    int tmpval = 0;
 
    while(1)
    {
        SharedMemoryRead(buffer);
        memcpy(&tmpval, buffer, sizeof(int));
        if(tmpval == 0xFFFF)
        {
            cout << "Receive data from shared memory!" << endl;
            SharedMemoryFree();
            break;
        }
    }
 
    return 0;
}
 
static int SharedMemoryCreate()
{
    if((shmid = shmget((key_t)KEY_NUM, MEM_SIZE, IPC_CREAT| IPC_EXCL | 0666)) == -1) {
        printf("There was shared memory.\n");
 
        shmid = shmget((key_t)KEY_NUM, MEM_SIZE, IPC_CREAT| 0666);
 
        if(shmid == -1)
        {
            perror("Shared memory create fail\n");
            return 1;
        }
        else
        {
            SharedMemoryFree();
            shmid = shmget((key_t)KEY_NUM, MEM_SIZE, IPC_CREAT| 0666);
 
            if(shmid == -1)
            {
                perror("Shared memory create fail\n");
                return 1;
            }
        }
    }
 
    return 0;
}
 
static int SharedMemoryRead(char *sMemory)
{
    void *shmaddr;
    char mess[MEM_SIZE] = {0};
 
    if((shmaddr = shmat(shmid, (void *)00)) == (void *)-1)
    {
        perror("Shmat failed\n");
        return 1;
    }
 
    memcpy(sMemory, (char *)shmaddr, sizeof(mess));
 
    if(shmdt(shmaddr) == -1)
    {
        perror("Shmdt failed\n");
        return 1;
    }
    return 0;
}
 
static int SharedMemoryFree(void)
{
    if(shmctl(shmid, IPC_RMID, 0== -1)
    {
        perror("Shmctl failed\n");
        return 1;
    }
 
    printf("Shared memory end\n");
    return 0;
}

 

Child 프로그램

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <QCoreApplication>
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
 
#define  KEY_NUM   1234
#define  MEM_SIZE  4096
 
using namespace std;
 
int shmid;
static int SharedMemoryInit();
static int SharedMemoryWrite(char *sMemory, int size);
 
int main()
{
    char buffer[MEM_SIZE] = {0,};
    int shareval = 0xFFFF;
 
    SharedMemoryInit();
 
    memcpy(buffer, &shareval, sizeof(shareval));
    SharedMemoryWrite(buffer, sizeof(buffer));
 
    return 0;
}
 
static int SharedMemoryInit()
{
    if((shmid = shmget((key_t)KEY_NUM, 00)) == -1)
    {
        perror("Shmid failed");
    }
 
    return 0;
}
 
static int SharedMemoryWrite(char *sMemory, int size)
{
    void *shmaddr;
 
    if((shmaddr = shmat(shmid, (void *)00)) == (void *)-1)
    {
        perror("Shmat failed");
    }
 
    memcpy((char *)shmaddr, sMemory, size);
 
    if(shmdt(shmaddr) == -1)
    {
        perror("Shmdt failed");
        exit(1);
    }
 
    return 0;
}

 

결과 화면