[42서울] minitalk : UNIX signal

jabae·2022년 6월 23일
0

42Seoul

목록 보기
15/20

📡 UNIX signal

signal은 Software interrupt로, 프로세스에 무엇인가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것이다. 쉽게 말하자면 특정 이벤트가 발생했을 때, 신호를 알려주는 걸 말한다.

운영체제가 개입해서 처리해줘야 하는 다양한 상황이 있기 때문에 다양한 시그널의 종류가 존재하는데, kill -l 명령어를 통해 시그널 목록을 확인할 수 있다.

시그널은 <signal.h> 헤더 파일에 정의되어있다. 시그널을 받은 프로세스는 시그널에 따라 미리 지정된 기본 동작을 수행하거나, 미리 정해놓은 함수에 의해 원하는 동작을 할 수 있다. 미리 지정된 기본 동작은 종료, 코어 덤프, 무시, 중지, 재시작 등이 있다.

자주 사용하는 시그널은 다음과 같다.

  • SIGINT : Ctrl + c 입력시, 기본 처리 종료
  • SIGQUIT : Ctrl + ₩ 입력시, 기본 처리 코어 덤프
  • SIGKILL : 강제 종료시, 기본 처리 종료
  • SIGSEGV : 세그먼트 폴트 시, 기본 처리 코어 덤프
  • SIGTSTP : Ctrl + z 입력시, 기본 처리 중지

과제에서 허용되는 시그널은 다음 두 개이다.

  • SIGUSR1 : 사용자 정의 시그널1, 기본 처리 종료
  • SIGUSR2 : 사용자 정의 시그널2, 기본 처리 종료

SIGKILLSIGSTOP은 사용자가 절대 제어할 수 없다. 만약 프로세스를 계속 생성하는 좀비 프로세스가 있다고 하면, 이 프로세스를 컨트롤 하지 못한다면 큰 일이 나기 때문에 중요한 작업을 하는 시그널은 재정의 할 수 없다.

  • ❓ interrupt
    하던 일을 잠시 멈추고, 다른 일을 하고 난 후 다시 돌아와 멈춘 부분부터 일을 하는 것. signal이 발생하면 프로그램의 정상적인 실행을 멈추고 주어진 signal 유형에 대한 실행을 하기 때문에 signal을 Software interrupt라고 한다.

  • ❓ core dump
    컴퓨터 프로그램이 특정 시점에 작업 중이던 메모리 상태를 기록한 것으로, 보통 프로그램이 비정상적으로 종료했을 때 만들어진다.

🔌 예시

시그널의 예시로, Ctrl + c로 프로그램을 강제 종료하는 것이 있다. 사용자의 특정 입력이 들어왔을 때, SIGINT 시그널이 생성되고 프로세스에 전달된다. 또, kill 명령으로 프로세스 종료시, 프로세스에는 SIGTERM 시그널이 전달된다.

🛠 signal 사용하기

signal() 함수를 통해 이러한 특정 입력이 들어왔을 때, 원하는 동작을 하게 할 수 있다. 함수 원형은 다음과 같다. 성공 시, 이전 액션을 실행하고 실패 시, SIG_ERR(errno를 설정)

  • void (signal(int sig, void (func)(int)))(int);
  • sig_t signal(int sig, sig_t func);
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint_handler(int signo)
{
	printf("^C를 누른 것을 기억하고 있어요. 다시 누르면 종료\n");
	signal(SIGINT, SIG_DFL);
}

int main(void)
{
	signal(SIGINT, sigint_handler);
	while(1)
	{
    printf("jabae is HAPPY!\n");
    sleep(1);
	}
}

메인문 안에서 signal(SIGINT, sigint_handler)ctrl + c가 들어왔을 때, sigint_handler가 동작하도록 등록해 놓는다. 그리고 다시 한 번 ctrl + c를 눌렀을 때, SIG_DFLSIGINT 시그널의 기본 동작을 실행시킨다.

🧳 허용 함수

✅ signal

시그널(sig)과 시그널을 처리할 핸들러(func)를 인수로 넣어주면 핸들러를 반환한다.

함수 원형

  • void (*signal(int sig, void (*func)(int)))(int)
  • sig_t signal(int sig, sig_t func)

리턴 값 : 성공시 이전 액션, 실패시 SIG_ERR(errno를 설정)

✅ sigemptyset

setset_t라는 집합에서 모든 시그널을 제거한다.

함수 원형

  • int sigemptyset(sigset_t *set);

리턴 값 : 성공시 0, 실패시 -1을 반환

✅ sigaddset

setset_t라는 집합에 두번째 인자 시그널(signo)을 추가한다.

함수 원형

  • int sigaddset(sigset_t *set, int signo)

리턴 값 : 성공시 0, 실패시 -1을 반환

✅ sigaction

signal보다 향상된 기능을 제공하는 시그널 처리를 결정하는 함수이다. 시그널(signum)이 발생하면 새로 실행할 행동(act)과 갖고 있는 이전 행동(oldact)을 인자로 받는다.

함수 원형

  • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

리턴 값 : 성공시 0, 실패시 -1을 반환

✅ kill

시그널을 받을 프로세스의 id(pid)를 받아 해당하는 프로세스에 시그널(sig)을 보낸다.

함수 원형

  • int kill(pid_t pid, int sig)

리턴 값 : 성공시 0, 실패시 -1을 반환

✅ getpid

현재 프로세스의 프로세스 id를 얻는다.

함수 원형

  • pid_t getpid(void);

리턴 값

  • 프로세스 id
  • 리눅스 기준 범위: 2~32768
  • 맥 기준 범위: 100~99999

✅ pause

시그널이 도착할 때까지 실행을 중단시킨다.

함수 원형

  • int pause(void);

리턴 값 : 항상 -1 리턴

✅ sleep

인수로 주어진 seconds 만큼 작업을 중단시킨다.

함수 원형

  • unsigned int sleep(unsigned int seconds);

리턴 값 : 지정된 시간이 되면 0을 반환, 그러나 그 중에 시그널이 발생했다면 남은 시간을 반환한다.

✅ usleep

주어진 ms만큼 작업을 중단한다. 시스템 활동이 예기치 않게 sleep 기간을 늘릴 수 있다.

함수 원형

  • int usleep(useconds_t microseconds);

리턴 값 : 성공 시 0, 실패 시 -1을 반환(errno를 설정)

✅ exit

status 인수 값은 0 - 255이거나 매크로 EXIT_SUCCESS 또는 EXIT_FAILURE 중 하나이다.

함수 원형

  • void exit(int status);

리턴 값 : status 값과 제어 둘 다 리턴한다.

👊 구현하기

과제의 목표는 클라이언트와 서버로 이루어진 통신 프로그램을 작성하는 것이다. 서버가 먼저 실행되어 자신의 PID를 출력하고, 클라이언트는 서버의 PID 값과 서버로 전송할 문자열을 인자로 받아 서버로 전송해야 한다. 이때, SIGUSR1SIGUSR2 단 두 개의 시그널만 사용해야 한다.

1. 클라이언트 input 값 유효성 검사

유효한 PID 값인지, 인자 2개(서버의 PID, 문자열)이 들어오는 지

2. 인자로 들어오는 문자열을 한 글자씩 쪼개고, 다시 비트 연산으로 1이면 SIGUSR1, 0이면 SIGUSR2를 서버로 시그널을 보낸다.

여기서 헤메었던 부분이 비트 연산이다. 사용해 본 적이 없어서 낯설었는데, 역시 피신 때부터 나에게 도움을 주었던 c언어 코딩도장... 비트연산도 있어서 공부하기 좋았다.
또 여기서 중요한 건 계속 클라이언트 쪽에서 시그널을 일방적으로 쏘면 그 시그널이 꼬일 수 있으므로 usleep()으로 적당하게 텀을 주어야 한다.

3. 이제 서버를 구현한다. 일단 실행이 되면 자신의 PID를 출력하게 한다.

4. 그리고 signal()로 시그널이 들어왔을 때, 8개의 시그널(비트)을 모아주는 함수를 구현한다.

5. 이 8개의 시그널(비트)를 하나의 문자로 만들어주고, 출력하는 함수를 만들면 끝!

😊 bonus)유니코드 전달하기

❓유니코드
1바이트로 표현 가능한 127번까지(~255번: 확장된 아스키코드) 아스키 코드를 제외한 256번부터의 친구들!

write는 1바이트씩 출력을 하는데 어떻게 2~4바이트의 유니코드를 출력해 줄 수 있을까?

유니코드는 맨 앞비트를 통해 몇 바이트의 문자인지 알 수 있다. write는 몇 바이트인지 알고, 그 바이트만큼 시그널을 받기 때문에 유니코드 문자를 출력해줄 수 있다!😊👍

💡 참고

https://dojang.io/mod/page/view.php?id=172
https://jaeseokim.dev/C/C-유니코드(unicode)에_대해_알아보기(feat.42seoul_ft_printf)/
https://namu.wiki/w/UTF-8

😃 Thanks to

플젝을 하느라 블랙홀의 압박에 시달리던 저에게 처음부터 끝까지 가르쳐 주신 dha, daekim 선생님 감사드립니다!!😇😇😇

profile
it's me!:)

0개의 댓글