이 코드는 어떤 문제를 푸나요?
시그널은 번호 하나만 전달하는 일이 아닙니다. 보낸 쪽의 PID와 UID가 받는 태스크의 namespace에서 어떤 뜻인지도 맞춰야 합니다. 이 함수는 시그널 정보를 정리한 뒤 실제 큐 처리 함수로 넘깁니다. 여기서 사용자 핸들러를 바로 호출하는 것은 아닙니다.
읽을 범위: v6.18.37 · kernel/signal.c · send_signal_locked 1183–1217행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
namespace
PID와 UID 같은 번호가 어느 범위에서 해석되는지 정합니다.
siginfo
시그널 원인과 발신 정보 등을 담는 기록입니다. 특별한 포인터 값으로 정보의 종류를 나타내기도 합니다.
처음 읽을 때
코드를 읽을 때 정보가 없는 경우·커널 요청·일반 siginfo를 구분합니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.
더 깊이 살펴볼 때
이 경로의 호출 문맥, 잠금·인터럽트 상태, 오류 시 남는 자원을 함께 추적해 보세요. 호출된 함수가 수행하는 작업과 현재 함수가 직접 보장하는 범위를 구분하는 것이 중요합니다.
그림으로 보는 변화

1. 발신 정보 판별
정보가 없는 경우·커널 요청·일반 siginfo를 구분합니다.
분기는 정보 형식에 따른 선택입니다.
2. 받는 쪽 기준으로 변환
대상의 user namespace와 PID 가시성을 고려합니다.
번호 변환은 대상 task_struct 교체가 아닙니다.
3. 대기 상태에 전달
정리한 정보를 __send_signal_locked에 넘깁니다.
전달 요청과 실제 핸들러 실행 시점은 다릅니다.
send_signal_locked를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
int send_signal_locked(int sig, struct kernel_siginfo *info,
struct task_struct *t, enum pid_type type)
{
/* Should SIGKILL or SIGSTOP be received by a pid namespace init? */
bool force = false;
if (info == SEND_SIG_NOINFO) {
/* Force if sent from an ancestor pid namespace */
force = !task_pid_nr_ns(current, task_active_pid_ns(t));
} else if (info == SEND_SIG_PRIV) {
/* Don't ignore kernel generated signals */
force = true;
} else if (has_si_pid_and_uid(info)) {
/* SIGKILL and SIGSTOP is special or has ids */
struct user_namespace *t_user_ns;
rcu_read_lock();
t_user_ns = task_cred_xxx(t, user_ns);
if (current_user_ns() != t_user_ns) {
kuid_t uid = make_kuid(current_user_ns(), info->si_uid);
info->si_uid = from_kuid_munged(t_user_ns, uid);
}
rcu_read_unlock();
/* A kernel generated signal? */
force = (info->si_code == SI_KERNEL);
/* From an ancestor pid namespace? */
if (!task_pid_nr_ns(current, task_active_pid_ns(t))) {
info->si_pid = 0;
force = true;
}
}
return __send_signal_locked(sig, info, t, type, force);
}int send_signal_locked(int sig, struct kernel_siginfo *info,시그널 번호 sig와 정보 info를 받아 대상 t의 namespace에서 발신 정보가 올바르게 해석되도록 정리한 뒤 pending 처리 경로에 넘깁니다. 이름의 locked는 호출자가 시그널 상태를 보호하는 잠금 규칙을 지켜야 한다는 의미입니다.
struct task_struct *t, enum pid_type type)t는 신호를 받을 태스크이고 type은 개별 태스크인지 스레드 그룹 대상인지 등의 전달 범위를 구분합니다. 이 두 값도 지역변수가 아니라 함수 인자입니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.
bool force = false;시그널을 강제로 처리해야 하는 특별한 조건이 아직 없다고 초기화합니다.
if (info == SEND_SIG_NOINFO) {일반 siginfo 객체 대신 정보 없음이라는 특별한 입력인지를 구분합니다.
force = !task_pid_nr_ns(current, task_active_pid_ns(t));발신 태스크가 대상 PID namespace에서 보이지 않는지 검사하여 강제 처리 조건을 정합니다.
} else if (info == SEND_SIG_PRIV) {커널이 지정하는 특별한 시그널 정보 유형으로 분기합니다.
force = true;이 경로의 커널 요청 또는 namespace 조건 때문에 일반 처리와 다른 force 표시를 설정합니다.
} else if (has_si_pid_and_uid(info)) {PID와 UID를 실제로 담는 siginfo 형식일 때만 신원 변환을 수행합니다.
struct user_namespace *t_user_ns;수신 태스크가 속한 user namespace를 담습니다. 발신 UID 숫자를 수신자에게 보이는 번호로 바꾸는 데 사용합니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.
rcu_read_lock();참조하는 RCU 보호 객체의 수명이 읽기 도중 끝나지 않도록 읽기 구간을 시작합니다.
t_user_ns = task_cred_xxx(t, user_ns);대상 태스크의 자격 정보에서 user namespace를 읽습니다. 주변 RCU 구간과 함께 봐야 합니다.
if (current_user_ns() != t_user_ns) {발신 쪽과 수신 쪽 UID 번호 체계가 다른 경우에만 매핑을 변환합니다.
kuid_t uid = make_kuid(current_user_ns(), info->si_uid);현재 user namespace의 UID 숫자를 커널 내부 UID 표현으로 바꿉니다.
info->si_uid = from_kuid_munged(t_user_ns, uid);내부 UID를 받는 쪽 user namespace에서 보이는 번호로 바꿉니다. 매핑이 없을 때의 대체 표현도 처리합니다.
rcu_read_unlock();RCU 보호 읽기 구간을 끝냅니다. 잠든 독자를 기다리는 일반 mutex unlock과는 다릅니다.
force = (info->si_code == SI_KERNEL);명시적인 커널 기원 시그널인지 원인 코드를 검사합니다.
if (!task_pid_nr_ns(current, task_active_pid_ns(t))) {발신자가 대상의 PID namespace에서 표현되지 않는 경우를 처리합니다.
info->si_pid = 0;대상 namespace에서 나타낼 수 없는 발신 PID를 0으로 표기합니다.
force = true;이 경로의 커널 요청 또는 namespace 조건 때문에 일반 처리와 다른 force 표시를 설정합니다.
return __send_signal_locked(sig, info, t, type, force);정리된 시그널 정보와 force 여부를 실제 pending·대상 선택 경로로 넘기고 그 결과를 돌려줍니다.
함께 생각해 볼 질문
시그널 전송 즉시 대상 함수가 실행되나요?
보통 대상의 pending 상태와 실행 문맥을 거쳐 적절한 복귀 경로에서 처리합니다.
UID 숫자를 그대로 복사하면 안 되나요?
서로 다른 user namespace에서는 같은 숫자가 다른 사용자를 뜻할 수 있습니다.
왜 RCU 읽기 구간이 있나요?
대상 자격 정보 포인터를 안전하게 읽기 위한 수명 보호가 필요하기 때문입니다.
출처와 읽은 범위
Linux stable v6.18.37 · kernel/signal.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
