leebaek

[UNIX프로그래밍] 8강 - 시그널 본문

수업 정리/UNIX 프로그래밍

[UNIX프로그래밍] 8강 - 시그널

leebaek 2024. 11. 17. 17:39

  • signal
  • child proxess의 종료 상태 확인
  • signal 보내기
  • signal handling
  • sigaction의 구조
  • signal 사용 예시
  • signal 집합 지정
  • sa_sigaction()에 의한 signal handling
  • 이전의 설정 복원하기
  • alarm signal 설정
  • signal blocking
  • pause 시스템 호출

■ Signal

signal : software interrupt

- kernel -> process 로 알리거나, process -> process 로 알림

- 자료 전송 보다는 비정상적인 상황을 알릴 때 사용

 

ex. program 수행 중 Ctrl-C 를 누르면

- kernel 이 문자 감지하고, 해당 session에 있는 모든 process에게 SIGINT signal을 보냄

- 모든 process는 종료함 ( 단, shell process는 무시함 )

 

□ signal 처리 방법

  1. Default action : 기본동작( 종료 / 무시 / 중지 /코어 덤프 -비정상적인 종료일 시, OS에서 프로그램 종료 이유를 알려줌 )
  2. 무시 ( signal을 받는 프로세스가 signal을 무시함 )
  3. 지정된 함수 호출
  4. signal block ( signal을 받음, 당장은 다른 일을 하고 일을 처리한 뒤 block해둔 signal 처리 )

■ Child process의 종료 상태 확인

pid = wait(&status);

if ( WIFEXITED(status)) // 정상 종료
	printf("%d\n", WEXITSTATUS(status));
    
if ( WIFSIGNALED(status)) // 시그널을 받고 종료
	printf("%d\n", WTERMSIG(status));

 

 

- 매크로 WIFSIGNALED는 자식 프로세스가 신호를 받아 종료되었는지 확인함

- 예를 들어, SIGKILL 또는 SIGINT 등의 신호로 종료된 경우임

 

- WTERMSIG는 자식 프로세스를 종료시킨 신호 번호를 반환함

- 신호 번호는 각 신호의 정의에 따라 다르며, 예를 들어 SIGKILL은 9, SIGINT는 2임


■ Signal 보내기

□ kill() : 지정한 process나 process group에 signal을 보냄

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

 

- pid: signal을 받게 될 process 지정

- sig: 보낼 signal을 지정

 

□ signal을 받을 process 또는 process group  지정

  • pid > 0 : 해당 id의 proces에게 signal 전달
  • pid = 0 : sender와 같은 process group에 속하는 모든 process에게 signal 전달 ( sender도 포함 )
  • pid = -1 : uid가 sender의 euid와 같은 모든 proecess에게 signal 전달
  • pid < 0 & pid != -1 : process의 group id가 pid의 절대값과 같은 모든 process에게 signal 전달

- 다른 사용자의 process에게 signal을 보내면 -1 return 

 

□ raise() : 호출한 process에 signal을 보냄

#include <signal.h>
int raise(int sig);

■ Signal handling

  • default action ( 프로세스 종료 )
  • 무시
  • 정의된 함수 호출

□ sigaction() 

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

 

□ sigaction 지정 : signal 수신 시 원하는 행동을 취할 수 있음

-> 예외 ) SIGSTOP(process의 일시 중단), SIGKILL(process의 종료)의 경우는 별도의 action을 취할 수 없음

 

sigaction의 구조

struct sigaction {
    void (*sa_handler)(int); // 기본 신호 처리기 함수
    sigset_t sa_mask; // 처리 중 blocking할 신호
    int sa_flags; // 신호 처리 동작 옵션
    void (*sa_sigaction)(int, siginfo_t *, void *); // 확장된 신호 처리기 함수
}

 

▷ void (*sa_handler)(int) - 함수 인자에 int signo 필요 ( ex. catchint(int signo) )

- signo를 수신하면 취할 행동 지정

- SIG_DFL : default 행동 ( 종료, 무시 등 )

- SIG_IGN : 신호를 무시

- 사용자 정의 함수 : signal을 받으면 함수로 제어 이동, 함수 실행 후 signal을 받기 직전의 처리 문장으로 return

 

▷ sigset_t sa_mask

- signal을 처리하는 동안 차단할 signal 집합을 설정함 ( blocking )

 

▷ int sa_flags

- SA_RESETHAND : 신호를 한번 처리한 후, signal action을 SIG_DFL로 재설정

- SA_SIGINFO : sa_handler 대신 sa_sigaction을 사용하도록 설정


■ Signal 사용 예

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

int main() {
    static struct sigaction act;
    void catchint(int);

    act.sa_handler = catchint;
    sigaction(SIGINT, &act, NULL);

    printf("sleep call1\n");
    sleep(1);
    printf("sleep call2\n");
    sleep(1);
    printf("exiting\n");
    exit(0);
}

void catchint(int signo) {
    printf("\n CATCHINT: signo=%d\n", signo);
}

 

- SIGINT를 무시

act.sa_handler=SIG_IGN
sigaction(SIGINT, &act, NULL);

 

- SIGINT시 종료

act.sa_handler = SIG_DFL;
sigaction(SIGINT, &act, NULL);

 

- 여러개의 signal을 무시

act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, NULL);
sigaction(SIGQUIT, &act, NULL);

 

함수 지정

- 핸들러로 지정된 함수 호출하는 것을 알 수 있음

 

signal 무시

- Ctrl-C 무시하고 프로그램이 돌아가는 것을 알 수 있음

기본 동작 - 종료

- Ctrl-C 프로세스가 종료되어 exiting까지 출력되지 않음


■ Signal 집합 지정

□ signal 집합 지정

  • sigemptyset -> sigaddset
  • sigfillset -> sigdelset
#include <signal.h>
int sigemptyset(sigset_t *set); // 빈 신호 집합 생성
int sigfillset(sigset_t *set); // 신호 집합에 모든 신호 추가

int sigaddset(sigset_t *set, int signo); // 특정 신호를 집합에 추가
int sifdelset(sigset_t *set, int signo); // 특정 신호를 집합에서 제거

int sigismember(sigset_t *set, int signo); // 특정 신호가 집합에 포함되어있는지 확인
sigset_t mask1, mask2;

sigemptyset(&mask);
sigaddset(&mask1, SIGINT);
sigaddset(&mask1, SIGQUIT);

siggillset(&mask2);
sigdelset(&mask2, SIGCHLD);

■ sa_sigaction()에 의한 signal handling

□ act.sa_flags = SA_SIGINFO 설정

- 시그널과 관련된 추가 정보를 받을 수 있어 복잡한 시그널 처리나 디버깅에 유용

#include <signal.h>
#include <stdio.h>
void handler(int signo, siginfo_t *sf, void *uc);

int main() {
    static struct sigaction act;

    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = handler;
    sigaction(SIGINT, &act, NULL);
    sleep(3);
    raise(SIGUSR1);  // 현재 프로세스에 SIGUSR1 신호를 보냄

}

void handler(int signo, siginfo_t *sf, void *uc) {
    psiginfo(sf, "...:");
    printf("%d\n", sf->si_code);
}

■ 이전의 설정 복원하기

□ sigaction(SIGTERM, NULL, &oact)

sigaction(SIGTERM, NULL, &oact) // 과거 설정 저장

act.sa_handler = SIG_IGN;
sigaction(SIGTERM, &act, NULL);

// do anything

sigaction(SIGTERM, &oact, NULL);
  1. SIGTERM 신호의 현재 설정을 oact에 저장
  2. SIG_IGN을 sa_handler에 설정하여 SIGTERM 신호를 무시하도록 지정함
  3. SIGTERM 신호를 무시하도록 설정을 변경함
  4. 처음 저장했던 oact를 사용해 SIGTERM 신호의 원래 설정을 복구함
  5. SIGTERM 신호는 이전 동작(예: 기본 동작 또는 사용자 정의 핸들러)으로 돌아감

■ alarm signal 설정

□ alarm()

#include <signal.h>
unsigned int alarm(unsigned int secs);

 

- 시간(초) 종료 후, SIGALARM을 보냄

- alarm은 exec 후에도 계속 작동, 그러나 fork 후에는 자식 process에 대한 alarm은 작동하지 않음

 

alarm(5);
alarm(10);

// 15초 기다리는 것이 아니라 10초로 대체됨
// 첫번째 알람에서 2초가 지났어도, 두번째 알람은 10초 후 울림

alarm(0) // 알람 끄기

 

- alarm은 누적되지 않음, 2번 사용되면 두번째 alarm으로 대체됨

- 두번째 alarm의 return 값이 첫 alarm의 잔여시간임


■ signal blocking

□ sigprocmask()

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset)

 

 

  • how: 신호 마스크를 어떻게 처리할지 지정하는 값
    • SIG_SETMASK: 현재 신호 마스크를 set에 지정된 신호 집합으로 교체
    • SIG_UNBLOCK: set에 있는 신호들을 현재 마스크에서 제거(차단 해제)
    • SIG_BLOCK: set에 있는 신호들을 현재 마스크에 추가(차단)
  • set: 변경할 신호 집합의 포인터.
    • sigset_t 자료형으로, 차단하거나 차단 해제할 신호 집합을 지정
  • oset: 함수 호출 전에 현재 신호 마스크를 저장할 포인터
    • 이전 신호 마스크를 알고 싶다면 이 값을 사용
    • 관심이 없다면 NULL로 지정
#include <signal.h>
#include <stdio.h>

int main() {
    sigset_t set, oldset;

    sigemptyset(&set);              // 신호 집합 초기화
    sigaddset(&set, SIGINT);        // SIGINT(Ctrl+C) 신호 추가

    // SIGINT 차단
    if (sigprocmask(SIG_SETMASK, &set, &oldset) < 0) {
        perror("sigprocmask");
    }

    printf("SIGINT is now blocked. Press Ctrl+C...\n");
    sleep(5);

    // 이전 신호 마스크로 복원
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) {
        perror("sigprocmask");
    }

    printf("SIGINT is now unblocked.\n");
    sleep(5);

    return 0;
}

■ pause 시스템 호출

□ pause() : 호출된 프로세스의 실행을 무기한 대기 상태로 둠

#include <unistd.h>
int pause(void); // 항상 -1 반환

 

- signal 도착까지 실행을 일시 중단 ( CPU 사용 없이 )

- signal이 포착되면 처리 routine 수행하고 -1 return