[리눅스 프로그래밍] 시그널

Yoon Yeoung-jin·2022년 6월 26일
0

Linux

목록 보기
4/13

시그널이란.

  • 시그널 보내 시그널 보내 찌릿찌릿찌릿찌릿
  • 시그널은 프로세스에게 특정한 사건의 발생을 알려주는 소프트웨어적인 통지이다.
  • 시그널은 대표적으로 아래에 대해서 사용중이다.
    • Ctrl+C
    • Child process termination
    • Alarm
    • divide by zero
    • inter-process communication
    • etc
  • 이러한 시그널은 시그널 번호 + 추가적인 번호 + 사용자 정의 데이터를 담고 있다.

시그널이 처리하는 동작 및 대표적인 시그널 번호

시그널이 처리하는 동작은 3개이다.

  • 무시
    • 아무런 동작도 하지 않음.
    • SIGKILL, SIGSTOP은 무시 불가능 하다.
  • 처리
    • 시그널 별 처리 함수를 수행한다.
  • 기본 동작
    • 시그널 종류 별 기본 동작을 수행한다.
      • 프로세스 종료
      • 코어 덤프 생성 후 종료
      • 무시
      • 정지

주요 시그널 번호는 다음과 같다.

시그널의 실행과 상속

  • fork() → 자식 프로세스는 부모 프로세스의 시그널 동작을 상속받는다.
  • exec() → 부모 프로세스가 붙잡아 처리하는 시그널은 기본 동작으로 변경

재진입성

  • 시그널을 사용하게 되면 실행이 끝나기 전에 시그널을 시작하고 시그널이 끝나면 다시 본 코드를 이어나가야 된다.
  • 이때 실행이 끝나기 전에 중단되었다가 재개되었을때도 정상적으로 수행을 마치는 함수를 의미한다.
  • 재진입이 불가능한 함수와 시그널 핸들러 함수를 같이 사용하게 되면 데이터가 망가질 수 있다.
  • 따라서 시그널 핸들러를 사용하게 되면 아래 항목들에 대해서 유념하며 사용해야 한다.
    • 재진입이 가능한 함수만 사용해야 함.
    • 글로벌 데이터를 수정할 때 조심해야 한다.
    • 필요한 경우 시그널 블록 처리 → 데이터 처리 → 시그널 언블록 처리를 하여 데이터를 안전하게 다뤄야 한다.

사용 헤더.

  • signal.h

사용 함수

  • typedef void (*sighandler_t) (int);
  • signal(): 시그널 처리 방법 설정 함수
    sighandler_t signal(int signum, sighandler_t handler);
    
    파라미터
    - signum: 처리 대상 시그널 번호
    - handler: 시그널 핸들러
    - SIG_IGN: 해당 시그널을 무시 처리 한다.
    - SIG_DFL: 해당 시그널을 기본 동작 처리 한다.
    - 그외 사용자 정의 시그널 핸들러
    반환값
    - 성공 : 이전 시그널 핸들러
    - 실패 : SIG_ERR
  • kill(): 시그널을 보내는 함수
    int kill(pid_t pid, int sig);
    
    파라미터
    - pid: 시그널 송신 대상 지정
    		- 1 이상: 프로세스 ID
    		- 0: 프로세스 그룹 전체
    		- -1: 권한 내의 모든 프로세스
    		- -1 미만: 프로세스 그룹 아이디가 (-pid)인 프로세스 그룹 전체
    - sig: 보낼 시그널 번호
    		-, 0인 경우 보내지는 않고, process 유무 및 권한 판단 수행
    반환값
    - 하나 이상의 프로세스에게 송신시: 0 
    - 실패: -1
  • sigprocmask: blocked signal 리스트에 추가 혹은 삭제 하는 함수.
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    파라미터
    - how
    	- SIG_BLOCK: blocked signal에 추가
    	- SIG_UNBLOCK: 현재 blocked signal에서 제외
    	- SIG_SETMASK: set 변수를 blocked signal로 설정
    - set: 설정할 sigset
    - oldset: 기존 설정되어 있던 sigset
    반환값
    - 성공: 0 
    - 실패: -1
  • sigset_t 설정 함수들
    int sigemptyset(sigset_t *set);
    - sigset 변수를 empty set으로 설정(아무런 시그널 설정되어 있지 않음) 
    
    int sigfillset(sigset_t *set);
    - sigset 변수에 모든 시그널을 설정
    
    int sigaddset(sigset_t *set, int signum);
    - sigset 변수에 특정 시그널을 추가
    
    int sigdelset(sigset_t *set, int signum);
    - sigset 변수에서 특정 시그널을 삭제
    
    int sigismember(const sigset_t *set, int signum); - sigset 변수에서 특정 시그널이 포함되어 있는지 확인
    - 포함시1리턴
  • sigaction: signal() 함수보다 더 향상된기능을 제공하는 시그널 처리 함수이다.
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    파라미터
    - signum: 대상 시그널 번호
    - act: 시그널 처리 액션
    - oldact: 기존 시그널 처리 액션
    반환값
    - 성공: 0 
    - 실패: -1
    
    struct sigaction {
    		void (*sa_handler)(int);
    		void (*sa_sigaction)(int, siginfo_t *, void *); 
    		sigset_t sa_mask;
    		int sa_flags;
    		void (*sa_restorer)(void); /* 사용되지 않음 */
    };
    • sa_flags에 SA_SIGINFO 포함 시 sa_sigaction이 호출됨
    • sa_mask는 시그널 핸들러 실행하는 동안 블록해야 할 시그널 모음 정의
    • sa_flags 플래그 리스트
      flags의미
      SA_SIGINFOsa_sigaction()이 사용됨.
      SA_NODEFER설정시 시그널 핸들러가 실행 중인 시그널에 대해 블록하지 않음
      SA_NOCLDSTOPsigno가 SIGCHLD인 경우 자식이 정지해도 SIGCHLD를 보내지 않도록 함
      SA_NOCLDWAITsigno가 SIGCHLD인 경우 자식에 대한 자동처리 활성화 (wait() 필요 없음)
      SA_RESETHAND시그널 핸들러가 1회면 동작한 후 default로 초기화 된다.
      SA_RESTARTread()등의 시스템콜이 자동으로 재시작된다.
    • siginfo_t 주요 필드
      siginfo_t {
                     int      si_signo;     /* Signal number (주요)*/
                     int      si_errno;     /* An errno value (주요) */
                     int      si_code;      /* Signal code (주요) */
                     int      si_trapno;    /* Trap number that caused
                                               hardware-generated signal
                                               (unused on most architectures) */
                     pid_t    si_pid;       /* Sending process ID */
                     uid_t    si_uid;       /* Real user ID of sending process */
                     int      si_status;    /* Exit value or signal */
                     clock_t  si_utime;     /* User time consumed */
                     clock_t  si_stime;     /* System time consumed */
                     sigval_t si_value;     /* Signal value */
                     int      si_int;       /* POSIX.1b signal sigqueue()를 통해 보낸 시그널 페이로드 */
                     void    *si_ptr;       /* POSIX.1b signal sigqueue()를 통해 보낸 시그널 페이로드 */
                     int      si_overrun;   /* Timer overrun count;
                                               POSIX.1b timers */
                     int      si_timerid;   /* Timer ID; POSIX.1b timers */
                     void    *si_addr;      /* Memory location which caused fault 
      													SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP의 경우 장애를 일으킨 주소*/
                     long     si_band;      /* Band event (was int in
                                               glibc 2.3.2 and earlier) */
                     int      si_fd;        /* File descriptor SIGPOLL의 경우 작업을 완료한 파일 디스크립터 */
                     short    si_addr_lsb;  /* Least significant bit of address
                                               (since Linux 2.6.32) */
                     void    *si_lower;     /* Lower bound when address violation
                                               occurred (since Linux 3.19) */
                     void    *si_upper;     /* Upper bound when address violation
                                               occurred (since Linux 3.19) */
                     int      si_pkey;      /* Protection key on PTE that caused
                                               fault (since Linux 4.6) */
                     void    *si_call_addr; /* Address of system call instruction
                                               (since Linux 3.5) */
                     int      si_syscall;   /* Number of attempted system call
                                               (since Linux 3.5) */
                     unsigned int si_arch;  /* Architecture of attempted system call
                                               (since Linux 3.5) */
                 }
    • 모든 시그널 번호에 대해 유효한 si_code
      si_codemeaning
      SI_ASYNCIO비동기식 입출력의 완료 표시
      SI_KERNEL커널이 보낸 시그널
      SI_MESGQPOSIX 메시지 큐의 상태 변화 표시
      SI_QUEUEsigqueue()로 보낸 시그널
      SI_TIMERPOSIX 타이머 만료를 표시
      SI_USERkill() 또는 raise()로 보낸 시그널
    • SIGCHLD인 경우에만 유효한 si_code
      si_codemeaning
      CLD_CONTINUED자식이 정지되었다가 재시작됨
      CLD_DUMPED자식이 비정상적으로 종료됨
      CLD_EXITED자식이 exit()로 정상 종료됨
      CLD_KILLED자식이 종료됨
      CLD_STOPPED자식이 정지됨
      CLD_TRAPPED자식이 트랩에 걸림
  • sigqueue: 페이로드와 함께 시그널을 전송하는 함수
    union sigval {
        int sival_int;
        void *sival_ptr;
    };
    int sigqueue(pid_t pid, int signo, const union sigval value);
    파라미터
    - pid: 대상 프로세스
    - signo: 시그널 번호
    - value: 페이로드
    반환값
    - 성공: 0 
    - 실패: -1
    해당 시그널을 sigaction()으로 받으면...
    • siginfo_t.si_code: SI_QUEUE 로 설정
    • siginfo_t.si_int: 페이로드 설정

    예제 코드

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

#include<signal.h>
#include<sys/signal.h>

void ctrl_c_sighandler(int signum)
{
    printf("CTRL + C \n");
}

int main(int argc, char **argv)
{
    signal(SIGINT, ctrl_c_sighandler);

    while(1){
        sleep(1);
    }   
    return 0;
}
profile
신기한건 다 해보는 사람

0개의 댓글