#Unix Signals
유닉스/리눅스에서 시그널이란 무엇일까? 리눅스 터미널에서 프로그램을 실행시키다가 ctrl-c 를 눌러서 프로그램을 강제로 종료 시켜본 경험이 있다면 이미 시그널을 보내 보았다는 의미이다. 프로그램을 실행시키다가 ctrl-c를 누르면 인터럽트를 발생 시켜 운영체제가 프로그램에 제제를 건다. 이렇게 프로세스에서 특정 event가 발생 했을 때 "신호"를 보내는 것을 시그널이라고 한다.
- 유닉스 신호는 kernel이 사전 정의된 집합(< 32)에서 관심 있는 이벤트를 프로세스에 알릴 수 있는 메커니즘을 제시한다.
- 정수 번호로 표시되며, 일부 선택적 추가 정보와 연관되기도 한다.
- 여기서 이런 event들은 2가지로 분류된다
- Synchronous (동기적) event: 프로세스가 수행한 어떤 일에 의해 발생함 ('내부적으로 생성된 이벤트')
- Asynchronous (비동기적) event: 프로세스가 현재 수행하는 작업과 관련이 없음('외부적으로 생성된 이벤트')
- Signal API는 다음과 같은 signal에 대해 어떠한 조치를 취해야 하는지 선택 사항을 제공한다
- 프로세스 종료 (선택적으로 코어 덤프 /core dump 사용하여)
- 신호 무시
- 사용자 정의 핸들러(handler) 호출
- 프로세스 중지(작업 제어의 의미)
- 프로세스 계속 진행하기
- 합리적인 기본 작업을 통해 사용자 제어 및 장애 발생 시 장애 중지 동작 지원
#Signals Representing Synchronous Conditions
SIGILL (1): Illegal InstructionSIGABRT (1): 프로그램이 abort()를 call함SIGFPE (1): Floating Point Exception (예: 정수 0으로 나누기, 그러나 일반적으로 0.0에 의한 IEEE 754 나누기는 아님)
SIGSEGV (1): Segmentation Fault - catch all for memory and privilege violations
SIGPIPE (1): Broken Pipe - 닫힌 파이프에 write할려고 시도할시 발생
SIGTTIN (2): Terminal input: 백그라운드에서 terminal에서 read 시도
SIGTTOU (2) Terminal output: 백그라운드에서 terminal에서 write 시도
*(1) Default action: terminate the process / 프로세스 종료시킴
*(2) Default action: stop the process / 프로세스 멈춤
#Selected Signals Representing Asynchronous Notifications
SIGINT (1, 3): 인터럽트, 유저가 Crtl-c를 침
SIGQUIT (1, 3): 인터럽트, 유저가 Ctrl-\를 침
SIGTERM (3): 유저가 kill pid를 침 (default)
SIGKILL (2, 3): 유저가 kill -9 pid를 침 (urgent)
SIGALRM (1, 3): 알람 타이머가 다됨 (alarm(2))
SIGCHLD (1): Child process (하위 프로세스)가 종료되거나 중지됨
SIGTSTP (1): Terminal stop: 유저가 Ctrl-z를 침
SIGSTOP (2): 유저가 kill -STOP pid를 침
*(1) These are sent by the kernel, e.g., terminal device driver
*(2) SIGKILL and SIGSTOP cannot be caught or ignored
*(3) Default action: terminate the process
#How Signals Work
그렇다면 시그널들은 어떻게 작동할까?
- 첫째, 신호는 (커널을 통해) target 프로세스로 전송된다.
- 일부 신호는 커널에 의해 내부적으로 전송된다 (예: SIGALRM, SIGINT, SIGCHLD).
- 사용자 프로세스는 kill(2) 시스템 호출을 사용하여 서로 신호를 보낼 수 있다 (permission에 따라 다름).
- kill(1) 명령 또는 shell의 built-in kill command
- raise(3)는 현재 프로세스에 신호를 보냄.
- 위 동작을 수행하면 신호가 "pending"이 된다.
- 그런 다음 (잠시 후) 대상 프로세스가 신호를 수신하고 작업을 수행한다 (무시, 종료 또는 handler 호출).
* 추가 사항: 프로세스가 보류 중인 신호에 대해 학습하는 방법과 이에 반응하는 방법에 대한 세부 사항은 복잡하지만 커널에 의해 처리된다.
#Signal들은 queue에 들어가지 않는다
- 각 신호는 대상 프로세스의 보류 중인 mask에 있는 비트를 나타내며 신호가 전송되었는지 여부를 나타 낸다 (아직 수신되지 않음).
- 따라서 이미 보류 중인 신호를 보내도 아무런 효과가 없다.
- 이는 내부적으로 트리거된 신호에도 적용된다. 특히 SIGCHLD가 보류 중인 동안 종료되는 여러 개의 하위 프로세스가 SIGCHLD를 한 번 전달하게 된다.
- 개별 메시지보다는 철도 처럼(ON/OFF) 보내는 신호와 유사함
#Control Flow (asynchronous notification)
사용자 정의 신호 Handler가 설정된 경우, 임의의 시점에서 현재 프로그램을 중단할 수 있다. Handler를 실행하고 반환한 후에는 원래 프로그램이 계속된다.
Control Flow 예제:
void
list_insert (struct list_elem *before,
struct list_elem *elem)
{
elem->prev = before->prev;
elem->next = before;
before->prev->next = elem;
before->prev = elem;
}
list_insert:
movq (%rdi), %rax
movq %rdi, 8(%rsi)
movq %rax, (%rsi)
movq %rsi, 8(%rax)
movq %rsi, (%rdi)
ret
신호가 list_insert() 실행중 도착하면 조작된 목록의 목록 요소가 부분적으로 연결된 상태 (Linked state)에 있다. Signal_Handler가 동일한 목록에 액세스하는 경로(반복되는 경로 등)를 취하면 inconsistent behavior이 발생한다. 그렇기 때문에 이 상황은 피해야 한다.
#Async-Signal Safety
- 신호가 전달될 때 실행 중이던(그리고 중단되었던) 프로그램에 의해 동일한 데이터가 조작되는 동안 신호 핸들러의 데이터를 조작하는 것이 안전할까?
- 또한, 일반적으로 신호가 전달될 때 동일한 기능이 실행되는 동안 신호 핸들러에서 함수를 호출해도 안전한가?
- 정답은 상황마다 다르다 이다.
- POSIX는 안전한 함수, 이른바 비동기 신호 안전 함수들의 목록을 정의한다. (https://man7.org/linux/man-pages/man7/signal-safety.7.html)
- 예를 들어 위 linux man page 링크에 따르면, printf() 는 async-signal-safe (비동기 신호 안전)가 아니다 (acquires the console lock)
- 비동기 신호 안전 (async-signal-safe) 프로그램을 작성하는 두 가지 전략
- Singnal Handler에서 async-signal-unsafe 함수들을 call하지 않는다
- main control flow 에서 안전하지 않은 함수을 호출하는 동안 신호 차단(또는 공유 데이터 조작 시)
#Blocking/Masking Signals
프로그램은 신호를 차단하여 부적절한 시간 동안 전달되지 않도록 할 수 있다. 보류 (pending) 상태가 되는 차단된 신호는 차단 해제 시 전달된다.
#Trade-Off
메인 프로그램에서 대부분의 시간 동안 신호가 마스킹/차단되면 신호 처리기가 대부분의 기능을 호출할 수 있지만 신호 전달이 지연될 수 있다. 대부분의 경우 신호가 마스킹되지 않으면 신호 처리기를 매우 신중하게 구현해야 한다. 실제로, *coarse-grained 솔루션은 신호에 반응하는 최대 허용 지연 시간을 제한하는 요구사항이 없는 한 완벽하게 허용된다.
Side Note: OS는 (하드웨어) 인터럽트 핸들러를 구현할 때 동일한 트레이드오프를 마주한다.
*coarse-grained란 하나의 작업을 프로세스로 나누고 Single call을 사용해 작업결과를 생성하는 방식이다, 단순히 printf()라는 함수가 있으면, 그 함수를 호출하는 방식이다. 이러한 특성 때문에 distributed system상에서 많이 쓰이는 방식이다.
'Computer Science > Computer Systems' 카테고리의 다른 글
[P1] The Customizable Shell (0) | 2022.09.20 |
---|---|
[Lecture 4] Implementing Job Control Shells (0) | 2022.09.16 |
[Lecture 2] Unix File Descriptors and Pipes (0) | 2022.09.10 |
[Lecture 1-3] Processes Part III (0) | 2022.09.04 |
[Mini-Lecture] Character Sets and Unicode (0) | 2022.09.04 |