Signal Concepts
-
Signal : are SW interrupts and provide a way to handle asynchronous events
(비동기식 프로그램을 수행하기 위한 하나의 수단..)
-
HW는 clock에 의해 동기적으로 수행, SW는 왜 비동기적인가?
OS에서 HW를 추상화시키기 때문에 우리가 program을 짤 때, clock의 개념이 사라진다.
-
Signal이 발생하면, 그 Signal은 해당 Process에게 전달되고,
그 process는 signal에 대한 action을 취한다.
ignore it : 하지만 몇몇 signal은 무시되지 못함
catch it : Signal handler를 호출하여 처리
accept the default
- Ignored
- Terminated
- Core dump :
A core dump file.swp(process의 regiser, memory값들을 작성) is generated,
and the process is terminated
- Stopped
- Resumed
Linux Signals
- Each signal is defined as a unique integer. (defined in signal.h)
- See also signal(2)/signal(7) for deatils of the signals

Core : Core dump
Stop : 일시 정지
Cont : 재개
Term : 종료
Ign : 무시
- 기본적으로 알아야 하는 SIGNAL
SIGALRM : 특정 시간이 지나면 알람
SIGCHILD : child process가 끝나면 parent process한테 가는 signal
SIGSEGV : 잘못된 memory 접근
SIGSTOP : process 일시 정지 (= Ctrl + Z)
SIGCONT : process가 중지되어 있을 경우, 재개
SIGTERM : process 종료 (= Ctrl + C)
- handler를 등록할 수 없는 signal :
SIGKILL, SIGSTOP

Dispatching Signals : Kill(2), raise(3)
kill(2) :
명시적으로 지정한 process에게 signal을 보낼 수 있다.
kill(pid, SIGTERM)
- pid > 0 : signal is sent to process with process ID pid
- pid = 0 : sender와 같은 gid를 갖는 process에게 모두 signal = broadcasting
raise(3) : sends a signal to itself

Example
SIGINT = Ctrl + C
SIGINT :

Ctrl + C :

SIGSTOP = Ctrl + Z
SIGSTOP :

Ctrl + Z :

SIGQUIT = Ctrl + \
-
SIGQUIT :

-
Ctrl + \ :

SIGKILL = 9
-
kill -SIGKILL <pid> :

-
kill -9 <pid> :

Signal delivery and handler execution

gdb
gdb : The GNU Debugger
gcc -g : debug용으로 compile하는 옵션

gdb ./(실행파일)

break = b : breakpoint 설정

r : 실행

n : 한줄씩 실행 / 그냥 enter : 이전 명령어(n) 실행

step = s : 해당 함수로 들어가라

list = l : 현재 어디에 있는지 확인하기

backtrace :
(goo함수에 들어왔으니)

(foo함수의 마지막 코드인 return; 뒤 '}'에서 backtrace)
Ctrl + d : debugger 종료

Setting up a Signal Handler : signal(2)
signal(2) :
signal은 UNIX가 개발되었을 때부터 사용되었던 오래된 system call임.
그런데 thread 같이 새로운 것들이 Linux에 점점 추가되면서
signal을 더 정교하게 사용해야할 필요가 있어졌다.
그래서 이제는 signal(2)보다 sigaction(2)을 더 많이 사용한다.

sigaction(2)
sigaction(2) :
signal(2)과 유사하게 해당 signal에 대한 signal handler를 설정하는데,
더욱 정밀하게 할 수 있다. (thread, Real time signal 등)

- int signum → void(*sa_handler)(int) :
signal handler에 전달되는 값 (SIGINT, SIGKILL 등등)
- const struct sigaction

- void (*sa_handler)(int):
signal handling을 해줄 function의 pointer
- sigset_t sa_mask:
signal handling되고 있는 중에, 또 다른 signal(sigset에 지정한 signal들)이 왔을 때 blocking할 수 있도록 막아줌.
예를 들어, SIGINT에 대한 signal handler를 수행하고 있는데 SIGTERM signal이 들어왔다.
만약 SIGINT에 대한 signal handling이 중단되기를 바라지 않으면 SIGTERM에 대해서 blocking할 수 있음.
그리고 SIGINT가 끝나고 나면, 그 다음 SIGTERM을 수행하도록.
signal set(sigset_t)
- signal을 하나씩 지정해주지 않고, set으로 지정해주고 싶을 때

example
sigprocmask(2), sigpending(2)
sigprocmask(2) : blocking할 signal들을 등록


int how: const sigset_t *set을 어떻게 등록할 것인가?
- SIG_BLOCK :
기존에 blocking 등록된 signal set들과
현재 *set로 blocking하려는 signal을 합산(union)하여 block
- SIG_UNBLOCK :
기존에 blocking 등록된 signal set들 중에서
현재 *set로 전달해주는 signal만 unblocking하겠다.
- SIG_SETMASK :
기존에 blocking 등록된 signal set들을 무시하고,
새로운 blocking signal set을 만들겠다.
sigset_t* oldset:
기존의 blocked된 signal set을 받아옴
sigpending(2) :
현재 pending되고 있는 signal이 무엇이 있는지 set에 반환.
example
alarm(2)
alarm(2) :
지정한 시간이 지나면, SIGALRM signal이 들어온다.
alarm(2)은 process에서 딱 1개만 설정할 수 있어서 제약점이 많다.
그래서 여러 개의 alarm설정, 정밀한 작업을 위해 POSIX interval timer도 만들어졌다.
example

sleep(3), nanosleep(2)
sleep(3) :
지정된 시간 동안 program 실행을 중지

nanosleep(2) :
만약 1초 100ms를 sleep하고 싶다면,
tv_sec에 1,
tv_nsec에 100,000,000 (ms = 10−3, ns = 10−9)
Summary
간단하게 정리하자면
sigaction(2)은 기존의 Signal에 대한 action을 다르게 지정할 수 있는 system call.
sigprocmask(2)는 특정 signal이 들어오는 것을 blocking할 수 있는 system call.
Examples
-SIGINT, Ctrl + C (interrupt)



-SIGSTOP, Ctrl + Z (stop)


-SIGCONT
-
아래처럼, SIGCONT로 STOP된 process를 실행시킬 수 있지만, terimnal로 입력하는 signal을 받지 못함.
(STOP된 프로세스는 user-space 코드가 완전히 중단되기 때문에 signal handler를 실행할 수 없다.
SIGINT/SIGQUIT 같은 터미널 신호는 handler를 실행해야 동작하는데, STOP 상태에서는 handler 실행 자체가 불가능하므로 신호를 받아도 아무 반응이 없다.
SIGCONT는 커널이 동작을 재개시키는 특별한 신호라서 STOP 상태에서도 즉시 동작한다.)

-
그래서 kill -SIGKILL 로 중단
-SIGQUIT, Ctrl + \ (core dump)


gdb (break, r, bt, n, list, print)
- gcc의
-g 옵션으로 디버깅 정보를 포함한 실행 파일을 만들 수 있음.

(r)un: 전체 실행
(b)reak: 중단 지점 설정하기
(n)ext: 다음 명령 실행
(l)ist: 현재 내가 어느 코드에 있는지 확인하기
(b)ack(t)race: 호출된 function stack 확인하기

gdb 실행파일
gdb 실행파일
- 함수 호출(또는 종료) 직전, 직후를 중단점으로 잡아서 여러 gdb 옵션을 테스트해보겠다.

run: 다음 breakpoint까지 명령어 쭉 실행.
아래에서는 첫 breakpoint가 main()이었기 때문에 main 함수 들어오고 break됨.
즉, foo() 함수 호출 직전.

(b)ack(t)race:
함수 호출 상태, 즉 스택을 확인.
아직 main() 함수만 존재.

(n)ext:
다음 명령어를 한 줄씩 실행
다음 명령어 한 줄을 실행 했더니, 마침 breakpoint였음
즉, foo() 함수 호출 직후임.

backtrace로 확인해본 결과, function call stack에 foo()가 push된 것을 확인할 수 있음.
n으로 쭉 실행하고, goo() 함수 호출 직후로 이동.

n으로 쭉 실행하고, goo() 함수 종료 직후로 이동.
backtrace 결과, stack에서 goo ()가 pop되었음을 확인할 수 있음.

- 이쯤에서,
list로 현재 내가 디버깅 중인 코드 주변 라인을 출력.
foo() 함수 언저리에 있다는 것을 알 수 있음

- 이제
n으로 쭉 실행하여 종료


Ctrl+D로 gdb에서 quit하기.

gdb 실행파일 [coredump]
- -SIGQUIT, -SIGSEGV 같은 signal들은 default action으로 core dump file을 생성한다.
gdb를 이용하여 core dump file을 확인해보자.
core dump file 생성 허가
- 우선 Linux는 저장공간을 절약하기 위해 default로 core dump file 생성을 막아놨다.
core dump file 생성을 허가하고, 크기를 제한하고 싶다면 다음 명령어를 사용한다.
ulimit -c unlimited

core dump file 생성
-
-SIGQUIT (Ctrl + \)을 통해 core dump file을 생성하겠다.

-
이제 core dump file이 생성되었는데, 어디에 있을까?
- WSL에서는 core dump file이
/mnt/wslg/dumps 에 만들어진다고 함.
- 내 system에서는
/var/lib/apport/coredump에 만들어졌다.

-
gdb 실행파일_path coredumpfile_path
아래 화면과 같이, debugging 결과 SIGQUIT signal로 인해 program이 종료되었다는 메시지를 확인할 수 있다.

sigaction(2) - SIGINT, SIGQUIT, SIGALRM에 대한 signal handling 처리
sigaction(2):
어떤 signal을 어떤 handler로 처리할지 지정
단, SIGSTOP과 SIGKILL에 대한 handler는 설정할 수 없음.


sigprocmask(2), sigpending(2), sigismember(3)
sigprocmask(2):
process가 어떤 signal을 block(mask)할지 설정
sigpending(2):
signal set 주소를 전달해주면, pending되어 있는 signal들에 대해 값을 처리해줌.
그리고 나서, sigismember(3)를 사용하여 pending된 signal을 확인할 수 있음.
sigismember(3):
sigset_t 안에 특정 signal이 포함되어 있는지 확인
SIGINT blocking & pending시키기

sigprocmask()로 race condition 해결하기
- data = zeros;와 data = ones; 코드를 무한 루프로 계속해서 수행하다가
SIGALRM이 들어오면 handler가 구조체의 값을 읽어서 출력하는 예제이다.
여기서, 아래와 같이 의도한 (0,0) (1,1)이 나오지 않고 (0, 1) 또는 (1,0)이 나오는 경우가 존재한다.
이 이유는, 구조체 대입은 atomic한 연산이 아니기 때문에 (0,0) → (1,1)이 바로 되는게 아니라
(0,0) → (0, 1) 또는 (1,0) → (1,1) 이러한 순서로 저장된다.
이때, (0,1)인 순간에 SIGALRM event 발생으로 인해 handler가 수행 될 때, 원하지 않는 (0,1)이 출력되는 것이다.
즉, signal handler 호출 중 global variable에 대해 race condition이 일어나서 구조체 값이 깨지는 것이다.

이를 해결하기 위해, sigprocmask를 이용하여 구조체 대입 연산을 atomic하게 만들어 비동기 signal을 blocking할 수 있다.
