42 Seoul

미니톡 - signal.h

parktest0325 2021. 8. 21. 20:01

과제

커스텀 시그널 2개를 이용하여 클라이언트 -> 서버로 메세지 보내는 시스템을 만들어야한다.

kill 명령을 통해 시그널을 전달할 수 있다. 

kill -USR1 [pid]

 

사용할 수 있는 함수

  • write
  • signal
  • sigemptyset
  • sigaddset
  • sigaction 
  • kill. int kill(pid_t pid, int sig); 해당하는 프로세스에 시그널보내는 함수
  • getpid pid_t getpid(void);  현재 실행중인 프로세스의 pid반환
  • malloc
  • free
  • pause : 시그널 수신할때까지 대기하는 함수
  • sleep
  • usleep
  • exit

 

#include <signal.h>

sigempty, sigaction, sigaddset, kill

#include <unistd.h>

usleep, write, getpid, pause

#include <stdlib.h>

malloc, free, exit

 

이것처럼 시그널함수를 통해 시그널을 받으면 시그널에 맞는 핸들러 함수를 호출할 수 있다.

지금 생각은 클라이언트에서 문자를 비트열(?) 1010101011011 로 변환한다음 USR1은 0, USR2는 1 비트를 서버에 순서대로 보내고, 서버는 대기하면서 쉬프트 연산으로 비트를 쌓은 뒤 출력하면 될것이라 생각된다.

널문자(USR1*8)가 들어올때까지 데이터를 받고나면 서버는 출력하고 다음 시그널을 받는다.

서버는 문자열을 저장할 메모리를 할당해둬야 하기 때문에 미리 길이를 입력받는것도 좋다

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

void handler(int sig){
    printf("signal no(%d) Received\n",sig);
}

main()
{  
    if(signal(SIGUSR1,handler)==SIG_ERR)    {
        fprintf(stderr,"cannot set USR1\n");
        exit(1);
    }                
    if(signal(SIGUSR2,handler)==SIG_ERR)  {
        fprintf(stderr,"cannot set USR2\n");
        exit(1);
    }
    for(;;)       pause();
}

 

 

  • 서버와 클라이언트 중 서버가 먼저 실행되어야 하며, 클라이언트가 런치가 될 때에 PID를 표시해야 합니다.
  • 클라이언트가 실행될 때 다음의 매개변수를 받습니다 :
    • 서버 PID
    • 전송할 문자열
  • 클라이언트는 매개변수로 전달한 문자열을 서버로 통신해야 합니다. 서버는 문자열이 수신되면 해당 문자열을 표시해야 합니다.
  • 서버는 문자열을 매우 빠른 속도로 표시할 수 있어야 합니다. 즉, 표시되는 시간이 너무 길다고 생각된다면, 그건 너무 길다고 여겨야 합니다. (힌트 : 100개의 문자로 이루어진 문자열을 표시하는 데 1초가 걸린다면 그건 어마어마하게 긴 것입니다.)
  • 서버가 재시작할 필요없이 여러 클라이언트로부터 문자열을 연속으로 수신할 수 있어야 합니다.
  • SIGUSR1 SIGUSR2 두 신호만 사용할 수 있습니다.
  • 실행파일은 각각 client와 server로 이름을 지어야 합니다.

시그널

signal : 단순히 signal 핸들러만 지정할 수 있음. 단발성. 그리고 os별로 차이가 있어 주의가필요.

void (*signal (int signum, void (*func)(int))) (int); 
 - int  signum
 ; 시그널 상수. 각각의 시그널에 부여된 상수 번호

 - void (*func) (int)
 ; 시그널 핸들러. void형의 반환 타입에 int형의 인자를 한개 받는 함수를 구현하여 함수 이름을 인자로 넘기면된다. 
 ; 함수명이 바로 포인터 상수가 되기 때문이다.
 
 - return type
 ; 핸들러 함수의 주소

 

sigaction : signal 핸들러 지정하는것 외에도 플래그를 통해 처리과정 제어, 시그널 처리중 시그널 블록가능, 한번 지정하면 계속 사용가능하다는 장점이 있다. 요즘은 대부분 sigaction을 사용한다고 한다. 

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldAct);
 - int signum
 ; 시그널 상수.
 
 - const struct sigaction *act
 ; 시그널 상수에 대한 처리를 할 시그널 핸들러 등의 정보가 정의된 구조체의 포인터.
 
 - struct sigaction *oldAct
 ; 원래 사용되던 시그널 핸들러 정보가 정의된 구조체 정보를 담을 구조체 포인터. 
 ; 기존의 처리를 저장하였다가 필요한 만큼 정의한 처리를 수행하고 다시 기존의 처리로 되돌릴 때 
 ; 저장해두었던 것을 다시 설정해서 되돌릴때 유용하다. 필요없을 경우 NULL 또는 0값을 입력하면 된다.
 
 - return type
 ; 성공시 0. 실패시 -1을 반환한다.

 

struct sigaction

#define sa_handler      __sigaction_u.__sa_handler
#define sa_sigaction    __sigaction_u.__sa_sigaction

struct  sigaction {
    union __sigaction_u {    // 핸들러 함수 두개가 공용체 형태로 묶여있따.
        void    (*__sa_handler)(int);  // sa_flags에서 SIGINFO가 세팅되지 않은경우 이 핸들러 호출
        void    (*__sa_sigaction)(int, siginfo_t *, void *); // 세팅된 경우 호출하는 핸들러
    } __sigaction_u;  
    sigset_t sa_mask; // 핸들러 실행중에 블록할 시그널셋을 지정한다.         
    int     sa_flags; // flag를 지정해서 핸들러가 실행될때 옵션을 지정할 수 있다.
};

     union __sigaction_u

 

sa_flags

  플래그  설명
 SA_ONSTACK  이 값을 설정하고 시그널을 받으면 시그널을 처리할 프로세스에 sigaltstack 시스템 호출로 생성한 대체 시그널 스택이 있는 경우에만 대체 스택에서 시그널을 처리합니다. 그렇지 않으면 시그널은 일반 스택에서 처리됩니다.
 SA_RESETHAND  이 값을 설정하고 시그널을 받으면 시그널의 기본 처리 방법은 SIG_DFL로 재설정되고 시그널이 처리되는 동안 시그널을 블록하지 않습니다.
 SA_NODEFER  이 값을 설정하고 시그널을 받으면 시그널이 처리되는 동안 유닉스 커널에서 해당 시그널을 자동으로 블록하지 못합니다.
 SA_RESTART  이 값을 설정하고 시그널을 받으면 시스템은 시그널 핸들러에 의해 중지된 기능을 재시작하게 합니다.
 SA_SIGINFO  이 값이 설정되지 않은 상태에서 시그널을 받으면 시그널 번호만 시그널 핸들러로 전달됩니다. 만약 이 값을 설정하고 시그널을 받으면 시그널 번호 외에 추가 인자 두 개가 시그널 핸들러로 전달됩니다. 두 번째 인자가 NULL이 아니면 시그널이 발생한 이유가 저장된 siginfo_t 구조체를 가리킵니다. 세 번째 인자는 시그널이 전달될 때 시그널을 받는 프로세스의 상태를 나타내는 ucontext_t 구조체를 가리킵니다.
 SA_NOCLDWAIT  이 값이 설정되어 있고 시그널이 SIGCHLD면 시스템은 자식 프로세스가 종료될 때 좀비 프로세스를 만들지 않습니다.
 SA_NOCLDSTOP  이 값이 설정되어 있고 시그널이 SIGCHLD면 자식 프로세스가 중지 또는 재시작할 때 부모 프로세스에 SIGCHLD 시그널을 전달하지 않습니다.

 

int sigemptyset(sigset_t *set);

sigset_t 구조체를 비우는 함수. sigaction.sa_mast 인데, 시그널 처리중 블록될 시그널 플래그를 세팅해준다. 

 

int sigaddset(sigset_t *set, int signum);

시그널 집합에 두번째 인자로 전달한 시그널을 추가하는것이다. 

 

 

시그널의 발생

0으로 나누는 등 예외적인 상황이 발생할 때

kill함수 등과 같이 함수를 통해 시그널을 발생시켰을때

Ctrl+C 같은 인터럽트 키를 입력한 경우

 

시그널의 처리

각각의 시그널에는 기본 동작이 정의되어 있다.

핸들러를 등록하지 않으면 기본동작이 실행되고, 시그널을 무시하거나 핸들러를 등록해서 핸들러를 실행시키는 방법도 있다.

특정 코드가 실행될때 시그널을 블록시킬 수 있는데, 무시와 다르게 블럭이 해제되면 순차적으로 전달된다.

 

시그널 원인 검색

SA_SIGINFO 플래그가 세팅되면 핸들러로 sa_sigaction이 실행되는데  인자로 전달되는 siginfo_t 구조체에서 시그널 발생원인을 알 수 있다. siginfo.h 의 psiginfo 함수에 구조체를 전달하면 표준출력으로 출력해주기도 한다.

 


SIGACTION 사용법

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signalHandler(int signo);

main()
{
    struct sigaction act;

    /* 시그널이 오면 signalHandler를 실행하도록 설정 */
    act.sa_handler = signalHandler;
    /* 모든 시그널은 시그널을 처리하는 동안 블록되도록 설정 */
    sigfillset(&act.sa_mask);
    act.sa_flags = 0;
    /* SIGINT에 대해 act 행동을 하도록 설정 */
    sigaction(SIGINT, &act, NULL);
    printf("call sigaction\n");
    while(1)
       sleep(5);
}

void signalHandler(int signo)
{
    printf("start handler\n");
    sleep(3);
    printf("end handler\n");
}

 

[localhost@local]#a.out
call sigaction
^C              --> signalHandler 함수 실행
start handler
^Z              --> 블록화 됨(sigstop 시그널을 보내지만 블록화됨)
end handler
                --> 블록화 되었던 ^Z(sigstop) 시그널 해제되어 프로세스 멈춤
[1]+ Stopped                       ./a.out