[OS, System programming] IPC, Signal이란? 시그널로 서버,클라이언트 간 메세지 통신하기

김예찬·2024년 4월 16일
0
post-thumbnail

IPC (Inter Process Communication)란?

프로세스간의 데이터를 주고받는 통신으로 pipe,socket,공유메모리,signal을 사용하는 다양한 방법이 존재한다.
처음에 IPC의 개념만 보고는 프로세스 간의 통신이므로 같은 호스트 컴퓨터에서 일어나는 로컬환경 통신이라고 생각했다. 하지만 우리가 익숙해하는 네트워크 통신도 결국 어떠한 프로세스와 프로세스 간의 통신이므로 IPC에 속한다고 볼 수 있다.

즉, IPC는 모든 형태의 통신을 포괄하는 개념이라고 볼 수 있다.

시그널이란?

시그널은 앞서 말한 IPC의 한종류이다.
OS에서 특정 이벤트가 발생했을 때 프로세스에게 전달되는 소프트웨어 인터럽트 신호이다.

시그널은 언제 사용될까?
가장 일반적으로 여러분이 모두 사용해봤을 시그널은 바로 ctrl+c 일 것이다. 프로그램 실행중 또는 터미널에서 ctrl+c를 사용해서 현재 진행중인 프로세스를 종료할 수 있다. 사실 이러한 동작은 프로세스에 INTERUPT signal을 보내서 종료하게 하는 것이다.

시그널이 어떻게 동작하는지 확인할 수 있는 간단한 c예시를 보자.

signal헤더를 통해 signal에 관련된 함수들을 사용할 수 있다.

  • void (signal(int signum, void (handler)(int)))(int)
    첫번째 인자에 처리할 시그널을, 두번째인자에서 그 시그널을 처리할 함수를 지정한다. 두번째 인자에 SIG_IGN을 적으면 그냥 그 신호를 무시한다.
#include <signal.h>
#include <stdio.h>

int main()
{
	signal(SIGINT,SIG_IGN);
	printf("I'm running...\n");
	while (1)
	{
		printf("Still going ...\n");
		sleep(1);
	}
}

위 소스 파일 컴파일. 실행 시


프로그램이 계속 실행된다.
ctrl +c 입력시 해당 프로그램의 프로세스에 SIGINT(INTERRUPT)가 전달되어 종료되는 것이 일반적이다.
하지만 코드 상에서 SIGINT를 받을경우 무시하라는 SIG_IGN 동작을 지정해 놓았기 때문에 아래와 같은 결과를 볼 수 있다.

ps 명령어로 현재 돌아가는 프로세스들을 확인 가능

Minitalk

"42Seoul"에서 Signal을 이용해서 서버에서 클라이언트가 보낸 메세지를 출력하게하는 과제를 진행해야했다.

과제를 진행하면서 생각한 내용이나 과정을 정리한다.

How to?

과제를 완료한 지금은 생각보다 간단한 내용이였구나라는 생각이 들지만 처음에는 메세지를 어떻게 시그널이라는 정보로 전달 할 수 있을까라는 고민했던 것 같다.

메세지라는 것의 본질을 생각하면서 시그널을 적용할 수 있는 방법을 생각해 낼 수 있었다.

메세지라고 말하는 문자열은 사실 0과 1로 이루어진 데이터이다.
char *형의 문자열에서 하나의 char문자는 결국 8비트(char형의 크기인 1바이트)의 데이터로 8자리의 0또는1로 이루어져있다는 것이다.

즉 0,1을 마치 모스부호 신호처럼 시그널로 매핑하여 보내고 서버에서는 우리가 정한 시그널을 0,1로 변환하여 다시 문자로 바꾸고 문자열을 구성하면되는 것이다.

여기까지 생각을 했다면 코드로 구현하는 과정은 굉장히 간단하다.

먼저 문자열을 보내는 클라이언트 프로그램과 메세지를 받는 서버 프로그램이 따로 있어야하므로 두개의 분리된 소스파일이 필요하다.

client.c

#include <signal.h>

void	send_signal(pid_t pid, char c)
{
	int	bit;
	int	temp;

	bit = 0;
	while (bit < 8)
	{
		if ((c & (1 << bit)))
			temp = kill(pid, SIGUSR1);
		else
			temp = kill(pid, SIGUSR2);
		if (temp == -1)
		{
			ft_printf("Error: Sending signal");
			exit(0);
		}
		usleep(90);
		bit++;
	}
}

void	send_message(pid_t pid, char *msg)
{
	int	i;

	i = 0;
	while (msg[i] != '\0')
	{
		send_signal(pid, msg[i]);
		i++;
	}
	send_signal(pid, '\n');
	send_signal(pid, '\0');
}

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

	i = 1;
	if (argc != 3)
	{
		ft_printf("Error: Incorrect number of parameter\n");
		return (0);
	}
	pid = ft_atoi(argv[1]);
	if (pid < 0 || pid > 32768)
	{
		ft_printf("Error: Wrong pid\n");
		return (0);
	}
	send_message(pid, argv[2]);
	return (0);
}

프로그램 실행시 두개의 인자 pid, 메세지를 받는다.
인자의 개수가 잘못될 경우 에러처리를 해주었다.

문자열에 대해 각 문자를 구성하는 8자리의 0,1비트를 각각 SIGUSR1,SIGUSR2의 시그널로 매핑하여 인자로 받은 pid에 kill함수를 이용하여 전달한다.
이때 서버측에서 시그널을 처리하는 시간을 보장하기위해 약간의 송신 딜레이를 usleep()함수로 설정해주었다.

server.c


#include <signal.h>

void	handle_message(int signal)
{
	static int		bit;
	static char		temp;

	if (signal == SIGUSR1)
		temp = temp | (1 << bit);
	bit++;
	if (bit == 8)
	{
		ft_printf("%c", temp);
		bit = 0;
		temp = 0;
	}
}

int	main(void)
{
	ft_printf("[Server started]\n");
	ft_printf("PID: %d\n", getpid());
	signal(SIGUSR1, handle_message);
	signal(SIGUSR2, handle_message);
	while (1)
	{
		pause();
	}
	return (0);
}

이제 서버측은 프로그램이 실행된후 유저가 보내는 시그널을 pause(); 함수로 기다리게 된다.
유저가 보낸 시그널 SIGUSR1 또는 SIGUSR2 에 따라 0,1로 8자리수를 구성한다.
이후 8개의 시그널이 모이면 이를 문자로 바꾸어 바로 출력해주었다.

이때 시그널 핸들러 함수가 실행될 때마다 이전의 비트를 저장하고 있어야하므로 정적 변수를 사용하여 이전의 상태를 계속 저장하고 있도록 해주었다.

실행


서버 실행시 pid를 알려줌!

클라이언트 실행하면서 pid와 함께 메세지를 인자로 보냄
서버측에 해당 메세지가 뜨는 것을 확인가능하다!

이렇게 간단히 시그널을 이용한 메세지 전달을 구현해보았다.

0개의 댓글