multi thread 프로그래밍이 발전하면서, 초기에 여러 thread간의 교통정리(synchronization)을 위해

lock이라는 메커니즘이 등장하게 됩니다.

 

초기에 존재했던 lock은 mutex(MUTually EXclusive lock)로 존재 했습니다.

mutex는 만약 다른 쓰레드가 lock을 잡고 있는 경우 쓰레드가 sleep하도록 설계되어,

쓰다보니 critical section이 매우 짧은 경우에 퍼포먼스가 낭비되는 단점이 있고,

lock을 잡지 못해서 thread가 sleep하는 경우, sleep을 하기 위한 작업을 하는 시간 안에

이미 lock이 풀려 버리기 때문이었죠.

 

그래서 spinlock이 등장하게 되는데, spinlock은 lock을 잡을 수 없을 때,

mutex처럼 sleep에 들어가는게 아니라, loop를 돌며 busy-waiting을 하게 하는 겁니다.

critical section이 짧은 경우는 굳이 mutex가 아니라 spinlock을 쓰면 더 빠르게 동작 할 수 있는 장점이 있습니다.

 

이렇게 해서 행복하게 마무리 되는가 싶었으나.....

인터럽트가 발생하는 경우 또 다른 문제가 야기 되게 됩니다.

thread가 spinlock을 잡고 일을 하고 있는데, 더 강력한 인터럽트가 발생해 버리면,

인터럽트 핸들러가 돌고 있는데, 그 인터럽트 핸들러가 spinlock을 잡으려고 하는 경우가 발생합니다.

이럴 경우, 원래 thread가 spinlock을 잡고 있었는데, 그 thread가 더 이상 돌지 못하게 되어 deadlock이 발생하게 됩니다.

 

그래서 등장한 것이 spin_lock_irq 함수이고, 이 놈은 spinlock을 잡으면서 인터럽트를 disable 시켜주죠.

(물론, enable 해주면 그 동안 발생한 인터럽트가 처리됩니다.)

spin_lock_irq를 쓰면 spinlock의 critical section내에서 인터럽트 자체가 처리되지 않으므로, 앞에서 언급한 그런 문제가

발생하지 않게 됩니다.

 

하지만, 함수를 nesting해서 쓰게 되는 경우, spin_lock_irq 함수가 까다로워 집니다.

몇번을 disable 했던지간에, enable은 한번만 해주면 결국 인터럽트가 다시 복구되는데,

spin_lock_irq를 하려는 함수가 들어왔을때 인터럽트가 enable 상태인지, disable 상태인지 모르면,

spin_unlock을 할 때 enable 해야 할지 말아야 할지 모르게 되버리는 상황이 생깁니다.

 

이런 경우에 사용하는게 spin_lock_irqsave 함수 인데요. 이 함수는 irq를 disable 하면서 irq가 이전에 enable인지 disable인지

를 변수에 저장해줍니다.

그래서 이 함수는 저장할 변수를 인자로 받게 됩니다.

interrupt disable 함수가 nesting 되어서 사용될 때에는 이 함수를 이용하시는게 좋습니다.

다만, 단순히 disable만 하는게 아니라 상태를 메모리에 저장도 하기 때문에,

퍼포먼스 오버헤드는  spin_lock_irq보다 살짝 크게 됩니다.


추가적으로, spin_lock()이 interrupt handler / softirq에서 사용할 수 없는 건 아닙니다.

Thread context 실행 중 임의의 시점에 중지되고 softirq나 irq가 실행될 수 있으며, softirq 실행 중엔 임의의 시점에 irq가 실행될 수 있습니다. 

위의 설명과 같이, 만약 같은 lock을 thread context와 irq에서 사용한다면 thread context에서 락을 잡고 있는 중에 irq가 실행되고 irq가 다시 같은 락을 획득하려고 할 수 있습니다. 

따라서, 위 현상은 데드락입니다. spin_lock_{irq[save]|bh}()는 이런 데드락을 피하기 위해서 사용합니다.

하나의 lock이 irq에서도 사용되고 softirq나 thread context에서도 사용된다면, 

softirq/thread context에선 spin_lock_irq()를 irq 코드에선 spin_lock()을 사용합니다. 

같은 코드를 irq와 다른 컨택스트에서 공유하거나해서 irq 상태를 알 수 없으면 spin_lock_irqsave()를 쓰면 됩니다. 

irq handler안에서는 spin_[un]lock_irq()를 쓰면 문제를 야기 시킬 수 있습니다. 

마찬가지로 softirq와 thread context가 공유하는 락은 thread context에서 spin_lock_bh()를 쓰면 됩니다. 

softirq disable은 카운팅이 되기 때문에 softirq안에서도 그냥 써도 무방합니다. (bh_save가 없는 이유 입니다).


하나의 컨택스트 (irq, softirq, thread context 중) 안에서만 사용되는 Lock은 spin_lock()을 씁니다.

하나 이상의 컨택스트에서 사용되는 락은 낮은 우선순위의 컨택스트에서 락을 잡을 때 락을 사용하는 컨택스트 중 가장 높은 우선순위를 막고 잡으면 됩니다.

 



 

정리

 

  • spin_lock_init(spinlock)

spin_lock을 커널에 초기화 및 등록 합니다.

  • spin_lock (spinlock)   ↔  spin_unlock (spinlock)

spin lock으로 보호해야 할 데이터가
interrupt handler(top half)에서도, softirq handler에서도 접근하지 않고
단지 그 외의 일반적인 커널 코드에서만 접근한다면
interrupt disable, softirq disable 같은 overhead 없이 spin lock을 얻을때 사용한다.

dead lock의 위험, interactivity등 시스템의 퍼포먼스에 영향이 크다.

기본이 spinlock. 다른걸 이용할 필요가 없다면 이걸 쓴다.

가장 퍼포먼스 오버헤드가 적다.

  • spin_lock_irq (spin lock irq disable)   ↔  spin_unlock_irq(spinlock enable)

interrupt handler와 일반 thread가 spinlock을 공유하는 경우에 사용한다.

spinunlock irq enable로 unlock을 해 주면, 인터럽트가 몇번 disable됐는지 상관 않고

enable돼 버리므로 주의한다.

  • spin_lock_irqsave (spin lock irq save)   ↔   spin_unlock_irqresotre(spin lock irqsave)

spin_lock_irq과 같지만, irq disable하기 직전의 interrupt enable여부를 변수에 저장한다.

spinlock을 이용하는 시점에서 interrupt enable여부를 확신할 수 없는 경우에 쓴다.

메모리에 상태를 저장하게 되므로, disable만 하는 함수에 비해서는 느리다.

  • 비교

1. spin_lock_irqsave() vs spin_lock_irq()

둘다 interrupt를 disable 시킨 이후에 spin lock을 획득하는 것은 동일하지만,
spin_lock_irqsave()는 CPU의 flag 레지스터를 보관했다가
spin_unlock_irqrestore()로 복구할 수 있습니다.

2. spin_lock() vs spin_lock_irq()

spin_lock()은 interrupt를 disable하지 않기 때문에
interrupt handler(top half)에서는 사용할 수 없다.
대신 이때는 spin_lock_irq()나 spin_lock_irqsave()를 사용해야 한다.

3. spin_lock_bh()

같은 종류의 softirq는 여려 CPU에서 동시에 실행될 수 있기 때문에
([ksoftirqd/CPUn] 커널 쓰레드가 softirq의 실행을 담당하고 있습니다.)
softirq를 disable한 이후에 spin lock을 획득하며,
원래 이 함수의 이름은 spin_lock_softirq() 정도가 되어야 맞겠지만
예전 커널의 흔적이 아직 남아있는 상태 입니다.


'System Programming > Linux Kernel' 카테고리의 다른 글

라이브러리 로딩 ld.so.conf  (0) 2017.01.17
udev  (0) 2016.09.12
커널 타이머  (0) 2016.06.08
[Linux] ticket spin lock  (0) 2016.05.17

+ Recent posts