Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

176162
807
85244


Spinlock(스핀락) - spin_lock() vs spin_lock_irq() vs spin_lock_irqsave() - 분석 9장. 커널 동기화 소개


자 그 동안 궁금해왔던 아래 API들의 차이점에 대해서 알아보는 시간을 갖도록 하겠습니다.
spin_lock(), spin_lock_irq(), spin_lock_irqsave()

리눅스 커널 책에서 마르고 닳도록 설명을 많이 하고 있는데요. 
직접 소스를 열어서 분석하는게 가장 좋은 리눅스 커널을 마스터하는 길인 것 같아요. 소스 코드가 오픈되어 있잖아요.

1> spin_lock()
아래 순서로 실제 구현부는 __raw_spin_lock() 함수 라는 걸 알 수 있어요.

spin_lock -> raw_spin_lock -> __raw_spin_lock
static inline void spin_lock(spinlock_t *lock)
{
 raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
 
전처리 파일로 __raw_spin_lock() 함수를 확인해보면 preempt_count_add(1) 함수 호출로 갑자기 irq가 호출될 시 preemption을 막고(*Appendix 1)
바로 do_raw_spin_lock() 함수를 호출하는군요.
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) void __raw_spin_lock(raw_spinlock_t *lock)
{
 do { preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
 do { } while (0);
 do_raw_spin_lock(lock);
}
 
2> spin_lock_irq()
역시나 이 함수의 정체는 __raw_spin_lock_irq() 임을 알 수 있어요.
spin_lock_irq -> raw_spin_lock_irq -> __raw_spin_lock_irq
static inline void spin_lock_irq(spinlock_t *lock)
{
 raw_spin_lock_irq(&lock->rlock);
}

#define raw_spin_lock_irq(lock)  _raw_spin_lock_irq(lock)
.
__raw_spin_lock_irq() 함수의 구현부를 보면,
arch_local_irq_disable()을 호출해버려서 "msr daifset, #2" ARM64(Aarch64) 인스트럭션이
수행되어 아예 IRQ가 하드웨어적으로 Trigger가 안되게 막아 버리는군요.

그리고 preempt_count_add(1)을 호출해서 IRQ가 혹시 trigger될 시 preemption이 안 되도록 방지하죠.(*Appendix 1)
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
 do { arch_local_irq_disable(); trace_hardirqs_off(); } while (0);
 do { preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
 do { } while (0);
 do_raw_spin_lock(lock);
}
 
3> spin_lock_irqsave()
이 함수의 정체는 __raw_spin_lock_irqsave() 인데요.
raw_spin_lock_irqsave -> _raw_spin_lock_irqsave -> __raw_spin_lock_irqsave

실제 구현부는 아래와 같아요.
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
 unsigned long flags;

 do { do { ({ unsigned long __dummy; typeof(flags) __dummy2; (void)(&__dummy == &__dummy2); 1; }); flags = arch_local_irq_save(); } while (0); trace_hardirqs_off(); }
 do { preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
 do { } while (0);

 do_raw_spin_lock(lock);

 return flags;
}
 
__raw_spin_lock_irqsave() 함수의 핵심 코드는 arch_local_irq_save()이라 이 코드를 좀 더 분석할 필요가 있어요.

arch_local_irq_save()함수의 구현부를 보면, 크게 2가지 일을 하고 있어요.
[1]: "mrs %0, daif" // Interrupt disable flag을 설정한 후 해당 값을 flags에 저장
[2]:"msr daifset, #2"" // 하드웨어적으로 IRQ를 disable함
static inline unsigned long arch_local_irq_save(void)
{
 unsigned long flags;
 asm volatile(
  "mrs %0, daif  // arch_local_irq_save\n"  //<<--[1]
  "msr daifset, #2"            //<<--[2]
  : "=r" (flags)
  :
  : "memory");
 return flags;
}
.
여기서 중요한 포인트는 flags를 저장해서 리턴한다는 건데요.
나중에 spin_unlock_irqrestore() 함수가 호출되어 아래 순서로 arch_local_irq_restore() 함수의 파라미터로 쓰이게 되요.
 __raw_spin_unlock_irqrestore -> local_irq_restore -> arch_local_irq_restore
static inline void arch_local_irq_restore(unsigned long flags)
{
 asm volatile(
  "msr daif, %0  // arch_local_irq_restore"
 :
 : "r" (flags)
 : "memory");
}

그럼 간단히 코드 리뷰를 했으니 정리 좀 하려고 해요.

spin_lock()
spinlock을 거는 구간이 빨리 수행되거나 IRQ가 trigger되어 preemption되어도 문제가 안될 때 쓸 수 있어요.
spin_lock 내 함수 호출이 없는 경우 쓰면 좋겠죠.

spin_lock_irq()
위 코드를 봤듯이 arch_local_irq_disable()을 호출해버려서 "msr daifset, #2" ARM64(Aarch64) 인스트럭션이
수행되어 spin_unlock_irq()이 수행될 때 까지 아예 IRQ가 하드웨어적으로 Trigger가 먹통이 되버리거든요.
spin_lock_irq()는 거는 구간이 너무 오래 수행되는 코드면 시스템이 느려지게 되겠죠.

spin_lock_irqsave() 
코드로 봤듯이 spin_lock_irq() 함수랑 기능적으로 다른게 아무것도 없거든요.

대신 arch_local_irq_save() 호출로  "mrs %0, daif" Instruction이 수행되어 interrupt flags를 가져온다는 점만 다른데요.

spin_lock_irq() 혹은 spin_lock() 함수가 서브루틴에 많이 쓰게 되면 Interrupt가 disable 혹은 enable 되었는지 파악하기 어려운 경우가 있거든요.
A -> B -> C 함수가 호출된다고 가정해보아요.
A함수: spin_lock_irq() 
-B함수: spin_lock_irq() 
--C함수: spin_lock_irq() 

그래서 ARM64 Instruction을 활용해서 Interrupt을 enable 때의 고유한 flag를  "msr daifset, #2"로 리턴하고
Interrupt을 disable 때 이전에 설정한 flag로  "msr daif, %0"  Interrupt을 disable하게 되요.

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"

[*Appendix 1]
http://rousalome.egloos.com/9964816
http://rousalome.egloos.com/9966282


Reference(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 


    핑백

    덧글

    댓글 입력 영역