42Cursus Minitalk

요구사항

  -by 42seoul-translation

진행도

2022.03.07

  • 프로젝트를 시작하며

    블랙홀에 빨려들어가지 않기 위해 학기중에 못할 것 같았던 42프로젝트를 손댔다.
    부디 바빠지기 전에 프로젝트를 완수하고 다시 학교 수업에 전념할 수 있게되길 바란다.

  • 문제 접근

    메시지 큐도 사용하지 않고 다른 프로세스에 문자열을 보내야 한다??
    유일하게 허용된 SIGUSR1, SIGUSR2 를 이용해 모스부호처럼 통신해야 하는건가??
    왠지 그래야 할 것 같다.

    그렇다면
    클라이언트가 서버에 시그널을 보내면 서버는 그것을 수신하고, handler에서 같은 시그널을 클라이언트에 보내 수신양호를 표현하고, 다음 비트 수신 준비가 되었음을 알리면 될 것 같다.

2022.03.10

  • 진행 상황

    세부적인 구성을 포함한 거의 모든 기능을 구현하였다. “거의” 라고 한정지은 이유는 3번에 1번 꼴로 이상한 문자가 출력된다.

    정말 5시간동안 코드를 노려봤는데 원인을 못찾겠다.

    더이상 집중하기 어려워 오늘 평가까지 끝내는 것은 포기하고 5시에 클러스터에서 나왔다.

2022.03.14

  • 프로젝트 마무리

    기분 전환겸 클러스터에 나갔다. 다시 3시간가량 진전없이 코드의 시그널이 어디서 막혀있는지 등을 가지고 고민을 하다가 함수 종료 시점이 아닐까? 라는 생각을 하게되었다. 정말 아무리 보아도 문제가 없었다.

    생각을 조금 바꾸어 클라이언트가 서버로부터 kill 시그널을 받고 너무 빠르게 서버에 새로운 비트를 넘겨 주는 것이 문제인가? 라는 생각이 들어 비트 전송 전에 usleep 코드를 넣어주었다.

    단박에 문제가 해결되었다. 분명이 sigpaction 함수로 인해 handler의 범위에선 mask가 씌여진 상태에서 실행될 것인데 어떤 이유에선지 마스크를 뚫고 시그널이 들어오는 경우가 있는 것 같았다. handler 의 종료 시점에 시그널이 딱 들어온다면?? 그 시그널은 panding list에 작성이 될 것인가??

시그널 이론 정리

  • 여러가지 시그널들

    SIGINT - 프로세스 종료 (interrupt)
    SIGKILL - 프로세스 죽이기
    SIGSEGV - 잘못된 메모리 접근
    SIGALRM - 시간초과
    SIGCHLD - child 종료시 parent 에 전달

    시그널은 커널에 의해 프로세스에 전달된다. 커널이 프로세스에 시그널을 보내는 사유

    • 커널이 스스로 판단(0으로 나누려고 한다거나 자식 프로세스가 종료 되었을 때)
    • 프로세스에서 kill command가 떨어졌을 때 (ctrl + c 등)
    • 시스템 콜 kill( ) 함수가 사용되었을 때.

    시그널을 전달 받은 프로세스는 도착한 시그널이 mask에 기록되지 않은 시그널일 경우 handler을 호출하여 해당 시그널을 처리하기 위한 동작을 취한다.

시그널의 관리

  • mask

    프로세스는 자신에게 들어오는 모든 시그널에 응답할 수 없다. 중요한 일을 처리하고 있는데 다른 시그널이 interrupt 하는 것을 방지하고, 그 외 다양한 이유로 다른 시그널의 방해를 막기 위해 무시하고 싶은 시그널을 기록한 mask 를 사용한다.
    mask 에 기록된 시그널이 프로세스에 입력 되더라도 프로세스가 별다른 동작을 취하지 않고, blocked list에 기록하고 하던 일 계속한다.

  • pending

    프로세스에 도착했지만, 아직 수신되지 않은 시그널들의 대기장소이다.
    무슨 소리냐 하면, 도착은 메일박스에 우체부(커널)가 편지(시그널)를 넣어준 상태, 수신은 수신자(프로세스)가 편지의 내용을 확인하고 그것을 처리하는 동작(handler 호출)을 시작한 상태를 이른다.

    mask에 막혀 도착만 하고 수신되지 않은 signal 또한 pending 에 기록된다.

  • blocked

    mask에 막혀 차단당한 signal 들을 보관하는 list이다. (block된 시그널은 handler가 호출되지 않기 때문에 당연히 pending에도 기록된다.)

    pending과 blocked는 커널에 의해 비트벡터 로 관리되며 각 프로세스의 컨텍스트에 저장된다.
    비트벡터로 관리되기 때문에 queue의 기능이 없다. 다시 말해 2개가 들어오건, 3개가 들어오건 <들어온 적="" 있음="">으로 똑같이 표기된다.

    들어온 적 있음: 0
    들어온 적 없음: 1 로 표기된다.

    심화 pending에서 blocked 걸러내기 pending엔 blocked signal이 섞여 있기 때문에 이런 쭉정이를 걸러내고 처리할 signal을 골라내는 작업이 필요하다.
    비트연산을 사용할 수 있다.
        
    pnb = pending & ~blocked //pending 시그널 중 현 blocked에 등록되지 않은 시그널을 찾아낸다.
        
      

signal handler

  • Default Actions - 시그널을 처리하는 헨들러를 따로 설정하지 않았을 경우 미리 정해진 핸를러 실행한다.
    다음 4가지 중 1개의 동작을 하도록 설계
    • 프로세스 종료
    • 프로세스 종료, 코어 덤프
    • SIGCONT(<=resume 을 의미하는 signal) 시그널을 받을 때까지 프로세스 정지
    • 그냥 무시되는 시그널(아무동작 안함.)
  • signal
      
      Handler_t *signal(int signum, handler_t *handler) // System Call 함수
      
    

    시그널 호출시 디폴트 액션 대신 사용자가 지정한 행동을 하도록 변경하는 함수.
    SIGKILL 또는 SIGSTOP가 default로 설정된 signal은 handler을 변경할 수 없다.

    handler에 올 수 있는 매크로 함수 SIG_IGN = 해당 타입의 시그널을 무시하도록 세팅 SIG_DFL = 시그널의 핸들러를 원래의 디폴트 핸들러로 변경해주는 매크로

    그 외에도 handler의 위치엔 사용자가 만든 함수의 포인터가 들어갈 수 있다.

  • sigaction

      
      sigaction(  int sig, // 처리할 시그널
                  const struct sigaction *restrict act, // 시그널 처리시 동작 방침
                  struct sigaction *restrict oact // 기존에 사용하던 action을(있다면) 저장할 공간
      )
    
      struct  sigaction {
          union __sigaction_u __sigaction_u;  /* signal handler의 함수 포인터 */
          sigset_t sa_mask;               /* handler 동작 동안 사용할 마스크 */
          int     sa_flags;               /* 동작의 세부를 조정할 플래그값 (man page 참고) */
       };
      
    

    signal이란 함수는 OS 마다 동작이 달라서 사용하기 곤란할 수 있다.
    반면에 signal과 같은 동작을 하는 sigaction은 POSIX 시스템을 지원하기만 한다면 OS와 무관하게 일관된 동작을 보인다.
    자신의 운영체제가 POSIX 시스템을 지원한다면 sigaction이 더 나은 선택지일 수 있다.

    • sigaction 함수
      • signum : 처리 할 시그널 넘버를 넘긴다. SIGKILL과 SIGSTOP을 제외한 모든 시그널이 가능하다.
      • act : signum에서 설정한 시그널에 대한 핸들링을 정의하는 sigaction 구조체 포인터이다. 일반적으로 설정된 시그널이 invoke 됐을 경우, sigaction 구조체에 정의된 sa_handler가 호출된다.
      • old_act : old_act가 null이 아니라면, 이전의 act가 old_act에 저장이 된다.
    • sigaction 구조체
      • sa_handler : 시그널이 invoke 됐을 때, 호출하는 함수 포인터이다.
      • sa_sigaction : sa_flag가 SA_INFO로 설정되어있을 경우, 시그널이 invoke 됐을 때, sa_handler가 아닌 이 함수가 호출된다. sa_handler와 달리 더 많은 인자를 갖는다.
      • sa_mask : 시그널 핸들러가 실행되는 동안, 블럭되어야 하는 시그널 마스크이다.
      • sa_flags : signal의 처리 방식이 정의되는 방식에 대한 플래그이다. OR연산으로 여러 flag을 세팅할 수 있다
    • 성공시 0. 실패시 1을 반환

signal 관련 함수

	
	sigemptyset(sigset_t *mask) 
	  // 빈 마스크(0)를 mask에 담아 줌.
	
  
	
	sigfillset(&mask) 
	  // 모든 시그널을 블락하는 마스크 착용
	
  
	
	sigaddset(&mask, SIGNAL) 
	  // SIGNAL의 해당 위치를 block해줌.
	
  
	
	Sigprocmask(SIG_FUNC, &mask, &prev_mask) 
	  // mask를 SIG_FUNC동작을 수행하고, 수행전 기존의 마스크를 prev에 저장
	  //
	  // SIG_FUNC
	  // SIG_BLOCK: 현재 마스크에 mask에 전달받은 block들을 막은 마스크를 착용
	  // SIG_UNBLOCK: 현재 마스크에서 mask에 있는 block들을 unblock,
	  // SIG_SETMASK: mask로 마스크를 대체.
	
  
	
	sigfillset(&mask) 
	  // 모든 시그널을 블락하는 마스크 착용
	
  
	
	sigsuspend(&prev) 
	  // 호출한 블럭 내에서 한시적으로 prev 마스크를 끼고 pause상태 진입.
	  // block되지 않은 시그널 발생시 깨어나고 prev 이전에 착용하고 있던(함수 호출시 착용하던)
	  // 마스크로 바꿔 끼고 반환 atomic 한 함수
	
  
심화 Safe signal Handling

G0: keep your handlers as simple as possible (시그널 내에서 전체 프로세스 hold하는 실행보단 전역 플래스 사용하여 호출함수에서 처리)
G1: async-signal-safe(atomicity)
G2: Save and restore errno on entry and exit
G3: protect accesses to shared data (critical section에서의 시그널 block)
G4: 공유되는 전역변수는 voletile 으로 선언
G5: 전역 플래그는 voletile, sig_atomic_t로 선언(단일 실행만 가능한 inturrupt에서 안전한 변수)

  • pause() 시그널을 받을 때 까지 멈추어있도록 하는 함수

  • kill(pid_t pid, int SIGNAL):

    지정한 프로세스로 특정 지정한 시그널을 전송해준다.

  • getpid():

    호출한 프로세스의 pid를 반환한다.

문제 접근

  • 사용가능한 프로세스간 정보 전달 수단은 SIGUSR1, SIGUSR2 이다.

    -> 이 의미는 각 시그널을 이용해 비트단위 데이터 전송을 해야 한다는 의미이다.

  • 100바이트의 데이터를 전송받는데에 1초는 엄청나게 비효율적인 minitalk 프로그램이라고 한다.(과제 요구사항에 언급된다.)

    -> signal을 이용해 정말 빠르게 정보 송을 해야 한다. 정보 처리에 비트 연산자를 사용하는 것을 고려한다.

  • 언급되지 않은 기본 요구사항: 메시지의 내용이 외곡되어선 안된다.

    -> server가 전송받은 signal을 해당 시그널을 전송한 클라이언트에 다시 되돌려준다. 클라이언트는 돌려받은 시그널이 전송했던 시그널이 아닐 경우 해당 비트를 재전송한다. (보너스 과제의 요구사항도 동시에 충족된다.)

  • 조금 더 빠른 퍼포먼스

    -> 서버와 클라이언트가 정보 전달을 seamless 하게 할 수 있도록 Server 의 handler에서 Client 시그널을 전송하고, 이를 처리하는 Client 의 handler에선 다음 바이트를 전송하는 함수를 호출한다.

    Safe signal Handling 원칙에 위배 되지만, 프로그램이 복잡하지 않기에 잘만 컨트롤 하면 성능의 상당한 향상을 기대할 수 있을 것 같다.

구현 결과

server.c
	  
  /* ************************************************************************** */
  /*                                                                            */
  /*                                                        :::      ::::::::   */
  /*   server.c                                           :+:      :+:    :+:   */
  /*                                                    +:+ +:+         +:+     */
  /*   By: chanhale <chanhale@student.42seoul.kr>     +#+  +:+       +#+        */
  /*                                                +#+#+#+#+#+   +#+           */
  /*   Created: 2022/03/08 16:47:27 by chanhale          #+#    #+#             */
  /*   Updated: 2022/03/08 23:58:07 by chanhale         ###   ########.fr       */
  /*                                                                            */
  /* ************************************************************************** */

  #include "../minitalk.h"

  t_infobox	g_infobox;

  static void	server_initiate(void)
  {
	ft_printf("pid: %d\n", getpid());
	g_infobox.character = 0;
	g_infobox.client_id = 0;
	g_infobox.counter = 1;
  }

  static void	handler(int signal, siginfo_t *info, void *func)
  {
	(void)func;
	if (g_infobox.client_id != info->si_pid)
	{
		g_infobox.character = 0;
		g_infobox.client_id = info->si_pid;
		g_infobox.counter = 1;
	}
	g_infobox.character <<= 1;
	g_infobox.counter <<= 1;
	if (signal == SIGUSR1)
		g_infobox.character |= 0;
	else
		g_infobox.character |= 1;
	if (g_infobox.counter >> 8)
	{
		write(1, &(g_infobox.character), 1);
		g_infobox.counter = 1;
		g_infobox.character = 0;
	}
	kill(info->si_pid, signal);
  }

  int	main(void)
  {
	static struct sigaction	sig_act;

	server_initiate();
	sig_act.sa_flags = SA_SIGINFO;
	sigemptyset(&(sig_act.sa_mask));
	sigaddset(&(sig_act.sa_mask), SIGUSR1);
	sigaddset(&(sig_act.sa_mask), SIGUSR2);
	sig_act.sa_sigaction = &handler;
	sigaction(SIGUSR1, &sig_act, NULL);
	sigaction(SIGUSR2, &sig_act, NULL);
	while (1)
	{
		if (sleep(1) == 0)
		{
			g_infobox.character = 0;
			g_infobox.client_id = 0;
			g_infobox.counter = 1;
		}
	}
  }
	  
	
client.c
	  
  /* ************************************************************************** */
  /*                                                                            */
  /*                                                        :::      ::::::::   */
  /*   client.c                                           :+:      :+:    :+:   */
  /*                                                    +:+ +:+         +:+     */
  /*   By: chanhale <chanhale@student.42seoul.kr>     +#+  +:+       +#+        */
  /*                                                +#+#+#+#+#+   +#+           */
  /*   Created: 2022/03/08 16:46:41 by chanhale          #+#    #+#             */
  /*   Updated: 2022/03/08 23:58:58 by chanhale         ###   ########.fr       */
  /*                                                                            */
  /* ************************************************************************** */

  #include "../minitalk_bonus.h"

  t_client_data	g_infobox;

  static void	send_char(void)
  {
	if (g_infobox.msg[g_infobox.index])
	{
		if (g_infobox.msg[g_infobox.index] >> g_infobox.bit & 1)
			kill(g_infobox.server_pid, SIGUSR2);
		else
			kill(g_infobox.server_pid, SIGUSR1);
	}
	else if (g_infobox.bit >= 0)
		kill(g_infobox.server_pid, SIGUSR1);
  }

  static void	client_handler(int signal)
  {
	if ((((g_infobox.msg[g_infobox.index] >> g_infobox.bit)
		& 1) + SIGUSR1) == signal)
	{
		g_infobox.bit--;
		if (g_infobox.bit < 0 && g_infobox.msg[g_infobox.index])
		{
			g_infobox.bit = 7;
			g_infobox.index++;
		}
	}
	usleep(10);
	send_char();
  }

  static void	client_initiate(char **argv)
  {
	static struct sigaction	sig_act;

	g_infobox.server_pid = ft_atoi_custom(argv[1]);
	if (g_infobox.server_pid == TYPE_PARSE_ERROR)
		exit(1);
	g_infobox.bit = 7;
	g_infobox.index = 0;
	g_infobox.msg = argv[2];
	sigemptyset(&(sig_act.sa_mask));
	sigaddset(&(sig_act.sa_mask), SIGUSR1);
	sigaddset(&(sig_act.sa_mask), SIGUSR2);
	sig_act.sa_handler = &client_handler;
	sigaction(SIGUSR1, &sig_act, NULL);
	sigaction(SIGUSR2, &sig_act, NULL);
  }

  int	main(int argc, char **argv)
  {
	int	retry;
	int	time_out;

	if (argc != 3)
		return (0);
	client_initiate(argv);
	retry = 10;
	send_char();
	while (retry)
	{
		time_out = usleep(30000);
		if (time_out == 0 && (--retry) < 0)
		{
			if (g_infobox.msg[g_infobox.index] && g_infobox.bit >= 0)
				send_char();
			else
				break ;
		}
	}
	return (0);
	}
	  
	
minitalk.h
	  
  /* ************************************************************************** */
  /*                                                                            */
  /*                                                        :::      ::::::::   */
  /*   minitalk.h                                         :+:      :+:    :+:   */
  /*                                                    +:+ +:+         +:+     */
  /*   By: chanhale <chanhale@student.42seoul.kr>     +#+  +:+       +#+        */
  /*                                                +#+#+#+#+#+   +#+           */
  /*   Created: 2022/03/08 16:13:25 by chanhale          #+#    #+#             */
  /*   Updated: 2022/03/08 23:53:25 by chanhale         ###   ########.fr       */
  /*                                                                            */
  /* ************************************************************************** */

  #ifndef MINITALK_H
  # define MINITALK_H

  # include 
  # include 
  # include 
  # include 
  # include 

  # define TYPE_PARSE_ERROR -2147483648
  # define TYPE_SEND_FAIL 1
  # define TYPE_SEND_SUCCESS 0

  typedef struct s_infobox
  {
	int		counter;
	int		character;
	pid_t	client_id;
  }	t_infobox;

  typedef struct s_client_data
  {
	char	*msg;
	size_t	index;
	int		bit;
	pid_t	server_pid;
  }	t_client_data;

  int	forge(va_list *ap, char c);
  void	ft_putnbr_fd(int n, int fd);
  void	ft_putchar_fd(char c, int fd);
  void	ft_putstr_fd(char *s, int fd);
  int	ft_isascii(int c);
  int	int_form(va_list *ap);
  int	uns_int_form(va_list *ap);
  int	ft_printf(const char *str, ...);
  int	char_form(va_list *ap);
  int	str_form(va_list *ap);
  void	un_putnbr(unsigned int n);
  int	lower_form(va_list *ap);
  int	upper_form(va_list *ap);
  int	ft_put_ptr(uintptr_t nbr);
  int	ft_puthex(unsigned int nbr, int is_it_upper);
  int	ptr_form(va_list *ap);
  int	other_form(char c);
  int	ft_atoi_custom(char *str);

  #endif
	  </code>
	</pre>
  </details>
Comment

There are currently no comments on this article
Please be the first to add one below :)

Leave a Comment

내용에 대한 의견, 블로그 UI 개선, 잡담까지 모든 종류의 댓글 환영합니다!!