← System Programming DUJINLABS.COM

Signal / Event / IPC · Linux userspace / kernel ABI

signal mask, pending, delivery

signal 생성, pending queue, thread 선택, mask, handler frame, sigreturn을 한 흐름으로 연결하고 handler 안전 규칙을 정리합니다.

Series
21 / 37
Build
cc -std=c17 -Wall -Wextra -O2 signal_wait.c -o signal_wait
Run
./signal_wait
Kernel
Linux 6.18.37 LTS

signal이 발생한 시점과 handler가 실행되는 시점은 왜 다른가?

signal은 생성되면 process-directed 또는 thread-directed pending 상태가 된다. 대상 thread가 그 signal을 block하지 않고 kernel에서 userspace로 돌아가는 지점에 도달해야 handler/default action이 실제로 적용된다.

handler는 원래 userspace stack에 sigframe을 만들고 instruction pointer를 handler로 바꾸는 방식으로 실행된다. return은 일반 함수 return만으로 끝나지 않고 rt_sigreturn trampoline이 register와 mask를 복원한다.

구조 그림

그림 1. process pending queue, thread mask, signal frame의 관계

Process signal state

  • shared disposition
  • process pending
  • SIGUSR1 siginfo

Thread A

  • mask: SIGUSR1 blocked
  • thread pending
  • delivery 불가

Thread B

  • mask: unblocked
  • get_signal 선택
  • user stack 사용

rt_sigframe

  • saved registers
  • old mask
  • ucontext · return trampoline

process-directed signal은 pending queue에 들어간 뒤 해당 signal을 block하지 않은 thread 하나에 전달될 수 있다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
signal generate kill/fault/timer
pending queue process 또는 thread
mask check deliverable 선택
sigframe stack에 context 저장
handler/sigreturn 실행 후 복원

생성, pending, delivery 세 시점을 분리한다. mask는 signal을 삭제하지 않고 delivery를 미루며 standard signal은 여러 번 발생해도 하나로 합쳐질 수 있다.

그림 3. 커널 내부에서 지나가는 주요 지점
send_signal sigpending enqueue
get_signal action 선택
arch setup_frame ucontext 복사
handler EL0 제한된 작업
rt_sigreturn register 복원

함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.

Linux 6.18.37 LTS 소스 위치

glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.

파일함수·구조체여기서 볼 것
kernel/signal.c __send_signal_locked(), get_signal() pending queue와 disposition 선택
arch/x86/kernel/signal.c arch_do_signal_or_restart(), setup_rt_frame() userspace signal frame 구성
arch/x86/entry/entry_64.S syscall/interrupt return userspace 복귀 전 signal 처리 연결

실행 예제 원본

아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.

빌드cc -std=c17 -Wall -Wextra -O2 signal_wait.c -o signal_wait
01#define _POSIX_C_SOURCE 200809L
02#include <signal.h>
03#include <stdio.h>
04#include <unistd.h>
05
06int main(void)
07{
08    sigset_t set;
09    sigemptyset(&set);
10    sigaddset(&set, SIGUSR1);
11    if (sigprocmask(SIG_BLOCK, &set, NULL) < 0)
12        return 1;
13
14    printf("pid=%ld; waiting for SIGUSR1\n", (long)getpid());
15    fflush(stdout);
16
17    siginfo_t info;
18    int signo = sigwaitinfo(&set, &info);
19    if (signo < 0)
20        return 1;
21    printf("received signal=%d sender=%ld value=%d\n", signo,
22           (long)info.si_pid, info.si_value.sival_int);
23    return 0;
24}

코드 조각별 설명

실제 코드 9행sigemptyset(&set)

sigset_t 내부 표현을 직접 0으로 가정하지 않고 API로 빈 집합을 만든다.

실제 코드 11행SIG_BLOCK

대상 signal을 먼저 block해 default action이나 handler delivery 대신 pending queue에 머물게 한다.

실제 코드 15행fflush(stdout)

stdout이 pipe로 redirect돼 line buffering이 아닐 때도 PID가 signal 전송자에게 즉시 보이도록 한다.

실제 코드 18행sigwaitinfo(&set

async handler가 아니라 동기 함수 반환으로 signal과 siginfo를 소비한다. 기다리는 signal은 호출 thread에서 block돼 있어야 한다.

실제 코드 22행info.si_value.sival_int

sigqueue로 보낸 realtime/queued value를 읽을 수 있다. kill로 보낸 standard signal이면 의미 있는 payload를 기대하지 않는다.

세부 동작

01

process-directed signal의 thread 선택

kill로 process에 보낸 signal은 해당 signal을 block하지 않은 thread 중 하나에 전달될 수 있다. 특정 thread에 보내려면 pthread_kill/tgkill 계열을 사용한다.

multithread 프로그램은 초기 thread에서 signal을 block한 뒤 전용 sigwait/signalfd thread가 소비하도록 구성하면 handler 경쟁을 줄일 수 있다.

02

handler 안에서 호출 가능한 함수는 제한된다

malloc, printf, pthread_mutex_lock은 내부 상태를 갱신하던 도중 handler가 끼면 재진입 deadlock이나 corruption을 만들 수 있다. async-signal-safe 목록 안의 함수만 사용한다.

보통 handler는 sig_atomic_t flag 또는 self-pipe write만 수행하고 실제 정리는 main loop로 넘긴다.

03

standard와 realtime signal의 queue 규칙

같은 standard signal은 block 중 여러 번 발생해도 pending bit 하나로 합쳐질 수 있다. realtime signal은 순서와 payload를 가진 queue로 쌓이지만 resource limit이 있다.

event count를 잃으면 안 되는 workload를 standard signal 횟수로 표현하지 않는다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
sighand_structthread group이 signal disposition을 공유하며 exec에서 caught action이 reset된다handler, flags, mask
sigpendingsignal 생성부터 delivery/소비까지 process 또는 task에 존재한다signal bitmap, queued siginfo
rt_sigframedelivery 때 user stack에 생기고 sigreturn에서 소비된다ucontext, old mask, registers

실패 조건과 오해하기 쉬운 부분

겉으로 보이는 현상실제 원인 후보확인 방법
signal 횟수 손실standard signal coalescingrealtime signal/eventfd 검토
handler deadlockasync-signal-unsafe 함수 재진입handler call graph와 core stack
원하는 thread가 안 받음process-directed delivery와 mask 구성/proc/PID/task/*/status SigBlk

직접 확인

  1. 실행 PID에 kill -USR1을 보내 sigwaitinfo가 sender PID를 반환하는지 확인한다.
  2. SIGUSR1을 block한 상태로 여러 번 보내 /proc/PID/status SigPnd와 한 번의 소비를 관찰한다.
  3. sigqueue로 integer payload를 보내는 sender를 작성하고 realtime signal에서 여러 값의 순서를 확인한다.
실행./signal_wait
추적strace -e trace=rt_sigprocmask,rt_sigtimedwait,kill ./signal_wait

원문