신호를 보냄과 handler가 실행됨은 다른 단계
신호 생성 경로는 번호를 pending 비트에 표시하고, 가능한 경우 siginfo를 큐에 보관한 뒤 대상 태스크가 처리할 수 있도록 알린다. 이 시점에 사용자 handler가 곧바로 호출되는 것은 아니다. get_signal()은 현재 태스크의 대기 신호를 선택하며, 무시·기본 동작·사용자 handler를 갈라놓는다. 아래 handler 경로는 arm64 native의 예다.
__send_signal_locked()가 필요하면 private/shared pending에 표시한다. 무시 또는 이미 pending인 경우는 앞에서 달라진다.signal.c L1050–1066 · L1135–1156get_signal()이 동기 신호를 먼저 시도하고, 이어 blocked mask를 사용해 pending을 꺼내 action을 본다.signal.c L2906–2942arch_do_signal_or_restart()가 handle_signal()로 넘겨 사용자 프레임을 준비한다.arm64 signal.c L1661–1682SIG_IGN·기본 ignore/stop/fatal, blocked 상태, ptrace 및 동기 신호 우선순위는 별도 분기다. 화살표는 하나의 조건부 설명이며 단일 즉시 호출 체인이 아니다.두 파일의 세 부분만 펼쳐 읽기
이 페이지가 표시하는 연속 발췌는 kernel/signal.c 두 구간(L1135–1156, L2906–2942)과 arch/arm64/kernel/signal.c 한 구간(L1661–1682)이다. 이름이 비슷하다고 생성 쪽의 __send_signal_locked(), 소비 쪽의 get_signal(), 아키텍처 복귀 함수가 같은 함수인 것처럼 합치지 않는다.
읽은 한계: 생성 함수 전체, 모든 신호 종류, ptrace·job control·namespace, 다른 아키텍처, 사용자 라이브러리 trampoline 전체를 검증한 글은 아니다. signal frame의 다중 아키텍처 비교는 시그널 프레임과 사용자 실행 상태 복원에서 따로 다룬다. 여기서는 v6.18.37의 표시 범위와 그 바로 연결되는 분기만 설명한다.
문C 도식 참고: Exception -9- (ARM64 Fault Handler)의 EL0 fault 실패·signal 발생 분기와 fault 경로 그림, Exception -8- (ARM64 Handler)의 사용자 복귀 전 pending work 검사를 별도로 읽었다. 그림 1은 생성과 사용자 복귀 검사를 다른 경계로 나누는 질문만 참고해 독립적으로 다시 그렸다. 두 글은 ARM64 커널 v5.4의 일부를 설명하므로 아래 v6.18.37 함수명·순서의 증거는 아니다. 원본 그림은 재사용하지 않았다.
같은 태그의 원본: kernel/signal.c · arch/arm64/kernel/signal.c. 두 파일 모두 원본 SPDX 표기가 GPL-2.0-only다.
실제 함수에 연결한 연속 코드 발췌
1. 생성 쪽의 pending 표시와 wake-up 통지
kernel/signal.c:1135–1156 · 함수 후반부만 표시한다. 앞의 prepare_signal()·pending 선택·중복 확인은 L1053–1064다.
out_set:
signalfd_notify(t, sig);
sigaddset(&pending->signal, sig);
/* Let multiprocess signals appear after on-going forks */
if (type > PIDTYPE_TGID) {
struct multiprocess_signals *delayed;
hlist_for_each_entry(delayed, &t->signal->multiprocess, node) {
sigset_t *signal = &delayed->signal;
/* Can't queue both a stop and a continue signal */
if (sig == SIGCONT)
sigdelsetmask(signal, SIG_KERNEL_STOP_MASK);
else if (sig_kernel_stop(sig))
sigdelset(signal, SIGCONT);
sigaddset(signal, sig);
}
}
complete_signal(sig, t, type);
ret:
trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
return ret;2. get_signal의 대기 신호 선택과 handler 분기
kernel/signal.c:2906–2942 · 동기 신호 우선 시도부터 사용자 handler를 택하는 지점까지이며 default stop/fatal의 후반 분기는 L2944–3045에 있다.
/*
* Signals generated by the execution of an instruction
* need to be delivered before any other pending signals
* so that the instruction pointer in the signal stack
* frame points to the faulting instruction.
*/
type = PIDTYPE_PID;
signr = dequeue_synchronous_signal(&ksig->info);
if (!signr)
signr = dequeue_signal(¤t->blocked, &ksig->info, &type);
if (!signr)
break; /* will return 0 */
if (unlikely(current->ptrace) && (signr != SIGKILL) &&
!(sighand->action[signr -1].sa.sa_flags & SA_IMMUTABLE)) {
signr = ptrace_signal(signr, &ksig->info, type);
if (!signr)
continue;
}
ka = &sighand->action[signr-1];
/* Trace actually delivered signals. */
trace_signal_deliver(signr, &ksig->info, ka);
if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
continue;
if (ka->sa.sa_handler != SIG_DFL) {
/* Run the handler. */
ksig->ka = *ka;
if (ka->sa.sa_flags & SA_ONESHOT)
ka->sa.sa_handler = SIG_DFL;
break; /* will return non-zero "signr" value */
}3. arm64 사용자 복귀 시 handler 연결
arch/arm64/kernel/signal.c:1661–1682 · arm64 함수의 중간 부분으로, 앞의 syscall restart 처리와 뒤의 무-handler 경로는 표시하지 않는다.
/*
* Get the signal to deliver. When running under ptrace, at this point
* the debugger may change all of our registers.
*/
if (get_signal(&ksig)) {
/*
* Depending on the signal settings, we may need to revert the
* decision to restart the system call, but skip this if a
* debugger has chosen to restart at a different PC.
*/
if (regs->pc == restart_addr &&
(retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK ||
(retval == -ERESTARTSYS &&
!(ksig.ka.sa.sa_flags & SA_RESTART)))) {
syscall_set_return_value(current, regs, -EINTR, 0);
regs->pc = continue_addr;
}
handle_signal(&ksig, regs);
return;
}private/shared pending과 action을 분리하기
생성 쪽 __send_signal_locked()는 전달 대상 유형에 따라 task private pending 또는 thread-group shared_pending을 고른다(L1053–1064). 소비 쪽 dequeue_signal()은 private를 먼저 시도한 뒤 shared를 본다(L618–641). 그러나 get_signal()은 명령 실행으로 생긴 동기 신호를 먼저 시도하므로, “항상 private 큐의 첫 항목 → shared 큐”라는 전체 우선순위 공식은 아니다(L2906–2918).
pending 또는 thread-group shared_pending. blocked mask는 선택을 제한한다.dequeue_signal · send 선택SIG_IGN, 사용자 handler, SIG_DFL 중 무엇인지에 따라 다음 동작이 달라진다.get_signal L2927–2948| 선택 시 조건 | 이 구간의 동작 | handler 프레임? | 원문 |
|---|---|---|---|
SIG_IGN | 다음 pending 신호를 살핀다 | 없음 | L2927–2934 |
| 사용자 handler | ksig에 action을 복사해 성공으로 반환 | arm64 복귀 경로에서 준비 | L2934–2942 · arm64 L1661–1682 |
SIG_DFL | 기본 ignore·stop·fatal 등을 다시 분기 | 이 분기에 없음 | L2944–3036 |
| blocked | 일반 pending 선택에 blocked mask 적용 | 이때 선택되지 않음 | L2906–2918 |
arm64 handler 프레임과 sigreturn은 별도의 경계
get_signal()이 사용자 handler를 반환한 뒤에만 이 arm64 경로는 handle_signal()을 호출한다(L1661–1682). native 경로의 setup_rt_frame()은 사용자 스택 프레임·저장 문맥을 쓰고, 성공한 경우 handler 진입 레지스터를 설정한다(L1537–1575, L1588–1615). 호환 태스크는 다른 frame setup 분기를 쓴다.
setup_rt_frame()에서 사용자 프레임에 이전 문맥을 저장한다.L1537–1575rt_sigreturn을 “커널 스택 문맥 복원”이라고 부르지 않는다. 오류가 있으면 badframe 경로가 있다.| 단계 | 확인 가능한 조건 | 아직 단정할 수 없는 것 | 원문 |
|---|---|---|---|
| pending 표시 | sigaddset() 뒤 complete_signal() | handler 실행 여부 | L1135–1156 |
get_signal() | 신호를 꺼내고 action을 분기 | 프레임 생성 성공 | L2906–2942 |
| arm64 frame setup | 프레임 쓰기와 handler 진입 설정 | handler 완료·정상 복원 | L1537–1615 |
rt_sigreturn | 프레임 접근·복원 검사 | 사전에 저장한 내용의 무조건 신뢰 | L1126–1163 |
tracepoint 이름보다 관찰 경계를 먼저 정하기
trace_signal_generate()는 생성 함수의 반환 위치에 있고, 결과 코드에는 ignored·already pending·pending 설정·정보 손실·overflow failure 등이 있다(L1052–1156). 따라서 이 이벤트 한 건을 사용자 handler 실행 한 건으로 환산하지 않는다. trace_signal_deliver()도 get_signal()의 action 검사 직전에 있으므로 프레임 작성 완료를 뜻하지 않는다(L2927–2942).
/proc/<pid>/status의 SigPnd, ShdPnd, SigBlk, SigIgn, SigCgt는 현재 비트마스크를 비교할 때 유용하지만 과거 선택 순서나 사용자 handler 완료 시각을 기록하지 않는다. 보드에서 동작을 확인할 때는 커널 버전, 태스크와 thread group, blocked mask, 생성·선택·프레임 경계를 따로 기록한다.
이어서 읽을 위치
생성 전체의 예외와 큐 할당은 __send_signal_locked(), stop·fatal·ptrace의 주요 분기는 get_signal() 전체, arm64 frame의 저장·복원 세부는 restore·rt_sigreturn 및 setup·handle에서 더 확인한다. 시그널 프레임과 사용자 실행 상태 복원은 arm64·x86-64·RISC-V 프레임을 비교하는 별도 글이다.