미니쉘을 만들면서 자식프로세스의 시그널 처리에 대해서 정말 많은 생각을 했다.
자식프로세스를 만들고 시그널을 보내면 부모는 당연하고 자식, 손자 프로세스 모두 시그널이 전달돼서 꼬이는걸 확인할 수 있었는데
execve로 실행한 프로그램 내부에서 시그널을 어떻게 변경할지는 알 수 없기 때문에 자식프로세스의 시그널을 맘대로 집어넣을수 없었다. (cat같은 경우엔 SIGINT는 DFL로 변경되고, SIGQUIT는 부모프로세스 그대로 사용하는것 같았다.)
일단 만들고 제출했던 미니쉘에서는 fork하는 순간 부모프로세스의 시그널을 IGN 하는 방법으로 시그널 중복을 막을 수 있었지만, 출력을 어떻게 하는지 몰라서 처리를 못했었다.
결론부터 말하자면 자식프로세스는 종료될때 어떤 시그널로 종료됐는지 부모에게 넘겨주고, 부모는 그걸로 시그널에 따라 어떤 출력을 할지 정하고 리턴값도 세팅할 수 있다.
static int set_fork_ret(int fork_ret)
{
int signo;
if (WIFEXITED(fork_ret))
return (WEXITSTATUS(fork_ret)); // 정상종료는 exit값을 세팅해서 리턴
else
{
if (WIFSIGNALED(fork_ret))
{
signo = WTERMSIG(fork_ret); // 어떤 시그널로 자식이 죽었는지 리턴됨
if (signo == SIGINT)
ft_putstr_fd(2, "^C\n");
else
ft_putstr_fd(2, "^\\Quit: 3\n");
return (128 + signo); // 시그널로 종료됐을땐 signo + 128 값이 리턴값이다.
}
}
return (-1);
}
int exec_program(t_lst *node, t_stat *stat)
{
pid_t pid;
int fork_ret;
int i;
fork_ret = 0;
set_signal(SH_DFL, SH_DFL); // 자식프로세스를 위해 시그널을 일단 DFL로 설정
pid = fork();
if (pid == 0)
{
i = 0;
while (node->cmd[i] && node->cmd[i] != '/')
i++;
if (i == ft_strlen(node->cmd))
node->cmd = find_path(node->cmd, stat);
execve(node->cmd, node->argv, stat->env);
print_exec_err(i, node->cmd, stat);
exit(EXEC_ERR_CMDNF); // execve가 실패한경우는 command not found가 유력
}
set_signal(SH_IGN, SH_IGN); // 시그널 중복을 막기위해 부모프로세스는 IGN으로 설정
wait(&fork_ret); // 자식프로세스 리턴 대기
fork_ret = set_fork_ret(fork_ret); // 리턴값에 따라 에러출력 등 처리 후 리턴값 재세팅
set_signal(SH_SHELL, SH_IGN);
return (fork_ret);
}
fork와 wait에 대해서...
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int pipe_fd[2];
int ret;
pid_t pid;
pid = fork();
if (pid == 0)
{
ret = execve("/bin/cat", argv, 0);
printf("execve error!!!\n");
exit(ret);
}
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
wait(&ret);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
printf("%d, %d, %d\n", ret, WIFEXITED(ret), WTERMSIG(ret));
}
리턴값을 세팅하기 위해서 미리 정의된 매크로함수의 사용이 필요하다.
리턴값은 int이지만, 하위 8비트는 실패했을때, 상위 24비트는 성공했을때 세팅하는 값이라고 판단된다.
WIFEXITED(status)
자식이 정상적으로 종료되었다면 non-zero 이다.
WEXITSTATUS(status)
exit()를 호출하기 위한 인자나 return 값이 설정되고 종료된 자식의 반환 코드의 값을 세팅해준다.
wait으로 받은 값을 >> 8 연산한 결과나 마찬가지이다.
WIFSIGNALED(status)
자식프로세스가 어떤 신호때문에 종료되었다면 참을 반환한다.
WTERMSIG(status)
자식프로세스를 종료하도록한 신호의 번호를 반환한다.
WIFSTOPPED(status)
반환의 원인이 된 자식프로세스가 현재 정지되어 있다면 참을 반환한다.
WSTOPSIG(status)
자식을 정지하도록 야기한 신호의 숫자를 반환한다.
fork된 자식 프로세스가 정상 종료된 경우. WIFEXITED(ret) = non-zero
1. execve 성공인 경우
execve가 정상적으로 실행된 경우. wait으로 세팅된 값은 0. 매크로 함수에 따라 다른 리턴값을 가져올 수 있다.
execve에서 실행한 프로그램 자체 에러 ex) cat sdnfjkansdfkj 는 에러출력도 그 프로그램에서 하며, 정상 실행으로 간주한다.
wait으로 세팅된 값은 256
2. execve가 실패한 경우. (execve의 리턴값이 -1)
execve가 실패한것이지, fork된 자식프로세스는 타고내려가서 exit로 정상 종료되었다.
execve가 실행해야 하는 프로그램이 없는경우. (절대경로나 상대경로만 가능함. PATH에서 찾아서 직접 넣어줘야한다)
errno에 에러번호가 세팅되며 그에맞는 에러를 자식프로세스에서 출력할 수 있다. wait으로 리턴된 값은 (unsigned char)-1(=255)의 << 8 한 65280값이다.
자식 프로세스가 비정상 종료한 경우. WIFEXITED(ret) = 0
1. 시그널에 의한 종료
SIGINT, SIGQUIT, KILL 등 시그널을 받으면 종료되는데 이것을 fork의 비정상 종료로 판단한다.
이때 WTERMSIG(ret) 매크로를 통해 종료시킨 시그널의 signo 값을 받아올 수 있고
부모프로세스에서 자식이 어떤 시그널로 죽었는지 판단 가능하다.
실제로 시그널로 죽은 프로세스의 리턴은 signo + 128 값을 사용한다.
SIGINT = 2, SIGQUIT = 3
'42 Seoul' 카테고리의 다른 글
NETPRACTICE (0) | 2022.07.07 |
---|---|
cpp module (0) | 2022.05.27 |
exam rank 04 (0) | 2022.05.26 |
minishell (0) | 2022.04.21 |
philosophers - 식사하는 철학자 문제 (0) | 2022.02.19 |