C언어로 간단하게 프로세스간 통신하는 프로그램을 만들어보자

Updated:

Categories:

Tags:

#

C언어로 간단하게 프로세스간 통신하는 프로그램을 만들어보자

##

프로세스 간의 통신(IPC, inter-process communication)

  • 프로세스는 필요한 경우 통신을 한다

    IPC는 주고받는 데이터 크기가 매우 다양하며, 프로세스가 동일한 시스템에 있을 수도 잇고, 다른 시스템에 있을 수도 있다.

    과제에서 요구하는 IPC는 동일한 시스템 내의 두 프로세스 간의 통신을 말한다

##

시그널

  • 시그널(SIGNAL)은 보통 인터럽트(interrupt)의 종류 중 하나이며, 특정 이벤트가 발생했을 때, 프로세스에게 전달하는 신호이다. 우리가 Ctrl+C를 누를 경우 SIGINT가 반환되며 그 시그널로 프로그램을 종료할지 말지 결정할 수 있게 만들 수 있다.

##

시그널 핸들러(signal handler)

  • 시그널 핸들러는 프로세스가 시그널을 받았을 때 호출되는 함수이다

    프로세스가 시그널을 받으면 수행하던 작업을 멈추고 시그널 핸들러를 호출하고, 시그널 핸들러의 실행이 끝나면 멈추던 작업을 다시 시작하게 된다.

##

시그널과 관련된 시스템 콜

시스템 콜(system call) 의미
sigemptyset 시그널 집합을 공집합으로 만든다
sigfillset 시그널 집합을 모든 시그널이 포함된 전체 집합으로 만든다
sigaddset 시그널 집합에 특정 시그널을 추가한다
sigdelset 시그널 집합에서 특정 시그널을 제외시킨다
sigaction 특정 시그널에 대한 프로세스의 행동을 설정한다
kill 특정 프로세스에게 특정 시그널을 보낸다
raise 자기 자신에게 특정 시그널을 보낸다
alarm 설정된 시간이 경과한 후 SIGALRM 시그널을 받는다
pause 시그널이 도착할 때까지 대기 상태가 된다

##

시그널관련 함수

  • signal 함수

    • #include
    • void (signal(int signo, void (handler)(int)))(int);
    • 오류시 : -1(SIG_ERR)
    • 주요 시그널

      시그널 이름 설명
      SIGHUP 터미널을 읽어버렸을때 발생한다
      SIGABRT 프로그램의 비정상종료시 발생한다
      SIGINT Ctl+C 나 DELETE 키를 입력했을때 발생한다
      SIGIO 비동기적인 입출력이 발생했을때
      SIGKILL 프로세스를 죽이기 위해서
      SIGPIPE 단절된 파이프에 write 할 경우 발생
      SIGEGV 잘못된 메모리 참조(주로 포인터를 잘못 썼을때)
      SIGSTOP 프로세스의 일시중단 (Ctrl + z)
      SIGSUSR1 사용자를 위해 정의된 시그널
  • kill()

    • #include <sys/types.h>
    • #include
    • int kill(pid_t pid, int signo);
    • 성공시 : 0
    • 실패시 : -1

    • 첫 번째 인수, 프로세스 ID 값에 따른 처리 분류

      pid 의미
      양의 정수 지정한 프로세스 ID에만 시그널을 전송
      0 함수를 호출하는 포르세스와 같은 그룹에 있는 모든 프로세스에 시그널을 전송
      -1 함수를 호출하는 프로세스가 전송할 수 있는 권한을 가진 모든 프로세스에 시그널을 전송
      -1 이외의 음수 첫번째 인수 pid 의 절대값 프로세스 그룹에 속하는 모든 프로세스에 시그널을 전송
  • sigemptyset

    • 시그널 집합의 모든 시그널을 삭제한다. 시그널 집합을 빈 집합으로 만든다
    • 신호집합
      • 여러 개의 신호를 하나의 신호집합으로 표현하는 자료형식이 존재한다
      • sigset_t ss;
    • #include
    • int sigemptyset(sigset_t *set);
    • 반환값
      • 성공시 : 0
      • 실패시 : -1
  • sigaction

    • 시그널 처리를 다양하게 한다
  • sigaddset

    • 시그널 집합에 시그널을 추가한다
    • #include
    • int sigaddset(sigset_t *set, int signo);
    • 반환값
      • 성공시 : 0
      • 실패시 : -1
  • pause

    • 시그널을 수신할 때까지 대기한다
    • #include
    • int pause(void);
    • 반환값 : -1(errno가 EINTR로 설정된다)
  • sleep

    • 지정한 시간 동안 대기 상태가 된다. 지정한 시간이 경과되었거나 시그널을 수신하면 대기에서 풀린다. 인수로 받는 시간 값은 초 단위이다
    • #include
    • unsigned int sleep(unsigned int seconds);
    • 반환값 : 성공시 0, 남은 시간이 반환된다.

getpid()

  • 해당프로세스의 아이디를 반환하는 함수

#

client.c

#include "minitalk.h"

static int	ft_isdigit(char c)
{
	if (c >= '0' && c <= '9')
		return (1);
	return (0);
}

static int	ft_isspace(char c)
{
	if ((c >= 9 && c <= 13) || c == ' ')
		return (1);
	return (0);
}

static int	ft_atoi(const char *str)
{
	size_t	result;
	int		negative;

	result = 0;
	negative = 1;
	while (*str != '\0' && (ft_isspace(*str)))
		str++;
	if (*str != '\0' && (*str == '-' || *str == '+'))
	{
		if (*str == '-')
			negative = -1;
		str++;
	}
	while (*str != '\0' && ft_isdigit(*str))
	{
		result = (result * 10) + (*str - '0');
		str++;
	}
	return ((int)(result * negative));
}

void	my_kill(int pid, char *str)
{
	int		bit_shift;
	char	tmp;

	while (*str)
	{
		bit_shift = 8; /* 비트계산을 위해 8을 줌 */
		tmp = *str; /* 메시지저장 예를 들면 string = "hello"라면 처음엔
									tmp는 h를 저장 */
		while (bit_shift--)
		{
			/* 현재의 문자를 보내기 위해서 비트를 시프트 연산을 하게 된다
				이때 연산의 결과가 1이되면 SIGUSR1과 함께 값 1을 보냄
				아니라면 SIGUSR2과 함께 0을 보냄 */
			if (tmp >> bit_shift & 1)
				/* 주어진 pid 번호에 kill함수로 SIGUSR로 0 또는 1 보내기 */
				kill(pid, SIGUSR1);
			else
				kill(pid, SIGUSR2);
			/* 시그널이 겹치지 않도록 시그널 사이에 50ms를 넣는다 */
			usleep(50);
			/* 이렇게 모든 문자에 조건들을 적용하고 서버에 문자열을 전송한다 */
		}
		str++;
	}
	bit_shift = 8;
	/* 마지막 '\0'문자를 보내기 위한 코드이다 0000 0000 = '\0'*/
	while (bit_shift--)
	{
		kill(pid, SIGUSR2);
		usleep(50);
	}
}

/* 메인에서는 3개의 인자를 받아야 실행하게 만들어야 한다
	1. ./client
	2. server의 pid 번호
	3. 보낼 메시지
	이렇게 3개의 인자를 받아서 실행해야 하는 프로그램이기 때문에 예외처리를
	따로 했다

	그리고 입력한 pid는 int형으로 보내야하기 때문에 atoi를 써서 int변환
	하고 그 pid로 시그널을 보내기 위함이다
	"\n"은 맨 끝에 있다면 ASCII 값을 찾을 수 없기 때문에 직접 보낸다 */
int	main(int argc, char *argv[])
{
	if (argc != 3)
	{
		ft_printf("\n");
		return (0);
	}
	my_kill(ft_atoi(argv[1]), argv[2]);
	my_kill(ft_atoi(argv[1]), "\n");
}

#

server.c

#include "minitalk.h"

void	sig_function(int sig)
{
/* 여기서 static을 사용하는 이유는 새로운 데이터가 계속 들어오기 때문이다 */
	static char	tmp;
	static int	bit_shift;

	if (sig == SIGUSR1)
	/* 들어오는 데이터가 1일 경우에는 tmp 비트에 0을 준다
		이유는 나중에 저장되어 있는 데이터를 초기화함에 있어서 좋은 역할을 한다 */
		tmp = tmp | 1;
	bit_shift++;
	if (bit_shift == 8)
	{
		/* bit_shift가 8이 되면 이제 tmp에 저장되어 있는
			값을 출력한다.*/
		bit_shift = 0;
		ft_printf("%c", tmp);
		tmp = 0;
	}
	/* 받은 값이 1이 아니기 때문에 1비트를 옮기고,
		옮긴 값은 기본적으로 0으로 설정이 된다 */
	else
		tmp <<= 1;
}

int	main(void)
{
	ft_printf("pid = %d\n", getpid());
	/* 시그널 기능은 프로그램이 끝날때까지 계속 듣고 있게 된다 */
	while (1)
	{
		signal(SIGUSR1, sig_function);
		signal(SIGUSR2, sig_function);
	/* 프로그램이 종료되지 않도록 하는 함수 pause() 를 호출 */
		pause();
	}
	return (0);
}

C 카테고리 내 다른 글 보러가기