minitalk

coh·2023년 3월 9일
0

overview

signal함수들을 이용해서 server와 client가 통신하는 시스템을 만들어보자

requirements

  1. server가 먼저 실행되고 PID출력.
  2. client는 server's PID와 보낼 문자열을 parameters로 갖는다.
  3. parameter로 들어온 문자열을 server로 보내고 server는 이를 출력해야한다.
  4. server와 client의 통신은 unix signal로 이루어져야한다.
  5. server는 문자열 출력을 빠르게 해야한다.
  6. 1초 100글자는 굉장히 큰 시간.
  7. server는 다수의 clients에게 문자열을 연속적으로 수신할 수 있다.
  8. SIGUSR1과 SIGUSR2 두 개의 signal만 사용 가능.

signal

definition

= software interrupt. 즉, 프로그램이 예상하지 못한 외부의 event를 다루기 위한 매커니즘.
(interrupt = unexpected, external event, Asynchronous event.)

interrupt handling은 process가 동작 중일 때 (1)interrupt가 발생하면 (2)kernel이 process를 중단시키고 (3)적절하게 interrupt를 처리하는 것을 말한다.
이때 처리는 무시할 건지 service를 제공할 건지를 결정한다.

life cycle of signal

  1. 발생(raise)
    (1)Unexpected event is occured in program. e.g. divide by zero
    (2)user input : ctrl + c
  2. 보관(store)
    (1) signal 전달 전까지 kernel이 보관.
    (2) 전달이 가능해지면 해당 process에게 전달.
  3. 처리(handling)
    지정된 방법에 따라 시그널 처리.
  • Igonore the signal
  • Catch and handle the signal
  • perform the default action.

signal in unix

functions & concepts

  1. signal : It is signal function that help user handle arbitrary signal.
typedef void (*sighandler_t)(int)
sighandler_t signal  (int signum, sighandler_t handler)
  • signum : 처리할 시그널 번호. symbolic constant가 들어온다.
  • handler : signal handler's function pointer.
    함수 포인터인데 void func(int) 같이 변수를 하나 받는 핸들러
    다음은 default handler.
    (1) SIG_IGN : ignore signal.
    (2) SIG_DFL : Be processed system's default handler
  • return :
    (1)handler's function pointer
    (2)error : SIG_ERR
  1. sigaction

sigaction함수의 인자로 들어가는 sigaction구조체를 살펴보자.

  1. handler함수와 그 인자.
    union으로 handler함수들을 묶어놓았다.
    이 구조체의 sa_handler는 우리가 signal함수에서 쓰던 핸들러 함수 format이고
    우리는 sa_sigaction handler를 사용할 것이다.

sa_sigaction 핸들러는 세 개의 인자를 갖는다.
첫번째는 signal's symbolic constant.
-> SIGUSR1, SIGUSR2를 사용.
두번째는 siginfo_t structure.

siginfo_t {
      int      si_signo;  /* 시그널 넘버 */
      int      si_errno;  /* errno 값 */
      int      si_code;   /* 시그널 코드 */
      pid_t    si_pid;    // signal을 보낸  pid
      uid_t    si_uid;    /* 프로세스를 전송하는 실제 사용자 ID */
      int      si_status; /* Exit 값 또는 시그널 */
      clock_t  si_utime;  /* 소모된 사용자 시간 */
      clock_t  si_stime;  /* 소모된 시스템 시간 */
      sigval_t si_value;  /* 시그널 값 */
      int      si_int;    /* POSIX.1b 시그널 */
      void *   si_ptr;    /* POSIX.1b 시그널 */
      void *   si_addr;   /* 실패를 초래한 메모리 위치 */
      int      si_band;   /* 밴드 이벤트 */
      int      si_fd;     /* 파일 기술자 */
  }

세 번째는

void *context

이것은 이제 signal발생 시 문맥 정보가 포함된 ucontext_t 를 가리키는 역할을 한다고 한다.

  1. sa_flags
    sa_sigaction을 사용하기 위해선 sa_flags = SA_SIFINFO를 넣어야 handling process의 동작 변경이 된다.

  2. sa_mask
    블록시킬 signal을 지정하는 변수이다. 다른 일을 우선적으로 처리하기 위해 해당 시그널을 처리하지 않고 막겠다는 의미!

restcit를 사용하면 그 포인터가 가리키는 객체를 다른 포인터가 가리키지 못하게 한다.
sig : signal's symbolic constant
oact : 이전의 사용했던 sigaction구조체를 가리키는 변수. 우리는 사용하지 않을 것이다.

mandatory 구현
1. kill함수에서의 딜레이.

  • kill함수로 보내는 시간보다 서버에서 시그널을 수신하는 것에 시간이 더 필요하기 때문에 올바른 송수신을 위해 딜레이가 필요.
  1. client
# include "minitalk.h"

void bin_to_send(int pid, int bit)
{
    if (bit == 0)
        kill(pid, SIGUSR1);
    else if (bit == 1)
        kill(pid, SIGUSR2);
    usleep(100);
}

void ten_to_bin(int pid, int n, int cnt)
{
    if (n > 0)
    {
        ten_to_bin(pid, n / 2, cnt + 1);
        bin_to_send(pid, n % 2);
    }
    else if (n == 0)
    {
        while (cnt < 8)
        {
            bin_to_send(pid, 0);
            cnt++;
        }
    }
}

void    str_to_ten(int  pid, char *string)
{
    int i;
    int data;
    int bit_sh;

    i = 0;
    while (string[i])
    {
        data = (int)string[i];
        ten_to_bin(pid, data, 0);
        i++;
    }
    ten_to_bin(pid, 10, 0);
    ten_to_bin(pid, 0, 0);
}

int main(int ac, char **av)
{
    if (ac != 3)
    {
        printf("usage : ./a.out [server's pid] \"[string]\"\n");
        exit(1);
    }
    else
    {
        str_to_ten(atoi(av[1]), av[2]);
    }
}
  1. server
void bin_to_char(int sig, siginfo_t *info, void *context)
{
    static char	temp;
	static int	bit;

	if (sig == SIGUSR1 && bit < 8)
        temp <<= 1;
	else if (sig == SIGUSR2 && bit < 8)
	{
        temp <<= 1;
		temp |= 1;
	}
	bit++;
	if (bit == 8)
	{
		write(1, &temp, 1);
		bit = 0;
		temp = 0;
	}   
}

size_t ft_strlen(char *str)
{
    size_t  i;

    i = 0;
    while (str[i])
        i++;
    return (i);
}

void print_msg(char *msg)
{
    write(1, msg, ft_strlen(msg));
    exit(1);
}

int main(int ac, char *av[])
{
    struct sigaction test;
    
    (void)av;
    if (ac != 1)
        print_msg("format error is occured\n");
    printf("server's pid는 %d\n", getpid());
    test.sa_flags = SA_SIGINFO;
    test.sa_sigaction = bin_to_char;
    sigemptyset(&test.sa_mask);

    if (sigaction(SIGUSR1, &test, 0) == -1)
        print_msg("error is occured in receiving SIGUSR1\n");
    if (sigaction(SIGUSR2, &test, 0) == -1)
        print_msg("error is occured in receiving SIGUSR2\n");
    while (1)
        pause();
}

bonus

mandatory에서 재귀로 2진법을 만들어서 비트를 보내주었는데 유니코드같은 경우 값이 너무 크기 때문에 오버플로우 등등의 문제가 있어서 정상적으로 동작하지 않았다 따라서 재귀로 보내는 것이 아닌 그냥 비트연산을 통해 서버측으로 보내게 되었다.

유니코드에 관한 코드를 따로 짜야하나 했는데 신기하게도 쉘 터미널이 알아서 처리를 해주었다.

  1. client
# include "minitalk.h"

void handler(int sig)
{
    if (sig == SIGUSR1)
        printf("signal received well\n");
    exit(0);
}

void bin_to_send(int pid, int bit)
{
    if (bit == 0)
        kill(pid, SIGUSR1);
    else if (bit == 1)
        kill(pid, SIGUSR2);
    usleep(100);
}

void ten_to_bin(int pid, int n, int cnt)
{
    if (n > 0)
    {
        ten_to_bin(pid, n / 2, cnt + 1);
        bin_to_send(pid, n % 2);
    }
    else if (n == 0)
    {
        while (cnt < 8)
        {
            bin_to_send(pid, 0);
            cnt++;
        }
    }
}

void    str_to_ten(int  pid, char *string)
{
    int i;
    int data;
    int bit_sh;

    i = 0;
    while (string[i])
    {
        data = (int)string[i];
        ten_to_bin(pid, data, 0);
        i++;
    }
    ten_to_bin(pid, 10, 0);
    ten_to_bin(pid, 0, 0);
}

int main(int ac, char **av)
{
    if (ac != 3)
    {
        printf("usage : ./a.out [server's pid] \"[string]\"\n");
        exit(1);
    }
    else
    {
        signal(SIGUSR1, handler);
        str_to_ten(atoi(av[1]), av[2]);
    }
    while (1)
        pause();
}
  1. server
void bin_to_char(int sig, siginfo_t *info, void *context)
{
    static char	temp;
	static int	bit;

	if (sig == SIGUSR1 && bit < 8)
        temp <<= 1;
	else if (sig == SIGUSR2 && bit < 8)
	{
        temp <<= 1;
		temp |= 1;
	}
	bit++;
	if (bit == 8)
	{
        if (temp != 0)
		    write(1, &temp, 1);
        else
        {
            printf("%d\n", info->si_pid);
            kill(info->si_pid, SIGUSR1);
        }
		bit = 0;
		temp = 0;
	}   
}

size_t ft_strlen(char *str)
{
    size_t  i;

    i = 0;
    while (str[i])
        i++;
    return (i);
}

void print_msg(char *msg)
{
    write(1, msg, ft_strlen(msg));
    exit(1);
}

int main(int ac, char *av[])
{
    struct sigaction test;
    
    (void)av;
    if (ac != 1)
        print_msg("format error is occured\n");
    printf("server's pid는 %d\n", getpid());
    test.sa_flags = SA_SIGINFO;
    test.sa_sigaction = bin_to_char;
    sigemptyset(&test.sa_mask);

    if (sigaction(SIGUSR1, &test, 0) == -1)
        print_msg("error is occured in receiving SIGUSR1\n");
    if (sigaction(SIGUSR2, &test, 0) == -1)
        print_msg("error is occured in receiving SIGUSR2\n");
    while (1)
        pause();
}
profile
Written by coh

0개의 댓글