Linux v6.18.37 · 개념과 코드 읽기

시그널을 받는 쪽에서 보이는 신원을 맞춥니다

이 코드는 어떤 문제를 푸나요?

시그널은 번호 하나만 전달하는 일이 아닙니다. PID·UID를 담은 정보라면 필요할 때 받는 태스크의 namespace에 맞춰 신원을 변환합니다. 이 함수는 시그널 정보를 정리한 뒤 실제 큐 처리 함수로 넘깁니다. 여기서 사용자 핸들러를 바로 호출하는 것은 아닙니다.

읽을 범위: v6.18.37 · kernel/signal.c · send_signal_locked 1183–1217행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

namespace

PID와 UID 같은 번호가 어느 범위에서 해석되는지 정합니다.

siginfo

시그널 원인과 발신 정보 등을 담는 기록입니다. 특별한 포인터 값으로 정보의 종류를 나타내기도 합니다.

처음 읽을 때

코드를 읽을 때 SEND_SIG_NOINFO·SEND_SIG_PRIV·PID/UID를 담은 siginfo·그 밖의 정보를 나눕니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.

더 깊이 살펴볼 때

이 경로의 호출 문맥, 잠금·인터럽트 상태, 오류 시 남는 자원을 함께 추적해 보세요. 호출된 함수가 수행하는 작업과 현재 함수가 직접 보장하는 범위를 구분하는 것이 중요합니다.

그림으로 보는 변화

정보 형식별 네 경로 중 하나만 지나 공통 호출로 합류합니다

이 도식은 Linux v6.18.37 send_signal_locked()의 if / else if 순서입니다. force는 무조건적인 전달 보장이 아니라 후속 무시 판단의 입력입니다.

info의 종류를 순서대로 검사
① SEND_SIG_NOINFO대상 PID namespace에서 발신 PID가 안 보이면 force 설정. si_uid/si_pid 필드 변환 없음.
② SEND_SIG_PRIV커널 요청으로 force = true. 신원 필드 변환 없음.
③ has_si_pid_and_uid(info)user namespace가 다를 때만 si_uid 변환. 발신 PID가 안 보이면 si_pid = 0과 force = true; SI_KERNEL도 force에 반영.
④ 그 밖의 siginfo이 함수에서 UID/PID 변환 없이 force = false로 합류.
공통 → __send_signal_locked(sig, info, t, type, force) 호출. 무시되거나 이미 pending인 신호라면 새 큐 항목을 만들지 않을 수 있습니다.
입력별 신원 변환과 force · Linux v6.18.37
첫 일치 경로이 함수의 UID/PID 변경force 결과
SEND_SIG_NOINFO없음발신 PID가 대상 PID namespace에서 보이지 않을 때 참
SEND_SIG_PRIV없음참
PID·UID를 담은 siginfo다른 user namespace일 때 UID 변환; 불가시 발신 PID는 0SI_KERNEL 또는 불가시 발신 PID일 때 참
그 밖의 siginfo없음거짓
시그널을 받는 쪽에서 보이는 신원을 맞춥니다의 단계별 개념 그림
기존 3단계 PNG/GIF는 PID·UID를 담은 정보의 조건부 변환을 축약합니다. 다른 세 입력 경로는 2단계 신원 변환을 건너뛰며, 3단계는 공통 pending 처리 함수의 호출이지 새 큐 등록 보장이 아닙니다. 위 분기 도식과 함께 읽으세요.
1단계 설명

1단계 고정

GIF 원본 열기

1. 발신 정보 판별

정보 없음·커널 요청·PID/UID를 담은 정보·그 밖의 정보를 서로 배타적인 네 경로로 나눕니다.

분기는 정보 형식에 따른 선택입니다.

2. PID·UID 포함 정보만 조건부 변환

PID/UID를 담은 siginfo 경로에서만 user namespace 차이와 발신 PID 가시성에 따라 필드를 바꿉니다.

번호 변환은 대상 task_struct 교체가 아닙니다.

3. pending 처리 함수 호출

모든 경로가 __send_signal_locked()에 합류하지만 무시되거나 이미 pending이면 새 큐 항목은 생기지 않을 수 있습니다.

전달 요청과 실제 핸들러 실행 시점은 다릅니다.

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 읽기 구간이 있나요?

PID/UID를 담은 정보 경로에서 대상 자격 정보 포인터를 안전하게 읽기 위한 수명 보호가 필요하기 때문입니다.

출처와 읽은 범위

Linux stable v6.18.37 · kernel/signal.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑