본문 바로가기

STUDY/linux

SIGNAL in LINUX

운영체제를 공부하면 반드시 배우게 되는 것이 interrupt이다. 이때 interrupt는 크게 두가지로 구분할 수 있다.

 

Hardware Interrupt

: I/O 작업과 같은 이벤트

cpu와 I/O 간의 작업을 비동기로 처리하고 cpu에게 완료했음을 알려주는 것이 interrupt이다.

 

Software Interrupt

: 비동기로 이루어지는 이벤트에 대해 신호들을 주고 받는 것

즉 signal이다.

 

이러한 signal을 어떻게 요청하고 처리하는지 정리해보자


SIGNAL Action

os는 signal이 온 경우 어떻게 대처를 하는가?

 

signal 처리는 크게 두 가지 방식으로 이루어진다.

 

1. signal handler를 등록된 함수를 실행

2. signal handler가 없는 경우 Default action - 대부분 terminated or SIGCHLD의 경우 signal을 무시

 

signal의 종류

 

SIGINT, SIGQUIT (종료 event, 이때 control + ^ 로 프로그램을 종료하는 것은 SIGINT 신호를 주는 것이다.)

SIGABRT (비정상 종료 event, abort call을 호출한 경우 os가 process에게 signal을 보낸다.)

SIGBUS, SIGFPE, SIGSEGV (하드웨어 exception, SIGSEGV는 segmentation fault 즉 참조할 수 없는 메모리 영역)

SIGALRM (process가 sleep 후 신호를 보낸다.)

SIGCHLD (child process가 죽은 경우 보낸다.)

SIGPIPE (프로세스 간의 통신 에러 시 발생한다.)

SIGTERM (default signal, 프로세스를 종료한다. shell에서 kill command는 이 신호를 주는 것이다.)

SIGCONT (shell scroll을 pause했다가 다시 보여준다.)

SIGSTOP (sehll scroll을 pause한다.)

SIGHUP (설정을 바꿨을 떄 발생한다.)

SIGIO (I/O 명령이 잘못되는 경우 발생한다.)

SIGKILL (kill signal이 온 경우)

SIGUSR1, SIGUSR2 (사용자 정의 signal)

 

원래 signal handler는 일회용임. 즉 한번 실행하고 나면 등록을 해제하기 떄문에 다음에 신호가 왔을 때는 등록된 handler가 없으므로 프로세스 자체가 종료됨 (Default 동작이 종료이기 때문)

다행히 리눅스에서는 signal handler가 자동으로 재등록되기 때문에 이러한 일이 없음. 하지만 solaris system에서는 여전히 일회용이므로 재등록 과정이 필요하다.


Signal Handler 등록

# include <signal.h>

void (*signal(int signo, void (*func) (int))) (int);

함수 명세

return 성공 시 이전에 등록된 signal handler, 실패 시 SIG_ERR (-1)
description signo 신호에 대해 파라미터로 전달된 함수 포인터를 signal handler로 등록한다.

 

파라미터

int signo signal number, 주로 매크로로 정의된 상수를 사용 (ex. SIGITERM)
void *(func)(int) signal이 들어왔을 때 실행될 함수 포인터 (즉 handler로 등록될 함수)

 

signal handler라고 거창하게 말했지만 사실 signal이 들어왔을 때 실행될 함수를 정하는 것이다.

 

Example

SIGINT 신호가 들어올 때까지 무한 루프를 돌며 대기하다가 signal 이 들어오면 종료하는 프로그램을 만들자.

pause() 함수로 무한 대기를 구현하였다.

 

#include <stdio.h>  // printf()
#include <signal.h> // signal()
#include <unistd.h> // pause()
#include <stdlib.h> // exit()

void sigHandler(int signo)
{
	printf("This is SIGINT Signal Handler\n");
	printf("Terminate process...\n");

	exit(0);
}

int main(int argc, char *argv[])
{
	signal(SIGINT, sigHandler);

	printf("Waiting for SIGINT signal...\n");
	
	while (1)
		pause();

	return 0;
}

 

실행 결과는 다음과 같다.

 

 

위와 같이 무한 대기를 하다 control^ 로 SIGINT 신호를 보내게 되면

 

 

이처럼 signal handler로 등록되있는 함수를 실행한다.


SIGNAL을 보내는 함수

# include <sys/types.h>

# include <signal.h>

int kill(pid_t pid, int signo);

함수 명세

return 성공 시 0, 실패 시 -1
description 다른 process에게 signo에 해당하는 signal을 보낸다.

 

파라미터

pid_t pid signal을 보낼 process ID

pid > 0: pid에 해당하는 특정 프로세스에게 시그널에 보냄

pid == 0: Group ID가 같은 모든 프로세스에게 시그널을 보냄

pid < 0: PID의 절댓값에 해당하는 모든 Group ID를 가진 모든 프로세스에게 시그널을 보냄
int signo signal number, 주로 매크로로 정의된 상수를 사용 (ex. SIGITERM)

shell 에서 프로세스에게 signal을 보내는 command도 kill 이다.

따라서 kill 1234 라고 하면 1234 프로세스를 죽이라는 것이 아니라, 1234 프로세스에게 시그널을 보내라는 의미이다.

이때 어떤 시그널인지 지정을 안했기 때문에 SIGTERM이 보내지게 되어서 종료되는 것이다.

1234 프로세스를 죽이라는 의미가 아니다. (리눅스 배우기 전에는 이 의미인줄 알았음)

 

 

# include <sys/types.h>

# include <signal.h>

int raise(int signo);

함수 명세

return 성공 시 0, 실패 시 -1
description 프로세스가 자기 자신에게 signal을 보낸다.

 

파라미터

int signo signal number, 주로 매크로로 정의된 상수를 사용 (ex. SIGITERM)

 

Example

fork()를 사용하여 프로세스를 두개를 만들고 유저 입력이 있을 때 child process에게 SIGTERM을 보내 child process를 종료시키는 프로그램을 만들자.

 

#include <stdio.h>		// printf()
#include <signal.h>		// signal()
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>		

void SigHandler(int signo)
{
	printf("Child process Terminated\n");
	exit(0);
}

int main(int argc, char *argv[])
{
	pid_t	pid;
	
	pid = fork();

	if (pid == 0)
	{
		signal(SIGTERM, SigHandler);
		while (1)
			pause();
	}
	else
	{
		printf("press ENTER to terminate child process\n");
		getchar();

		kill(pid, SIGTERM);
		waitpid(pid, NULL, 0);
		printf("Parent process DONE\n");
	}
	return 0;
}

 

위와 같이 자식 프로세스에서는 (pid == 0) signal로 SiGTERM에 대한 signal handler를 등록하고 무한 대기를 하고 있다.

이때 부모 프로세스에서는 getchar()로 유저 입력을 아무거나 하나 받게 된다.

유저 입력이 들어온 경우 kill() 함수를 이용하여 자식 프로세스에게 SIGTERM 신호를 보낸다. 이때 fork()의 결과로 나온 pid가 바로 자식 프로세스의 id이므로 kill의 인자로 바로 넣어준다.

그 후 waitpid로 자식 프로세스의 종료를 기다리고 부모 프로세스도 종료한다.

 

결과는 다음과 같다.

프로그램을 컴파일하고 실행하면 다음과 같이 parent process의 getchar()로 유저 입력을 기다리는 부분이 실행된다.

 

 

이때 아무키나 눌러주면 다음과 같이 signal handler에 의한 메세지가 뜨고 프로그램이 종료된다.

( press ENTER라고 했지만 getchar()로 받은 입력에 따른 처리를 안했기 때문에 아무거나 받아도 종료된다.)

 

'STUDY > linux' 카테고리의 다른 글

IPC in LINUX - Message Passing  (0) 2022.04.02
Files and Directories in LINUX  (0) 2022.03.31
Process and Threads in LINUX  (0) 2022.03.28
File I/O functions in C Library  (0) 2022.03.23
File I/O system call in LINUX  (0) 2022.01.04