signal은 Software interrupt로, 프로세스에 무엇인가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것이다. 쉽게 말하자면 특정 이벤트가 발생했을 때, 신호를 알려주는 걸 말한다.
운영체제가 개입해서 처리해줘야 하는 다양한 상황이 있기 때문에 다양한 시그널의 종류가 존재하는데, kill -l
명령어를 통해 시그널 목록을 확인할 수 있다.
시그널은 <signal.h>
헤더 파일에 정의되어있다. 시그널을 받은 프로세스는 시그널에 따라 미리 지정된 기본 동작을 수행하거나, 미리 정해놓은 함수에 의해 원하는 동작을 할 수 있다. 미리 지정된 기본 동작은 종료, 코어 덤프, 무시, 중지, 재시작 등이 있다.
자주 사용하는 시그널은 다음과 같다.
Ctrl + c
입력시, 기본 처리 종료Ctrl + ₩
입력시, 기본 처리 코어 덤프Ctrl + z
입력시, 기본 처리 중지과제에서 허용되는 시그널은 다음 두 개이다.
SIGKILL
과 SIGSTOP
은 사용자가 절대 제어할 수 없다. 만약 프로세스를 계속 생성하는 좀비 프로세스가 있다고 하면, 이 프로세스를 컨트롤 하지 못한다면 큰 일이 나기 때문에 중요한 작업을 하는 시그널은 재정의 할 수 없다.
❓ interrupt
하던 일을 잠시 멈추고, 다른 일을 하고 난 후 다시 돌아와 멈춘 부분부터 일을 하는 것. signal이 발생하면 프로그램의 정상적인 실행을 멈추고 주어진 signal 유형에 대한 실행을 하기 때문에 signal을 Software interrupt라고 한다.❓ core dump
컴퓨터 프로그램이 특정 시점에 작업 중이던 메모리 상태를 기록한 것으로, 보통 프로그램이 비정상적으로 종료했을 때 만들어진다.
시그널의 예시로, Ctrl + c
로 프로그램을 강제 종료하는 것이 있다. 사용자의 특정 입력이 들어왔을 때, SIGINT
시그널이 생성되고 프로세스에 전달된다. 또, kill
명령으로 프로세스 종료시, 프로세스에는 SIGTERM
시그널이 전달된다.
signal()
함수를 통해 이러한 특정 입력이 들어왔을 때, 원하는 동작을 하게 할 수 있다. 함수 원형은 다음과 같다. 성공 시, 이전 액션을 실행하고 실패 시, SIG_ERR(errno를 설정)
#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_DFL
로 SIGINT
시그널의 기본 동작을 실행시킨다.
시그널(sig)과 시그널을 처리할 핸들러(func)를 인수로 넣어주면 핸들러를 반환한다.
함수 원형
void (*signal(int sig, void (*func)(int)))(int)
sig_t signal(int sig, sig_t func)
리턴 값 : 성공시 이전 액션, 실패시 SIG_ERR(errno를 설정)
setset_t라는 집합에서 모든 시그널을 제거한다.
함수 원형
int sigemptyset(sigset_t *set);
리턴 값 : 성공시 0, 실패시 -1을 반환
setset_t라는 집합에 두번째 인자 시그널(signo)을 추가한다.
함수 원형
int sigaddset(sigset_t *set, int signo)
리턴 값 : 성공시 0, 실패시 -1을 반환
signal보다 향상된 기능을 제공하는 시그널 처리를 결정하는 함수이다. 시그널(signum)이 발생하면 새로 실행할 행동(act)과 갖고 있는 이전 행동(oldact)을 인자로 받는다.
함수 원형
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
리턴 값 : 성공시 0, 실패시 -1을 반환
시그널을 받을 프로세스의 id(pid)를 받아 해당하는 프로세스에 시그널(sig)을 보낸다.
함수 원형
int kill(pid_t pid, int sig)
리턴 값 : 성공시 0, 실패시 -1을 반환
현재 프로세스의 프로세스 id를 얻는다.
함수 원형
pid_t getpid(void);
리턴 값
시그널이 도착할 때까지 실행을 중단시킨다.
함수 원형
int pause(void);
리턴 값 : 항상 -1 리턴
인수로 주어진 seconds 만큼 작업을 중단시킨다.
함수 원형
unsigned int sleep(unsigned int seconds);
리턴 값 : 지정된 시간이 되면 0을 반환, 그러나 그 중에 시그널이 발생했다면 남은 시간을 반환한다.
주어진 ms만큼 작업을 중단한다. 시스템 활동이 예기치 않게 sleep 기간을 늘릴 수 있다.
함수 원형
int usleep(useconds_t microseconds);
리턴 값 : 성공 시 0, 실패 시 -1을 반환(errno를 설정)
status 인수 값은 0 - 255이거나 매크로 EXIT_SUCCESS 또는 EXIT_FAILURE 중 하나이다.
함수 원형
void exit(int status);
리턴 값 : status 값과 제어 둘 다 리턴한다.
과제의 목표는 클라이언트와 서버로 이루어진 통신 프로그램을 작성하는 것이다. 서버가 먼저 실행되어 자신의 PID를 출력하고, 클라이언트는 서버의 PID 값과 서버로 전송할 문자열을 인자로 받아 서버로 전송해야 한다. 이때, SIGUSR1
과 SIGUSR2
단 두 개의 시그널만 사용해야 한다.
유효한 PID 값인지, 인자 2개(서버의 PID, 문자열)이 들어오는 지
SIGUSR1
, 0이면 SIGUSR2
를 서버로 시그널을 보낸다.여기서 헤메었던 부분이 비트 연산이다. 사용해 본 적이 없어서 낯설었는데, 역시 피신 때부터 나에게 도움을 주었던 c언어 코딩도장... 비트연산도 있어서 공부하기 좋았다.
또 여기서 중요한 건 계속 클라이언트 쪽에서 시그널을 일방적으로 쏘면 그 시그널이 꼬일 수 있으므로 usleep()
으로 적당하게 텀을 주어야 한다.
signal()
로 시그널이 들어왔을 때, 8개의 시그널(비트)을 모아주는 함수를 구현한다.❓유니코드
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
플젝을 하느라 블랙홀의 압박에 시달리던 저에게 처음부터 끝까지 가르쳐 주신 dha, daekim 선생님 감사드립니다!!😇😇😇