#커널 동기화
리눅스 커널 ( Linux Kernel ) 동네에서 가장 유명한 스핀락에 대한 상세 분석을 더 해보려고 해요.
자자, 이제 A, B, C 모듈에서 spinlock을 순서대로 잡는 시나리오를 만들어 볼께요.
spinlock value는 특정 메모리 공간에 있는 전역 변수와 같다고 보면 되요.
1. A 모듈이 스핀락을 겁니다.
spinlock value
next | owner
0001 0000
2. B 모듈이 스핀락을 겁니다. 자 이때 A모듈이 스핀락을 잡고 있어요.
spinlock value
next | owner
0001 0000 //<<-- arch_spin_lock() 진입 전 next값을 로컬 변수에 저장, 자 그럼 로컬 변수 lockval.tickets.next=1, lockval.tickets.owner=0 이겠죠
0002 0000 // spinlock value next를 1만큼 증가
// 아래 코드에서 spinlock value값에서 owner가 1만큼 증가(이 의미는 A가 spinlock 해제:spinlock value owner 증가시킴)되어
// lockval.tickets.next=1 값과 같을 때까지 무한루프(B 모듈 입장은 이렇습니다.) 스핀락을 얻지 못하고 아래 무한루프에서 기다려요! 어디서요? 자신만의 스택 공간에서요.
while (lockval.tickets.next != lockval.tickets.owner) {
__asm__ __volatile__ ("wfe" : : : "memory");
lockval.tickets.owner = (*(volatile typeof(lock->tickets.owner) *)&(lock->tickets.owner));
}
3. C 모듈이 스핀락을 겁니다. 자 이때 A/B 모듈이 스핀락을 잡고 있어요.
spinlock value
next | owner
0002 0000 //<<-- arch_spin_lock() 진입 전 next값을 로컬 변수에 저장, 자 그럼 로컬 변수 lockval.tickets.next=2, lockval.tickets.owner=0 이겠죠
0003 0000 // spinlock value next를 1만큼 증가
// 아래 코드에서 spinlock value값에서 owner가 2만큼 증가(이 의미는 A/B가 spinlock 해제:spinlock value owner 증가시킴)될 때 까지 무한루프
// lockval.tickets.next=2 값과 같을 때까지 무한루프(C 모듈 입장은 이렇군요.) 역시 스핀락을 얻지 못하고 아래 무한루프에서 기다려요!
while (lockval.tickets.next != lockval.tickets.owner) {
__asm__ __volatile__ ("wfe" : : : "memory");
lockval.tickets.owner = (*(volatile typeof(lock->tickets.owner) *)&(lock->tickets.owner));
4. A 모듈이 드디어 스핀락 해제(arch_spin_unlock 호출)합니다.
spinlock value owner를 1만큼 증가시키죠.
spinlock value
next | owner
0003 0001
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) void arch_spin_unlock(arch_spinlock_t *lock)
{
__asm__ __volatile__ ("dmb " "ish" : : : "memory");
lock->tickets.owner++;
dsb_sev();
}
아까 2번 동작에서 B 모듈 입장은,
spinlock value값에서 owner가 1만큼 증가(이 의미는 A가 spinlock 해제:spinlock value owner 증가시킴)되어
lockval.tickets.next=1 값과 같을 때까지 무한루프 돌고 있었어요. 어디서요? 자신만의 스택 공간에서 돌고 있었겠죠.
A 모듈이 스핀락 해제 했으니 spinlock value owner가 1이 되구요,
아래 [1]번 코드에서 lockval.tickets.owner번이 1이 되는군요.
while (lockval.tickets.next != lockval.tickets.owner) {
__asm__ __volatile__ ("wfe" : : : "memory");
lockval.tickets.owner = (*(volatile typeof(lock->tickets.owner) *)&(lock->tickets.owner)); //<<--[1]
}
이제 드디어 B가 스핀락을 획득하게 됩니다.
하지만 C 모듈 입장은,
spinlock value값에서 owner가 1만큼 증가되도 lockval.tickets.owner값이 1로 바뀌게 되어 계속
lockval.tickets.next=2 값과 같을 때까지 while loop 내에서 무한루프 돌고 있겠죠.
안타깝게도 C 모듈은 스핀락을 못 얻게 되는군요.
5. 자 이제, B 모듈이 스핀락 해제합니다.
spinlock value owner를 1만큼 증가시키죠.
spinlock value
next | owner
0003 0002
아까 3번 동작에서 C 모듈 입장은,
spinlock value값에서 owner가 1만큼 증가(이 의미는 B가 spinlock 해제:spinlock value owner 증가시킴)되어
lockval.tickets.next=2 값과 같을 때까지 무한루프 돌고 있었어요. 어디서요? 자신만의 스택 공간에서 돌고 있었겠죠.
B 모듈이 스핀락 해제 했으니 spinlock value owner가 2로 됐구요,
아래 [2]번 코드에서 lockval.tickets.owner번이 2이 되는군요.
while (lockval.tickets.next != lockval.tickets.owner) {
__asm__ __volatile__ ("wfe" : : : "memory");
lockval.tickets.owner = (*(volatile typeof(lock->tickets.owner) *)&(lock->tickets.owner)); //<<--[2]
}
드디어 C 모듈이 while 루프에서 빠져나와 스핀락을 잡게 됩니다.
사실 위 시나리오는 spinlock이 정상적으로 돌 때구요.
spinlock deadlock은 위 시나리오에서 A란 모듈이 spinlock을 획득하고 릴리즈를 하지 않으면(spinlock value owner을 1로 증감시키지 않으면)
B/C 모듈은 각자의 스택 공간에서 while 루프에서 무한루프를 돌고 있겠죠. 물론 spinlock을 획득을 기다리면서요.
이게 spinlock deadlock이라고 해요. 실제 spinlock deadlock이 나면 보통 watchdog reset이나 CPU stall 현상으로 확인됩니다.
다른 Crash Issue 디버깅 과정을 업데이트할 예정이니 조금만 기다리세요.
# Reference: For more information on 'Linux Kernel';
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2

최근 덧글