리눅스 커널 spinlock·mutex 오용 디버깅 — DEBUG_SPINLOCK, DEBUG_MUTEXES, lockdep 실측 비교

스핀락(spinlock)과 뮤텍스(mutex)를 잘못 쓴 커널 코드는 대부분 조용히 지나간다. 초기화하지 않은 스핀락은 0으로 채워진 메모리라 x86에서는 그냥 동작하고, 스핀락 안에서 호출한 kmalloc(GFP_KERNEL)은 메모리가 넉넉하면 잠들지 않으니 아무 일도 없다. 그러다 메모리 압박이 오거나 다른 태스크와 경합이 붙는 날, 원인과 동떨어진 곳에서 행(hang)이나 soft lockup으로 터진다.

이 글에서는 락 오용 10가지를 커널 모듈로 만들어, 락 디버깅 옵션을 끈 커널·DEBUG_SPINLOCK/DEBUG_MUTEXES/DEBUG_ATOMIC_SLEEP만 켠 커널·PROVE_LOCKING(lockdep)까지 켠 커널에서 각각 어떤 메시지가 나오는지 비교한다. lockdep의 락 순서(ABBA) 검증은 LOCKDEP 사용법, watchdog 설정은 Kernel Lockup Debug 사용법에서 따로 다뤘다.

실험 환경과 커널 옵션

옵션잡는 것비고
DEBUG_SPINLOCK초기화 누락(bad magic), 재귀 잠금, 잡지 않은 락 해제, 다른 태스크·CPU의 해제spinlock_t에 magic·owner 필드 추가
DEBUG_MUTEXES다른 태스크의 mutex_unlock, 잘못된 대기 상태PREEMPT_RT에서는 사용 불가
DEBUG_ATOMIC_SLEEP스핀락·RCU·IRQ 등 atomic 문맥에서 잠들 수 있는 함수 호출might_sleep()이 있는 함수만 검사
PROVE_LOCKING위 전부 + 재귀·순서·IRQ 문맥 불일치, 락 잡은 채 복귀LOCKDEP, DEBUG_SPINLOCK, DEBUG_MUTEXES를 select
DETECT_HUNG_TASK, SOFTLOCKUP_DETECTOR실제로 멈춘 뒤의 증상세 커널 모두 켬

커널 6.12.0 소스에서 같은 x86_64 설정을 바탕으로 세 가지를 빌드했고, busybox initramfs와 함께 QEMU(TCG)로 부팅했다.

SRC=~/kbuild/linux-6.12
BASE=~/kbuild/config.gcc          # x86_64 기본 설정

build() {   # build <이름> <추가로 켤 옵션...>
  v=$1; shift
  mkdir -p build-$v && cp $BASE build-$v/.config
  $SRC/scripts/config --file build-$v/.config \
    --enable DETECT_HUNG_TASK --enable SOFTLOCKUP_DETECTOR --enable DEBUG_FS \
    $(printf -- '--enable %s ' "$@")
  make -C $SRC O=$PWD/build-$v olddefconfig
  make -C $SRC O=$PWD/build-$v -j6 bzImage modules
}

build off
build lite DEBUG_SPINLOCK DEBUG_MUTEXES DEBUG_ATOMIC_SLEEP
build on   DEBUG_SPINLOCK DEBUG_MUTEXES DEBUG_ATOMIC_SLEEP PROVE_LOCKING
$ grep -E "^CONFIG_(DEBUG_SPINLOCK|DEBUG_MUTEXES|DEBUG_ATOMIC_SLEEP|PROVE_LOCKING|LOCKDEP|DETECT_HUNG_TASK|SOFTLOCKUP_DETECTOR)=" build-{off,lite,on}/.config
build-off/.config:CONFIG_SOFTLOCKUP_DETECTOR=y
build-off/.config:CONFIG_DETECT_HUNG_TASK=y
build-lite/.config:CONFIG_SOFTLOCKUP_DETECTOR=y
build-lite/.config:CONFIG_DETECT_HUNG_TASK=y
build-lite/.config:CONFIG_DEBUG_SPINLOCK=y
build-lite/.config:CONFIG_DEBUG_MUTEXES=y
build-lite/.config:CONFIG_DEBUG_ATOMIC_SLEEP=y
build-on/.config:CONFIG_SOFTLOCKUP_DETECTOR=y
build-on/.config:CONFIG_DETECT_HUNG_TASK=y
build-on/.config:CONFIG_PROVE_LOCKING=y
build-on/.config:CONFIG_DEBUG_SPINLOCK=y
build-on/.config:CONFIG_DEBUG_MUTEXES=y
build-on/.config:CONFIG_LOCKDEP=y
build-on/.config:CONFIG_DEBUG_ATOMIC_SLEEP=y

$ ls -l build-{off,lite,on}/arch/x86/boot/bzImage | awk "{print \$5, \$9}"
13550592 build-lite/arch/x86/boot/bzImage
13468672 build-off/arch/x86/boot/bzImage
14492672 build-on/arch/x86/boot/bzImage

lockdep을 켠 커널은 이미지만 약 1MB 커지고, 락 획득마다 의존성 그래프를 갱신하므로 성능 측정용 커널과는 분리해서 쓴다.

테스트 모듈

시나리오 이름을 모듈 파라미터로 받아 해당 함수 하나만 실행한다.

// SPDX-License-Identifier: GPL-2.0
/* 락 오용 시나리오 모음 — insmod lockbug.ko test=<이름> */
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/kthread.h>
#include <linux/completion.h>

static char *test = "none";
module_param(test, charp, 0444);

static DEFINE_SPINLOCK(slock);
static DEFINE_MUTEX(mlock);

/* 1. 같은 스핀락을 두 번 잡기 */
static void spin_recursion(void)
{
	spin_lock(&slock);
	spin_lock(&slock);
}

/* 2. 잡지 않은 스핀락 해제 */
static void spin_double_unlock(void)
{
	spin_lock(&slock);
	spin_unlock(&slock);
	spin_unlock(&slock);
}

/* 3. 스핀락을 잡은 채 sleep */
static void spin_sleep(void)
{
	spin_lock(&slock);
	msleep(10);
	spin_unlock(&slock);
}

/* 3-2. 스핀락을 잡은 채 GFP_KERNEL 할당 (메모리가 넉넉하면 실제로 잠들지 않는다) */
static void spin_gfp_kernel(void)
{
	void *p;

	spin_lock(&slock);
	p = kmalloc(64, GFP_KERNEL);
	spin_unlock(&slock);
	kfree(p);
}

/* 4. 초기화하지 않은 스핀락 사용 */
struct dev_ctx {
	spinlock_t lock;
	int count;
};

static void spin_uninit(void)
{
	struct dev_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);

	if (!ctx)
		return;
	/* spin_lock_init(&ctx->lock); 누락 */
	spin_lock(&ctx->lock);
	ctx->count++;
	spin_unlock(&ctx->lock);
	kfree(ctx);
}

/* 5. 하드IRQ와 프로세스 컨텍스트에서 irqsave 없이 같은 락 사용 */
static struct hrtimer timer;

static enum hrtimer_restart timer_fn(struct hrtimer *t)
{
	spin_lock(&slock);
	spin_unlock(&slock);
	return HRTIMER_NORESTART;
}

static void spin_irq_unsafe(void)
{
	spin_lock(&slock);		/* 인터럽트가 켜진 상태 */
	spin_unlock(&slock);

	hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	timer.function = timer_fn;
	hrtimer_start(&timer, ms_to_ktime(10), HRTIMER_MODE_REL);
	msleep(100);
	hrtimer_cancel(&timer);
}

/* 6. 다른 태스크가 잡은 뮤텍스 해제 */
static DECLARE_COMPLETION(locked);

static int locker_fn(void *data)
{
	mutex_lock(&mlock);
	complete(&locked);
	return 0;			/* 잠근 채로 종료 */
}

static void mutex_other_owner(void)
{
	kthread_run(locker_fn, NULL, "locker");
	wait_for_completion(&locked);
	mutex_unlock(&mlock);
}

/* 7. 스핀락 안에서 mutex_lock (경합이 없으면 실제로 잠들지 않는다) */
static void mutex_in_atomic(void)
{
	spin_lock(&slock);
	mutex_lock(&mlock);
	mutex_unlock(&mlock);
	spin_unlock(&slock);
}

/* 8. 같은 뮤텍스를 두 번 잡기 */
static void mutex_recursion(void)
{
	mutex_lock(&mlock);
	mutex_lock(&mlock);
}

/* 9. 뮤텍스를 잡은 채 사용자 공간으로 복귀 */
static void mutex_return_held(void)
{
	mutex_lock(&mlock);
}

/* 10. 버그 두 개를 연달아 — 두 번째도 보고될까 */
static void two_bugs(void)
{
	spin_uninit();
	spin_gfp_kernel();
	mutex_other_owner();
}

static const struct {
	const char *name;
	void (*fn)(void);
} tests[] = {
	{ "spin_recursion",     spin_recursion },
	{ "spin_double_unlock", spin_double_unlock },
	{ "spin_sleep",         spin_sleep },
	{ "spin_gfp_kernel",    spin_gfp_kernel },
	{ "spin_uninit",        spin_uninit },
	{ "spin_irq_unsafe",    spin_irq_unsafe },
	{ "mutex_other_owner",  mutex_other_owner },
	{ "mutex_in_atomic",    mutex_in_atomic },
	{ "mutex_recursion",    mutex_recursion },
	{ "mutex_return_held",  mutex_return_held },
	{ "two_bugs",           two_bugs },
};

static int __init lockbug_init(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(tests); i++) {
		if (!strcmp(test, tests[i].name)) {
			pr_info("lockbug: run %s\n", test);
			tests[i].fn();
			pr_info("lockbug: %s returned\n", test);
			return 0;
		}
	}
	return -EINVAL;
}
module_init(lockbug_init);
MODULE_LICENSE("GPL");
#!/bin/busybox sh
/bin/busybox --install -s /bin
mount -t devtmpfs none /dev
mount -t proc none /proc
mount -t sysfs none /sys
T=$(sed -n 's/.*locktest=\([a-z_]*\).*/\1/p' /proc/cmdline)
echo 15 > /proc/sys/kernel/hung_task_timeout_secs
echo "===MARK start $T"
insmod /lockbug.ko test=$T &
sleep ${WAIT:-40}
echo "===MARK end $T"
poweroff -f
qemu-system-x86_64 -machine q35 -cpu max -smp 2 -m 1024 -nographic -no-reboot \
  -kernel build-$v/arch/x86/boot/bzImage -initrd ir-$v.cpio.gz \
  -append "console=ttyS0 no_timer_check tsc=reliable loglevel=7 locktest=$t WAIT=$w"

KVM 없이 TCG로 돌리면 lockdep 커널이 tsc-early 클럭소스를 불안정으로 판정한 뒤 hpet로 넘어가면서 부팅 도중 RCU stall로 멈췄다. tsc=reliable을 주자 세 커널 모두 2초 안에 부팅됐다.

결과 한눈에 보기

시나리오offlite (DEBUG_*)on (+lockdep)
spin_recursion26초 뒤 soft lockup만spinlock recursion 즉시 + soft lockuppossible recursive locking 즉시 + soft lockup
spin_double_unlockpreemption imbalance 경고만spinlock already unlockedbad unlock balance
spin_sleep (msleep)scheduling while atomicscheduling while atomicscheduling while atomic + 잡고 있는 락 표시
spin_gfp_kernel아무 메시지 없음sleeping function called from invalid context같음 + 잡고 있는 락 표시
spin_uninit아무 메시지 없음spinlock bad magictrying to register non-static key
spin_irq_unsafe아무 메시지 없음아무 메시지 없음inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W}
mutex_other_owner아무 메시지 없음DEBUG_LOCKS_WARN_ON(__owner_task(owner) != get_current())bad unlock balance
mutex_in_atomic아무 메시지 없음sleeping function called from invalid context같음 + Invalid wait context
mutex_recursion29초 뒤 hung task만29초 뒤 hung task만possible recursive locking 즉시 + hung task
mutex_return_held아무 메시지 없음아무 메시지 없음lock held when returning to user space

디버그 옵션이 없는 커널에서 10개 중 6개는 흔적 없이 지나갔고, 나머지도 한참 뒤 증상만 남았다. 가벼운 DEBUG_* 옵션만으로 스핀락 계열은 대부분 잡히지만, IRQ 문맥 불일치와 락을 잡은 채 복귀하는 경우는 lockdep만 잡는다.

스핀락 오용

같은 락을 두 번 잡기

[    2.111319] lockbug: run spin_recursion
[   28.316653] watchdog: BUG: soft lockup - CPU#1 stuck for 26s! [insmod:76]
[   28.318040] RIP: 0010:queued_spin_lock_slowpath+0x7f/0x290
[    2.420590] lockbug: run spin_recursion
[    2.420910] BUG: spinlock recursion on CPU#1, insmod/76
[    2.425933]  lock: slock+0x0/0xffffffffffffef00 [lockbug], .magic: dead4ead, .owner: insmod/76, .owner_cpu: 1
[    2.432804] CPU: 1 UID: 0 PID: 76 Comm: insmod Tainted: G           O       6.12.0 #1
[    2.433724] Call Trace:
[    2.434946]  dump_stack_lvl+0x53/0x70
[    2.558995]  do_raw_spin_lock+0x80/0xb0

off 커널은 26초가 지나서야 watchdog이 CPU가 멈췄다고 알려줄 뿐 어떤 락인지 모른다. DEBUG_SPINLOCK은 잠그기 직전에 owner == current를 검사해 락 이름과 소유자를 바로 찍는다.

static inline void
debug_spin_lock_before(raw_spinlock_t *lock)
{
	SPIN_BUG_ON(READ_ONCE(lock->magic) != SPINLOCK_MAGIC, lock, "bad magic");
	SPIN_BUG_ON(READ_ONCE(lock->owner) == current, lock, "recursion");
	SPIN_BUG_ON(READ_ONCE(lock->owner_cpu) == raw_smp_processor_id(),
							lock, "cpu recursion");
}

잡지 않은 락 해제

[    2.095976] lockbug: run spin_double_unlock
[    2.096126] lockbug: spin_double_unlock returned
[    2.096358] initcall lockbug_init+0x0/0xff0 [lockbug] returned with preemption imbalance 
[    2.097683] WARNING: CPU: 0 PID: 76 at init/main.c:1282 do_one_initcall+0x1c7/0x220
[    1.467538] lockbug: run spin_double_unlock
[    1.467739] BUG: spinlock already unlocked on CPU#0, insmod/76
[    1.467845]  lock: slock+0x0/0xffffffffffffef00 [lockbug], .magic: dead4ead, .owner: <none>/-1, .owner_cpu: -1

off 커널의 경고는 spin_unlock()이 선점 카운트를 한 번 더 내린 결과를 initcall이 끝난 뒤에 발견한 것이라 원인 위치가 없다. lite 커널은 .owner: <none>/-1로 이미 풀린 락이라는 것을 해제 시점에 보여준다.

초기화하지 않은 스핀락

[    1.558570] lockbug: run spin_uninit
[    1.558775] BUG: spinlock bad magic on CPU#1, insmod/76
[    1.558894]  lock: 0xff1fcdec81874b80, .magic: 00000000, .owner: <none>/-1, .owner_cpu: 0
[    1.559960] CPU: 1 UID: 0 PID: 76 Comm: insmod Tainted: G           O       6.12.0 #1
[    1.560602] Call Trace:
[    1.561213]  dump_stack_lvl+0x53/0x70
[    1.561361]  do_raw_spin_lock+0x63/0xb0
[    1.567270] lockbug: spin_uninit returned
[    2.829928] lockbug: run spin_uninit
[    2.830186] INFO: trying to register non-static key.
[    2.830267] The code is fine but needs lockdep annotation, or maybe
[    2.830429] turning off the locking correctness validator.

kzalloc으로 0이 채워진 spinlock_t는 qspinlock의 unlocked 값과 같아서 off 커널에서는 문제없이 동작했다. .magic: 00000000spin_lock_init() 누락의 직접 증거다.

스핀락을 잡은 채 잠들 수 있는 함수 호출

[    1.979703] lockbug: run spin_sleep
[    1.979819] BUG: scheduling while atomic: insmod/76/0x00000002
[    1.981964]  __schedule_bug+0x4d/0x60
[    1.982055]  __schedule+0x816/0x910
[    1.982119]  schedule+0x22/0xd0
[    1.982169]  schedule_timeout+0x99/0x170
[    1.991137] lockbug: spin_sleep returned
[    2.195276] lockbug: run spin_gfp_kernel
[    2.195657] BUG: sleeping function called from invalid context at include/linux/sched/mm.h:321
[    2.195861] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 76, name: insmod
[    2.196010] preempt_count: 1, expected: 0

msleep()처럼 실제로 스케줄러에 들어가면 옵션과 무관하게 scheduling while atomic이 나온다. 반면 kmalloc(GFP_KERNEL)은 메모리가 충분하면 잠들지 않으므로 off 커널에서는 조용했고, DEBUG_ATOMIC_SLEEPmight_sleep() 지점에서 "잠들 수도 있었다"를 미리 잡았다.

IRQ 문맥과 프로세스 문맥에서 같은 락

[    2.813518] lockbug: run spin_irq_unsafe
[    2.824783] 
[    2.824836] ================================
[    2.824899] WARNING: inconsistent lock state
[    2.825103] 6.12.0 #1 Tainted: G           O      
[    2.825182] --------------------------------
[    2.825323] inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage.
[    2.825420] swapper/0/0 [HC1[1]:SC0[0]:HE0:SE1] takes:
[    2.825517] ffffffffc0215198 (slock){?.+.}-{2:2}, at: timer_fn+0x10/0x30 [lockbug]
[    2.826037] {HARDIRQ-ON-W} state was registered at:
[    2.826142]   lock_acquire+0xc0/0x2d0
[    2.826398]   _raw_spin_lock+0x2b/0x40
[    2.826478]   spin_irq_unsafe+0x10/0x70 [lockbug]
[    2.828294] 
[    2.828294] other info that might help us debug this:
[    2.828437]  Possible unsafe locking scenario:
[    2.828437] 
[    2.828533]        CPU0
[    2.828580]        ----
[    2.828633]   lock(slock);
[    2.828691]   <Interrupt>
[    2.828808]     lock(slock);
[    2.828868] 
[    2.828868]  *** DEADLOCK ***
[    2.828868] 
[    2.828957] no locks held by swapper/0/0.
[    2.829024] 

인터럽트를 켠 채 spin_lock으로 잡은 락을 hrtimer 콜백(하드IRQ)에서도 잡았다. 락을 쥔 순간 같은 CPU에 인터럽트가 들어와야 실제로 멈추지만, lockdep은 두 사용 이력만으로 경고하며 프로세스 문맥 쪽을 spin_lock_irqsave()로 바꾸면 사라진다.

뮤텍스 오용

다른 태스크가 잡은 뮤텍스 해제

[    1.963444] lockbug: run mutex_other_owner
[    1.966829] DEBUG_LOCKS_WARN_ON(__owner_task(owner) != get_current())
[    1.969408] WARNING: CPU: 0 PID: 77 at kernel/locking/mutex.c:923 __mutex_unlock_slowpath.isra.0+0x176/0x290
[    2.500119] lockbug: run mutex_other_owner
[    2.505280] WARNING: bad unlock balance detected!
[    2.505653] insmod/76 is trying to release lock (mlock) at:
[    2.506835] but there are no more locks to release!
[    2.507836] no locks held by insmod/76.

off 커널에서는 아무 경고 없이 락이 풀려, 커널 스레드가 들고 있다고 믿는 락을 다른 태스크가 해제한 상태가 된다. DEBUG_MUTEXES__mutex_unlock_slowpath()MUTEX_WARN_ON(__owner_task(owner) != current)로 잡는다.

스핀락 안에서 mutex_lock

[    1.878787] lockbug: run mutex_in_atomic
[    1.878933] BUG: sleeping function called from invalid context at kernel/locking/mutex.c:283
[    1.879099] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 76, name: insmod
[    1.879476] preempt_count: 1, expected: 0
[    1.885501] lockbug: mutex_in_atomic returned
[    2.259025] [ BUG: Invalid wait context ]
[    2.259324] insmod/76 is trying to lock:
[    2.259382] ffffffffc0117148 (mlock){....}-{3:3}, at: mutex_in_atomic+0x1e/0x40 [lockbug]
[    2.259625] context-{4:4}
[    2.259666] 1 lock held by insmod/76:
[    2.259718]  #0: ffffffffc0117198 (slock){+.+.}-{2:2}, at: mutex_in_atomic+0x10/0x40 [lockbug]

경합이 없으면 mutex_lock()은 fast path로 바로 끝나 off 커널에서는 드러나지 않는다. lockdep은 {2:2}(스핀락) 안에서 {3:3}(잠드는 락)을 잡으려 한다는 wait-type 규칙 위반으로 한 번 더 경고한다.

같은 뮤텍스를 두 번 잡기

[    1.871995] lockbug: run mutex_recursion
[   31.193898] INFO: task insmod:77 blocked for more than 15 seconds.
[   31.194949]       Tainted: G           O       6.12.0 #1
[   31.195288] task:insmod          state:D stack:14168 pid:77    tgid:77    ppid:1      flags:0x00004002
[   31.195945] Call Trace:
[   31.196493]  __schedule+0x3f3/0x910
[   31.197089]  schedule+0x22/0xd0
[   31.197222]  schedule_preempt_disabled+0x10/0x20
[   31.197363]  __mutex_lock.constprop.0+0x3bf/0x6a0
[   31.197628]  lockbug_init+0x69/0xff0 [lockbug]
[    2.253411] lockbug: run mutex_recursion
[    2.254041] WARNING: possible recursive locking detected
[    2.254408] insmod/76 is trying to acquire lock:
[    2.254493] ffffffffc014e148 (mlock){+.+.}-{3:3}, at: lockbug_init+0x69/0xff0 [lockbug]
[    2.255196] but task is already holding lock:
[    2.255298] ffffffffc014e148 (mlock){+.+.}-{3:3}, at: mutex_recursion+0x12/0x20 [lockbug]
[    2.255922]   lock(mlock);
[    2.255981]   lock(mlock);
[    2.256037]  *** DEADLOCK ***
[    2.256288]  #0: ffffffffc014e148 (mlock){+.+.}-{3:3}, at: mutex_recursion+0x12/0x20 [lockbug]
[   30.864651] INFO: task insmod:76 blocked for more than 15 seconds.
[   30.870415] INFO: lockdep is turned off.

off·lite 커널에서는 hung_task_timeout_secs를 15초로 줄였는데도 두 번째 검사 주기에서야 D 상태 태스크로 보고됐고, 백트레이스에는 __mutex_lock만 있을 뿐 누가 락을 쥐고 있는지는 없다. lockdep 커널은 잠그는 순간 경고했지만, 그 첫 보고로 lockdep이 꺼져서 hung task 시점의 INFO: lockdep is turned off.처럼 보유 락 목록을 더 이상 보여주지 못했다.

뮤텍스를 잡은 채 사용자 공간으로 복귀

[    2.923954] lockbug: run mutex_return_held
[    2.924179] lockbug: mutex_return_held returned
[    2.925880] 
[    2.925941] ================================================
[    2.926023] WARNING: lock held when returning to user space!
[    2.926247] 6.12.0 #1 Tainted: G           O      
[    2.926321] ------------------------------------------------
[    2.926412] insmod/76 is leaving the kernel with locks still held!
[    2.926755] 1 lock held by insmod/76:
[    2.926828]  #0: ffffffffc0351148 (mlock){+.+.}-{3:3}, at: 0xffffffffc0355079

시스템 콜이나 ioctl 오류 경로에서 mutex_unlock()을 빠뜨린 버그가 이런 모양이다. 다음 호출이 올 때까지 증상이 없어서, 이 경고는 lockdep 커널에서만 볼 수 있었다.

첫 보고 이후에는 조용해진다

한 번의 실행에서 초기화 누락 → 스핀락 안 GFP_KERNEL → 다른 태스크의 뮤텍스 해제를 연달아 일으켰다(two_bugs). lockdep 커널은 init 스크립트에 grep debug_locks /proc/lockdep_stats를 추가해 상태도 확인했다.

[    2.061601] lockbug: run two_bugs
[    2.061752] BUG: spinlock bad magic on CPU#1, insmod/76
[    2.071987] BUG: sleeping function called from invalid context at include/linux/sched/mm.h:321
[    2.079410] lockbug: two_bugs returned
[    2.292197] lockbug: run two_bugs
[    2.292447] INFO: trying to register non-static key.
[    2.292789] turning off the locking correctness validator.
[    2.298569] BUG: sleeping function called from invalid context at include/linux/sched/mm.h:321
[    2.299120] INFO: lockdep is turned off.
[    2.305685] lockbug: two_bugs returned
 debug_locks:                             0

세 번째 버그(뮤텍스 소유자 불일치)는 두 커널 모두 보고하지 않았다. 락 디버깅 경고는 출력 전에 debug_locks_off()를 거치므로 이미 꺼져 있으면 침묵하고, 이 플래그와 무관한 might_sleep() 검사만 두 번째 버그를 보고했다.

static void spin_bug(raw_spinlock_t *lock, const char *msg)
{
	if (!debug_locks_off())
		return;

	spin_dump(lock, msg);
}

주의사항

항목내용
첫 경고만 믿을 것락 디버깅 경고는 부팅 후 한 번만 나온다. 첫 번째를 고치고 재부팅해야 다음 버그가 보인다. lockdep 커널은 /proc/lockdep_statsdebug_locks: 0으로 확인
부팅 중 다른 드라이버의 경고내 모듈을 올리기 전에 이미 debug_locks가 꺼져 있으면 내 버그는 보고되지 않는다. dmesg에 앞선 lockdep 경고가 없는지 먼저 확인
msleep vs GFP_KERNELDEBUG_ATOMIC_SLEEPmight_sleep() 지점만 본다. 실제로 잠드는 경로는 옵션 없이도 scheduling while atomic으로 나온다
경로를 실행해야 잡힌다모든 검사는 런타임 검사다. 오류 경로·IRQ 경로는 실제로 한 번 이상 실행돼야 경고가 뜬다
lockdep 없는 lite 커널의 한계IRQ 문맥 불일치, 락 잡은 채 복귀, 락 순서(ABBA)는 못 잡는다
재귀 잠금은 멈춘다경고를 찍은 뒤에도 실제로 데드락에 빠진다. 스핀락은 soft lockup, 뮤텍스는 hung task로 이어지므로 테스트는 VM에서
운영 커널배포판 커널은 이 옵션들이 꺼져 있다. 락 경합 통계만 필요하면 lockstat을 쓴다
QEMU TCG에서 lockdep 커널부팅이 RCU stall로 멈추면 tsc=reliable을 커맨드라인에 추가

마무리

상황권장 설정
드라이버·모듈 개발용 테스트 VMPROVE_LOCKING (나머지는 자동 select) + DEBUG_ATOMIC_SLEEP
lockdep 오버헤드가 부담되는 장시간 테스트DEBUG_SPINLOCK + DEBUG_MUTEXES + DEBUG_ATOMIC_SLEEP
이미 멈춘 시스템 분석DETECT_HUNG_TASK, SOFTLOCKUP_DETECTOR + SysRq로 태스크 덤프

락 오용은 대부분 "지금은 운 좋게 동작하는" 코드라 테스트 통과만으로는 안심할 수 없다. 새 모듈은 적어도 한 번은 lockdep 커널에서 모든 경로를 돌려보고, 첫 경고가 나오면 그것부터 고친 뒤 다시 돌리는 순서가 가장 확실하다.

참고

답글 남기기