[CS] 부모 프로세스로 자식프로세스 제어해보기 with Signal

윤동환·2023년 3월 30일
0

Computer Science

목록 보기
5/10
post-thumbnail

fork 호출 시 동작 방식

부모의 모든것을 물려받는 자식 프로세스
fork( )를 호출하면 자식 프로세스가 생성되면서 부모 프로세스와 완전히 동일한 소스코드(image) 갖게됩니다. 코드 뿐만 아니라 부모 프로세스의 PCB(Process Control Block)도 그대로 물려 받습니다.

두번 리턴하는 fork()
fork는 총 두번 리턴합니다.
1. 자식 프로세스에게 0을 리턴
2. 부모 프로세스에게 자식프로세스의 id를 리턴

fork 이후 부모와 자식의 동작 순서
fork를 하더라도 아직 부모프로세스가 cpu를 점유하며 자식 프로세스는 ready queue에 들어가서 기다리게됩니다.

ready queue에 들어가서 기다린다는 것은 부모 프로세스가 종료되길 기다리는 것이 아닌, 복사한 부모 프로세스의 이미지를 기반으로 자식프로세스의 실행을 위한 기다림입니다.

		...
        
        pid = fork();

        if (pid < 0) {
            perror("fork");
        }
        else if (pid[i] == 0) {
            printf("[%d] pid : %d is child sleep start\n",i, pid[i]);
            printf("[%d] pid : %d is child sleep end\n",i, pid[i]);
        } else if (pid[i] > 0) {
            sleep(1);
            printf("[%d] pid : %d is  parent\n",i, pid[i]);
        }
        
        ...

출력결과
[0] pid : 0 is child sleep start
[0] pid : 0 is child sleep end
unnessasary process
[0] pid : 948559 is parent
create child again sleep start
-> 부모프로세스보다 자식프로세스가 먼저 출력되는 것을 볼 수 있습니다. 다시 진행하게 됩니다.

일반적으로 PCB에 main()부터 시작하도록 명시되어 초기화 되지만, 이 경우에는 부모 PCB기준으로 동작합니다.
하지만, 부모 프로세스가 설정한 프로세스 잠금, 파일 잠금 등은 복사되지 않습니다.
또한, 프로세스 실행시간을 체크하는 tms 같은 구조체 또한 0으로 초기화 됩니다.

덕분에 fork()이후에 자식프로세스는 0을 리턴받으며 동작하게 됩니다.

wait() 동작 방식

wait()은 함수를 호출한 프로세스의 cpu권한을 박탈하는 시스템 콜입니다. (preempt)

preempt 원리
커널이 아닌 프로그램은 자신의 주소에 한정되어 read, jump를 할 수 있지만, 커널은 어디든 다 갈 수 있습니다.

일반적으로 시스템콜을 호출하게되면 커널이 해당 요청에 맞는 동작을 수행합니다.
이때, 수행이 끝나면 다시 user 모드로 jump하는데 wait()을 호출했다면 커널이 user 모드로 가지않고 ready queue에서 가장 우선순위 높은 PCB를 찾아 프로그램 카운터(PC)가 가리키는 곳으로 갑니다.(jump)

이러한 과정을 preempt라고 합니다.

부모는 언제 깨어나는가?
자식프로세스가 모든 동작을 마치고 cpu를 반환하면 cpu는 자식프로세스의 부모프로세스를 찾고, ready queue에 등록한다.
그 이후 부모프로세스가 cpu를 점유하게 될 때 wait()이 종료된다.

왜 사용하는가?
자식프로세스는 자신을 생성한 부모프로세스의 pid를(ppid)갖고있습니다.
이때, 부모프로세스가 자식프로세스보다 먼저 종료하게되면, 자식프로세스의 부모프로세스 pid(ppid)가 1로되어 고아 프로세스가 됩니다.
이러한 상황을 방지하기위해 부모와 자식을 동기화 하는 목적으로 사용합니다.

일반적인 fork 코드

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

int main() {
        pid_t pid = getpid();
        printf("before pid : %d\n", pid);

        pid = fork();

        //pid = getpid();
        if (pid == 0) {
                printf("pid : %d is child\n", pid);
        } else if (pid > 0) {
                wait(NULL);
                printf("pid : %d is  parent\n", pid);
        }

        return 0;
}

당연하게 출력물은 아래와 같이 나옵니다.

before pid : 803677
pid : 0 is child
pid : 803678 is parent
//만약 wait()이 없었다면 부모 프로세스가 먼저 출력이 됩니다.

이제 각 프로세스의 id를 ps 명령어로 확인해보겠습니다.

    if (pid == 0) {
        printf("pid : %d is child\n", pid);
        while(1);
    } else if (pid > 0) {
        //wait(NULL);
        printf("pid : %d is  parent\n", pid);
        while(1);
    }

출력 결과

before pid : 804893
pid : 804894 is parent
pid : 0 is child

위의 예제를 while(1)을 걸어 각 프로세스 id 를 확인하면

[ydh@krujyit1 ~]$ ps -a
PID TTY TIME CMD
804893 pts/1 00:01:15 doyun
804894 pts/1 00:01:15 doyun
805062 pts/2 00:00:00 ps

이렇게 fork이전의 pid와 부모에 해당하는 pid만 확인이 되고있습니다.
코드상에서 자식프로세스의 pid(변수명이 abc여도 상관없습니다.)가 0이 나왔더라도 이 값은 단순하게 fork의 return 값일뿐, 자식 프로세스의 pid는 부모 프로세스가 return 받은 804894가 됩니다.

부모와 자신과 자식의 관계를 알아봅시다.

int main() {
ㅑ    pid_t pid = getpid();
    printf("before pid : %d\n", pid);

    pid = fork();

    //pid = getpid();
    if (pid == 0) {
        printf("pid : %d is child\n", pid);
    } else if (pid > 0) {
        wait(NULL);
        printf("pid : %d is  parent\n", pid);
    }
    printf("Me : %d, parent : %d, child : %d\n", 
    	(int)getpid(), (int)getppid(), (int)pid);


    return 0;
}

출력 결과

[ydh@krujyit1 ~/src/program/simple_control_signal]$ ./doyun
before pid : 814162
pid : 0 is child
Me : 814163, parent : 814162, child : 0
pid : 814163 is parent
Me : 814162, parent : 787840, child : 814163
[ydh@krujyit1 ~/src/program/simple_control_signal]$ ps
PID TTY TIME CMD
787840 pts/1 00:00:00 bash
814215 pts/1 00:00:00 ps

parent에 해당하는 ps를 보면 parent의 부모 pid는 현재 실행중인 bash의 pid와 같은것을 확인할 수 있습니다.

Signal을 사용하여 자식 프로세스 살리기!

#include <cstdio>
#include <unistd.h> //fork
#include <cstdlib> //exit
#include <sys/wait.h> //wait
#include <csignal>
#include <sys/types.h> //signal, key_t
#include <sys/msg.h> // msgget
#include <string.h> //strcpy


#ifndef PID_CNT

# define PID_CNT 3

#endif

typedef struct s_msg_buf {
        long mtype;
        char mtext[80];
} t_msg_buf;


const pid_t parent_pid = getpid();

static pid_t pid[PID_CNT];

void basicProcessFork(int child_pid, pid_t *pid);
void childProcess(void);
void processParent(void);
int findPidIndex(pid_t pid);

void insertPid(pid_t i_pid) {
        for (int idx = 0; idx < PID_CNT; ++idx) {
                if (pid[idx] == 0) {
                        pid[idx] = i_pid;
                        break;
                }
        }
}

void sig_handler(int signo) {
        int pid = getpid();
        if (signo == SIGCHLD) {
                pid_t r_pid = fork();
                if (r_pid == 0) {
                        //int idx = findPidIndex(-1);
                        //insertPid(pid);
                        printf("alive %d to %d\n",pid, getpid());
                        childProcess();
                }
        } 
}

int findPidIndex(pid_t f_pid) {
        for (int i = 0; i < PID_CNT; ++i) {
                if (pid[i] == (int)f_pid) {
                        return (i);
                }
                printf("pid[i] : %d , f_pid : %d\n", pid[i],  (int)f_pid);
        }
        return (-1);
}

int resetPid(pid_t r_pid) {
        for (int idx = 0; idx < PID_CNT; ++idx) {
                if (pid[idx] == r_pid) {
                        pid[idx] = 0;
                        return (idx);
                }
        }
        return (-1);
}

void childProcess() {
        signal(SIGUSR1, sig_handler);

        pid_t c_pid = getpid();

        if (c_pid != parent_pid) {
                insertPid(c_pid);//add current pid in array
        }

        printf("child process[%d] do something\n", (int)c_pid);
        sleep(3);

        if (resetPid(c_pid) == -1) //reset pid array before exit
                perror("reset dosen't work");
        else
                printf("reset pid [%d]\n", c_pid);

	    printf("[%d] child is die....\n", (int)c_pid);
        exit(0);
}

void processParent() {
        printf("parent do something\n");
        while(1) {
                for (int i = 0; i < PID_CNT; ++i) {
                        sleep(10);
                }
        }
}

void basicProcessFork(int child_pid, pid_t *pid) {
        if (child_pid < 0) {
                perror("fork");
                exit(1);
        } else if(child_pid == 0) {
                childProcess();
        }
}

int main() {
        signal(SIGCHLD, sig_handler);
        for (int i = 0; i < PID_CNT; ++i) {
                int child_pid = fork();
                basicProcessFork(child_pid, &pid[i]);
        }
        if (parent_pid == getpid())
                processParent();
        return 0;
}

Reference

참고 글

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글