프로세스 간 통신-신호
ㅏ signal이벤트 발생을 나타내는 프로세스에 대한 알림입니다. 신호는 또한software interrupt 그리고 그 발생을 알기 위해 예측할 수 없기 때문에 asynchronous event.
신호는 번호 또는 이름으로 지정할 수 있으며 일반적으로 신호 이름은 SIG로 시작합니다. 사용 가능한 신호는 다음과 같이 kill –l (신호 이름 나열을위한 l) 명령으로 확인할 수 있습니다.
신호가 발생할 때마다 (프로그래밍 방식 또는 시스템 생성 신호) 기본 작업이 수행됩니다. 기본 작업을 수행하지 않고 신호 수신에 대한 자체 작업을 수행하려면 어떻게해야합니까? 모든 신호에 대해 가능합니까? 예, 신호를 처리 할 수 있지만 모든 신호를 처리 할 수는 없습니다. 신호를 무시하려면 어떻게해야합니까? 예, 신호를 무시할 수 있습니다. 신호를 무시한다는 것은 기본 작업을 수행하거나 신호를 처리하지 않음을 의미합니다. 거의 모든 신호를 무시하거나 처리 할 수 있습니다. 무시하거나 처리 / 잡을 수없는 신호는 SIGSTOP 및 SIGKILL입니다.
요약하면 신호에 대해 수행되는 작업은 다음과 같습니다.
- 기본 동작
- 신호 처리
- 신호 무시
설명했듯이 신호는 기본 동작의 실행을 변경하여 처리 될 수 있습니다. 신호 처리는 시스템 호출, signal () 및 sigaction () 등 두 가지 방법 중 하나로 수행 할 수 있습니다.
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
시스템 호출 signal ()은 signum에서 언급 한대로 신호 생성시 등록 된 핸들러를 호출합니다. 핸들러는 SIG_IGN (신호 무시), SIG_DFL (신호를 기본 메커니즘으로 다시 설정) 또는 사용자 정의 신호 핸들러 또는 함수 주소 중 하나 일 수 있습니다.
성공시이 시스템 호출은 정수 인수를 사용하고 반환 값이없는 함수의 주소를 반환합니다. 이 호출은 오류 발생시 SIG_ERR을 반환합니다.
signal ()을 사용하면 사용자가 등록한 각 시그널 핸들러를 호출 할 수 있지만 차단해야 할 시그널 마스킹, 시그널 동작 수정 및 기타 기능과 같은 미세 조정은 불가능합니다. 이것은 sigaction () 시스템 호출을 사용하여 가능합니다.
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
이 시스템 호출은 신호 동작을 검사하거나 변경하는 데 사용됩니다. 행위가 null이 아닌 경우 신호 signum에 대한 새 작업이 행위에서 설치됩니다. oldact가 널이 아니면 이전 조치가 oldact에 저장됩니다.
sigaction 구조는 다음 필드를 포함합니다-
Field 1 − sa_handler 또는 sa_sigaction에서 언급 된 핸들러.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sa_handler의 핸들러는 signum을 기반으로 수행 할 작업을 지정하고 SIG_DFL은 기본 작업을 나타내거나 SIG_IGN은 신호 처리 함수에 대한 신호 또는 포인터를 무시합니다.
sa_sigaction에 대한 핸들러는 신호 번호를 첫 번째 인수로 지정하고, siginfo_t 구조에 대한 포인터를 두 번째 인수로, 사용자 컨텍스트에 대한 포인터 (자세한 내용은 getcontext () 또는 setcontext () 확인)를 세 번째 인수로 지정합니다.
siginfo_t 구조는 전달할 신호 번호, 신호 값, 프로세스 ID, 전송 프로세스의 실제 사용자 ID 등과 같은 신호 정보를 포함합니다.
Field 2 − 차단할 신호 세트.
int sa_mask;
이 변수는 신호 처리기 실행 중에 차단되어야하는 신호 마스크를 지정합니다.
Field 3 − 특수 깃발.
int sa_flags;
이 필드는 신호의 동작을 수정하는 플래그 집합을 지정합니다.
Field 4 − 복원 처리기.
void (*sa_restorer) (void);
이 시스템 호출은 성공하면 0을, 실패하면 -1을 반환합니다.
몇 가지 샘플 프로그램을 고려해 보겠습니다.
먼저 예외를 생성하는 샘플 프로그램부터 시작하겠습니다. 이 프로그램에서는 0으로 나누기 연산을 수행하여 시스템에서 예외를 생성합니다.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
컴파일 및 실행 단계
Floating point exception (core dumped)
따라서 산술 연산을 수행하려고 할 때 시스템은 신호의 기본 동작 인 코어 덤프와 함께 부동 소수점 예외를 생성했습니다.
이제 signal () 시스템 호출을 사용하여이 특정 신호를 처리하도록 코드를 수정 해 보겠습니다.
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
컴파일 및 실행 단계
Received SIGFPE, Divide by Zero Exception
논의 된 바와 같이, 신호는 시스템에 의해 생성되거나 (0으로 나누기 등과 같은 특정 작업을 수행 할 때) 사용자가 프로그래밍 방식으로 신호를 생성 할 수도 있습니다. 프로그래밍 방식으로 신호를 생성하려면 라이브러리 함수 raise ()를 사용하십시오.
이제 다른 프로그램을 사용하여 신호 처리 및 무시를 설명해 보겠습니다.
raise ()를 사용하여 신호를 발생했다고 가정하면 어떻게됩니까? 신호를 올리면 현재 프로세스의 실행이 중지됩니다. 그러면 중지 된 프로세스는 어떻게됩니까? 두 가지 시나리오가있을 수 있습니다. 첫째, 필요할 때마다 실행을 계속합니다. 둘째, 프로세스를 종료합니다 (kill 명령 사용).
중지 된 프로세스의 실행을 계속하려면 해당 특정 프로세스에 SIGCONT를 보냅니다. fg (전경) 또는 bg (백그라운드) 명령을 실행하여 실행을 계속할 수도 있습니다. 여기서 명령은 마지막 프로세스의 실행 만 다시 시작합니다. 둘 이상의 프로세스가 중지되면 마지막 프로세스 만 재개됩니다. 이전에 중지 된 프로세스를 재개하려면 작업 번호와 함께 작업 (fg / bg 사용)을 재개하십시오.
다음 프로그램은 raise () 함수를 사용하여 SIGSTOP 신호를 발생시키는 데 사용됩니다. 사용자가 CTRL + Z (Control + Z) 키를 눌러 SIGSTOP 신호를 생성 할 수도 있습니다. 이 신호를 발행 한 후 프로그램은 실행을 중지합니다. 실행을 계속하려면 신호 (SIGCONT)를 보냅니다.
다음 예에서는 fg 명령을 사용하여 중지 된 프로세스를 다시 시작합니다.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
컴파일 및 실행 단계
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
이제 다른 터미널에서 SIGCONT를 실행하여 중지 된 프로세스의 실행을 계속하도록 이전 프로그램을 향상시킵니다.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
컴파일 및 실행 단계
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
다른 터미널에서
kill -SIGCONT 30379
지금까지 시스템에서 생성 된 신호를 처리하는 프로그램을 살펴 보았습니다. 이제 프로그램 (raise () 함수 또는 kill 명령 사용)을 통해 생성 된 신호를 살펴 보겠습니다. 이 프로그램은 기본 동작이 실행을 중지하는 신호 SIGTSTP (터미널 중지)를 생성합니다. 그러나 우리는 기본 액션 대신 지금 시그널을 처리하고 있기 때문에 정의 된 핸들러로 올 것입니다. 이 경우 메시지를 인쇄하고 종료합니다.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
컴파일 및 실행 단계
Testing SIGTSTP
Received SIGTSTP
우리는 기본 동작을 수행하거나 신호를 처리하는 인스턴스를 보았습니다. 이제 신호를 무시할 때입니다. 여기서는이 샘플 프로그램에서는 SIG_IGN을 통해 무시할 SIGTSTP 신호를 등록하고 SIGTSTP (터미널 정지) 신호를 올립니다. 신호 SIGTSTP가 생성되면 무시됩니다.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
컴파일 및 실행 단계
Testing SIGTSTP
Signal SIGTSTP is ignored
지금까지 우리는 하나의 신호를 처리하는 하나의 신호 핸들러가 있음을 관찰했습니다. 하나의 핸들러로 여러 신호를 처리 할 수 있습니까? 대답은 예입니다. 프로그램으로 이것을 고려해 봅시다.
다음 프로그램은 다음을 수행합니다.
Step 1 − 신호 SIGINT (CTRL + C) 또는 SIGQUIT (CTRL + \)를 포착하거나 처리하기 위해 핸들러 (handleSignals)를 등록합니다.
Step 2 − 사용자가 신호 SIGQUIT (kill 명령 또는 CTRL + \를 사용한 키보드 제어를 통해)를 생성하면 핸들러는 단순히 메시지를 리턴으로 인쇄합니다.
Step 3 − 사용자가 처음에 신호 SIGINT (kill 명령 또는 CTRL + C로 키보드 제어를 통해)를 생성하면 다음 번부터 기본 동작 (SIG_DFL 사용)을 수행하도록 신호를 수정합니다.
Step 4 − 사용자가 SIGINT 신호를 두 번 생성하면 기본 동작 인 프로그램 종료를 수행합니다.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
컴파일 및 실행 단계
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
다른 터미널
kill 71
두 번째 방법
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
우리는 신호를 처리하기 위해 두 가지 시스템 호출 즉, signal () 또는 sigaction ()이 있다는 것을 알고 있습니다. 지금까지 signal () 시스템 호출을 보았습니다. 이제 sigaction () 시스템 호출을 할 시간입니다. 다음과 같이 sigaction ()을 사용하여 수행하도록 위 프로그램을 수정 해 보겠습니다.
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
컴파일 및 실행 프로세스를 살펴 보겠습니다. 실행 과정에서 CTRL + C를 두 번 실행하고 나머지 검사 / 방법 (위와 같이)도이 프로그램에 대해 시도 할 수 있습니다.
컴파일 및 실행 단계
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C