ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 42Seoul - Minitalk
    42Seoul 2021. 8. 5. 12:08
    반응형

    Minitalk구현 과제에 대해서 포스팅 해보겠습니다.

     

    Minitalk는 간단하게 서버와 클라이언트 프로세서가 서로 통신하는 것을 구현하면 됩니다.

     

    ❗ 사용가능함수

    • 함수 리스트
      1. write : Terminal창에 글자를 쓰는 함수.
      2. signal : *signal(int signum, void (handler)(int))
      3. sigemptyset : *sigemptyset(sigset_t set)
      4. sigaddset : *sigaddset(sigset_t set, int signum)
      5. sigaction : **sigaction(int signum, const stuct sigaction act, struct sigaction oldact)
      6. kill : kill(pid_t pid, int signum)
      7. getpid : Process ID를 얻을 수 있는 함수(INT형).
      8. malloc : 메모리 동적할당 함수.
      9. free : 메모리 동적할당 해제 함수.
      10. pause : 항상 -1을 반환하며 시그널을 수신 할 때까지 기다리는 함수.
      11. sleep : sec만큼 실행을 늦추는 함수(초 단위)
      12. usleep : micro sec만큼 실행을 늦추는 함수(u단위)
      13. exit : 프로세서 종료 함수.

     

    📖 <signal.h>에 관한 함수

     

    기본적으로 Process가 signal을 받을 경우 시그널에 해당하는 기본동작을 하거나 시그널을 무시하거나 사용자가 정의한 동작을 함수를 통해 바꿀 수 있습니다.

    1. Signal : 시그널을 등록할 때 사용하는 함수.

    프로토 타입 : void (*signal(int signum, void (*handler)(int)))(int);

    매개 변수 : int signum(특정 상황에 대한 정보), void (*handler)(int)(특정 상황 발생 시 함수호출)

    signal은 UNIX,POSIX운영체제에서 프로세스 간 통신(IPC)에 쓰이는 방법 중 하나입니다.

    SIGUSR을 사용 할 경우는 사용자가 직접 신호를 지정하여 특정 상황을 핸들러로 지정해줍니다.

    signal같은 경우는 유닉스 운영체제 별로 동작방식 차이가 있어서 불안정합니다.

     

    2. Kill : 원하는 Process에 Signal을 보내고 싶을 때 사용하는 함수.

    프로토 타입 : int kill(pid_t pid, int sig)

    매개 변수 : pid(sig를 보낼 pid), sig(pid에 보낼 sig)

     

    pid  >  0 경우 → 해당 pid를 가지는 프로세스에게 sig를 보냄.

    pid  =  0 경우 → 현재 프로세스가 속한 그룹의 모든 프로세스에 sig를 보냄.

    pid  =  -1 경우 → 1번 프로세스를 제외한 모든 프로세스에게 sig를 보냄.

    pid  <  -1 경우 → 모든 프로세스 그룹에 sig를 보냄.

     

    3. Sigaction : 시그널을 받기위해서 사용되는 함수. signal대체용.

    프로토 타입 : int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    매개 변수 : signum(특정 상황에 대한 정보), *act(signum에 대한 시그너 핸들러 정보)

    struct sigaction
    {
        void (*sa_handler)(int);    
        void (*sa_sigaction)(int, siginfo_t *, void *);
    	//위의 두 개의 핸들러중 sa_flags값에 따라서 동작하는 핸들러 지정.
        sigset_t sa_mask;           // 시그널 처리동안 막아야 할 시그널 지정
        int sa_flags;               // 시그널의 처리 행위 조절을 위한 플래그
        void (*sa_restorer)(void);  // 사용되지 않는다. 
    }

     

    <Handler>

      /* Used if SA_SIGINFO is not set.  */
    	__sighandler_t sa_handler;
    	/* Used if SA_SIGINFO is set.  */
    	void (*sa_sigaction) (int, siginfo_t *, void *);
          }
        __sigaction_handler;

    핸들러는 둘 중 SIGINFO가 준비되어있으면 info까지 포함해서 만들고 아니면 그냥 핸들러만 만들어 주며 됩니다.

    둘 중 하나를 선택해서 이용하면 됩니다.

     

    <SIGSET>

    __sigset_t sa_mask;
    
    typedef struct
    {
      unsigned long int __val[_SIGSET_NWORDS];
    } __sigset_t;

    우리가 시그널 처리 중 막아야 할 시그널들을 정해줍니다.

     

    <SA-FALG>

    #define	SA_NOCLDSTOP  1	/*부모 프로세스에 SIGHLD신호를 보내지 않음.*/
    #define SA_NOCLDWAIT  2	/*자식 프로세스 좀비 프로세스 생성하지 않음.*/
    #define SA_SIGINFO    4	/*이것을 지정해줘야 INFO도 같이 넘어감. 아니면 숫자만 넘어감.*/
    #if defined __USE_XOPEN_EXTENDED || defined __USE_MISC
    # define SA_ONSTACK   0x08000000 /*이 값을 설정하면 대체 스택에서 시그널을 처리함.*/
    #endif
    #if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8
    # define SA_RESTART   0x10000000 /* 시그널 처리 시 핸들러에 의해 중지된 기능 재시작*/
    # define SA_NODEFER   0x40000000 /* 커널에서 시그널을 처리하는 동안 자동 블록이 안됨*/
    # define SA_RESETHAND 0x80000000 /* 시그널을 처리하는 동안 시그널 블록이 안됨.*/
    #endif

    Bonus파트에서 Client에게 다시 보내기 위해 siginfo의 SA_SIGINFO를 사용한다는 FLAG를 사용 할 것입니다.

     

     

    <Sigintfo>

    ```c
    typedef struct
      {
        int si_signo;		/* Signal number.  */
    #if __SI_ERRNO_THEN_CODE
        int si_errno;		/* If non-zero, an errno value associated with
    				   this signal, as defined in <errno.h>.  */
        int si_code;		/* Signal code.  */
    #else
        int si_code;
        int si_errno;
    #endif
    #if __WORDSIZE == 64
        int __pad0;			/* Explicit padding.  */
    #endif
    
        union
          {
    	int _pad[__SI_PAD_SIZE];
    
    	 /* kill().  */
    	struct
    	  {
    	    __pid_t si_pid;	/* Sending process ID.  */
    	    __uid_t si_uid;	/* Real user ID of sending process.  */
    	  } _kill;
    
    	/* POSIX.1b timers.  */
    	struct
    	  {
    	    int si_tid;		/* Timer ID.  */
    	    int si_overrun;	/* Overrun count.  */
    	    __sigval_t si_sigval;	/* Signal value.  */
    	  } _timer;
    
    	/* POSIX.1b signals.  */
    	struct
    	  {
    	    __pid_t si_pid;	/* Sending process ID.  */
    	    __uid_t si_uid;	/* Real user ID of sending process.  */
    	    __sigval_t si_sigval;	/* Signal value.  */
    	  } _rt;
    
    	/* SIGCHLD.  */
    	struct
    	  {
    	    __pid_t si_pid;	/* Which child.	 */
    	    __uid_t si_uid;	/* Real user ID of sending process.  */
    	    int si_status;	/* Exit value or signal.  */
    	    __SI_CLOCK_T si_utime;
    	    __SI_CLOCK_T si_stime;
    	  } _sigchld;
    
    	/* SIGILL, SIGFPE, SIGSEGV, SIGBUS.  */
    	struct
    	  {
    	    void *si_addr;	    /* Faulting insn/memory ref.  */
    	    __SI_SIGFAULT_ADDL
    	    short int si_addr_lsb;  /* Valid LSB of the reported address.  */
    	    union
    	      {
    		/* used when si_code=SEGV_BNDERR */
    		struct
    		  {
    		    void *_lower;
    		    void *_upper;
    		  } _addr_bnd;
    		/* used when si_code=SEGV_PKUERR */
    		__uint32_t _pkey;
    	      } _bounds;
    	  } _sigfault;
    
    	/* SIGPOLL.  */
    	struct
    	  {
    	    __SI_BAND_TYPE si_band;	/* Band event for SIGPOLL.  */
    	    int si_fd;
    	  } _sigpoll;
    
    	/* SIGSYS.  */
    #if __SI_HAVE_SIGSYS
    	struct
    	  {
    	    void *_call_addr;	/* Calling user insn.  */
    	    int _syscall;	/* Triggering system call number.  */
    	    unsigned int _arch; /* AUDIT_ARCH_* of syscall.  */
    	  } _sigsys;
    #endif
          } _sifields;
      } siginfo_t __SI_ALIGNMENT;
    ```

    sigaction을 통하여 siginfo를 전달 할 수 있습니다. 시그널에 대한 다양한 정보를 이용 할 수 있습니다.

     

    4. Pause : 신호를 받을 때 까지 프로세스를 쉬게 함

     

     

    ❓어떻게 구현할까?

     

    클라이언트 프로세스랑 서버 프로세스를 만들어 서버 프로세스에게 Data를 보내줍니다.

    그러나 우리에게 주어진 재료는 USR1, USR2, PID 뿐이 없습니다. 이것으로 설계를 해야합니다.

    그럼 숫자, 문자는 시간이 조금 걸리더라도 8비트로 쪼개서 전송이 가능합니다.

    그러나 유니코드는? "안녕"이란 문자열은 어떻게 전송 할 것인가 고민해봐야합니다.

    일단은 숫자, 문자에 대한 부분만 구현해보겠습니다.

    생각해보면 USR1과 USR0두 가지 신호를 사용하면 0과 1을 나타낼 수 있습니다.

    그리고 그것을 비트 연산자를 사용하여 옮겨가며 숫자, 문자를 표현 할 수 있습니다.

    또 하나의 방법은 2를 나누며 1,0을 구분하여 더해주는 방법이 있습니다.

    그러나 시간이 문제입니다. 이렇게 되면 타이밍을 잘 맞추지 못하면 서버에서는 이상한 값들을 출력하게 됩니다.

    그래서 sleep 또는 usleep을 이용하여 서버에게 신호를 처리 할 수 있는 시간을 줘야 합니다.

    그리고 함수는 출력을 위해서 ft__putchar_fd, ft__putstr_fd, ft__putnbr_fd을 사용합니다.

    PID를 숫자로 바꾸기 위해서 ft_atoi를 사용합니다.

    이렇게 정하고 서버부터 구현해보겠습니다.

     

    <Server.c>

    #include "utils.h"
    
    void	ft_signal(int sig)
    {
    		static int	res;
    		static int	cnt;
    	
    		if (cnt == 0)
    			cnt = 128;
    		if (sig == SIGUSR1)
    			res += cnt;
    		cnt /= 2;
    		if (cnt == 0)
    		{
    			ft_putchar_fd((char)res, 1);
    			cnt = 128;
    			res = 0;
    		}
    }
    
    int	main(void)
    {
    		pid_t	pid;
    	
    		pid = getpid();
    		ft_putstr_fd("PID = ", 1);
    		ft_putnbr_fd(pid, 1);
    		ft_putchar_fd('\n', 1);
    		signal(SIGUSR1, ft_signal);
    		signal(SIGUSR2, ft_signal);
    		while (1)
    			pause();
    		return (0);
    }

    문제의 요구사항대로 처음에 PID를 출력해줍니다.

    그리고 SIGUSR1, SIGUSR2를 사용해서 둘 중 하나의 신호가 들어오면 핸들러인 ft_signal이 동작합니다.

    그리고 128, 64, 32, 16, 8, 4, 2, 1까지 총 8번 8 bit를 완성시킵니다. (SIGUSR1 = 1, SIGUSR2 = 0)

     

    <Client.c>

    #include "utils.h"
    
    void	change_bit(int server_pid, char code)
    {
    		int	mask;
    	
    		mask = 128;
    		while (mask > 0)
    		{
    				if (code & mask)
    				{
    					if (kill(server_pid, SIGUSR1) == -1)
    						ft_putstr_fd("SIGUSR1 Error\n", 1);
    				}
    				else
    				{
    					if (kill(server_pid, SIGUSR2) == -1)
    						ft_putstr_fd("SIGUSR2 Error\n", 1);
    				}
    				usleep(100);
    				mask /= 2;
    		}
    }
    
    void	send(pid_t pid, char *str)
    {
    		int	server_pid;
    		int	i;
    	
    		server_pid = pid;
    		i = 0;
    		while (str[i])
    			change_bit(server_pid, str[i++]);
    }
    
    int	main(int argc, char **argv)
    {
    		pid_t	pid;
    	
    		pid = ft_atoi(argv[1]);
    		if (argc != 3)
    			ft_putstr_fd("Argument Error\n", 1);
    		if (pid <= 0)
    			ft_putstr_fd("PID Error\n", 1);
    		send(pid, argv[2]);
    		return (0);
    }

    Client는 Server PID문자열을 입력으로 받습니다.

    그리고 그것을 비트로 변환시켜 서버에게 1bit씩 전송합니다.

    send함수는 한 글자 씩 while문을 이용하여 전송하도록 설정해놨습니다.

    change_bit도 server에서와 마찬가지로 숫자를 줄이면서 한 비트씩 전송합니다.

    code & mask는 제일 앞 자리부터 1인지 0인지 판별하기 위해 사용하였습니다.

     

    💵 Bonus

    보너스는 유니코드 출력서버 측에서 받은 내용을 응답해주는 기능을 개발합니다.

     

    1. 유니코드 출력

     

    현재 UNIX는 2바이트 조합형 한글 부호를 사용하고 있습니다.

    그래서 Client입력에 한글 한자로 입력 할 경우 2바이트 조합으로 0 ~ 65520까지 다양한 글자를 쓸 수 있습니다.

    아래의 링크는 참고 표입니다.

     

    참고 : http://framework.xenosi.de/exams/allentities.php

     

    http://framework.xenosi.de/exams/allentities.php

     

    framework.xenosi.de

     

     

    2. 서버에서의 응답

     

    서버에서의 응답은 위에서 signal을 썼던 것을 sigaction으로 바꿔 핸들러에 siginfo→si_pid인자를 이용하면 됩니다.

    그러면 Client의 PID를 알 수 있어 8bit가 채워지고 다시 Client에게 신호를 보내주면 됩니다.

     

     

     

    이렇게 Minitalk 구현을 해봤습니다. 처음엔 signal부분을 공부하는게 많이 어려웠습니다.

    처음에 비트연산자로 구현을 했었는데 유니코드가 먹히지 않아서 새로 구성하였습니다. 물론 비트연산자로 유니코드를

    구현 할 수 있습니다. 그리고 클라이언트에서 서버로 전송 할 경우 비트의 순서를 앞에서부터 보낼지 뒤에서부터 보낼지

    고민하고 코드를 짜셨으면 합니다. 긴 글 읽어주셔서 감사합니다.

    반응형

    '42Seoul' 카테고리의 다른 글

    42Seoul - Push_swap  (0) 2021.09.13
    42Seoul - Netwhat  (0) 2021.05.26
    42Seoul - GNL(get_next_line)  (0) 2021.05.18
    42Seoul - Libft  (0) 2021.05.06
    42Seoul본 과정에 앞서 피신 후기..!!  (0) 2021.05.02

    댓글

Designed by Tistory.