본문 바로가기

ComputerScience/Linux

Linux 11. Synchronization (2)

728x90

1. Spinlock

spinlock은 linux에서 가장 많이 사용하는 lock중 하나이다. thread는 lock을 aquire하기 위해 loop안에서 계속 spin하면서 기다린다. lock이 available한지 계속해서 검사한다.

spinlock은 context switching없이 busy watiting 하는 동안 계속해서 cpu cycle을 낭비한다. 그래서 보통은 short time내에 lock을 얻을 수 있는 task에 많이 쓴다.

io 같이 hdd에서 읽기, 쓰기 작업이 많은 경우는 busy waiting이 상대적으로 길다. 이렇게 긴 시간을 기다려야 하는 경우는 보통 spinlock 대신 mutex등을 사용한다. 반면 자료구조에 삽입 삭제하는 경우는 상대적으로 busy waiting하는 시간이 짧기 때문에 spinlock을 사용할 수 있다.

더불어 ticket spinlock은 FIFO manner로 lock을 건네주기 때문에 fairness를 보장해서 starvation을 막을 수 있다.

 

2. Ticket Spinlock in Linux Kernel

 

global variable로 선언된 spinlock_t counter_lock을 init한다.

두개의 thread가 counter를 1씩 증가 시킨다. 공유자원인 counter를 1증가시키는 critical section을 보호하기 위해 spin_lock으로 감싼다. 두 쓰레드는 순차적으로 counter를 1씩 증가시킨다.

 

3. Spinlock APIs

총 세 종류의 spin_lock을 알아보자.

 

4. Spinlock APIs - Single spinlock

spin_lock은 preemption만 disable시킨다. unlock을 해주고 나서야만 이 task는 preemption이 가능하다.

위 예시에서만 보면 preempt_count를 그냥 true, false로만 나타내도 될 것 같지만

이렇게 nested spin_lock 구문에서는 race condition을 유발한다.

spin_unlock(lockb)에서 preempt_count를 false로 해버리면 아직 locka를 unlock하지않았음에도 불구하고 이 task는 preemption이 가능하다. 이때부터 a,b 자원에 접근하려는 경쟁이 생길 수 있다.

즉 boolen type으로는 nested spin_lock을 지원할 수 없다.

spinlock에서도 disadvantage가 있다. Task1 수행을 시작하고 lock을 aquire하고 critical section으로 진입한다. a에 대한 값을 변화시키고 있는데 Interrupt가 발생했다. interrupt service routine을 수행하기 시작한다. ISR안에서도 a를 변경하려고 한다. 그런데 이미 task1이 lock을 얻고나서 release해주지 않은 상태기 때문에 ISR은 spin에 걸리고 만다. 즉 task1의 unlock을 기다리는 deadlock이 발생한다.

*참고로 ISR code에서 preempt_count는 0이다. preempt_count는 per task이다. 반면 interrupt는 based on core이다.

 

5. Spinlock APIs - spin_lock_irq()

spin_lock_irq는 interrupt/preemption 둘다를 disable/enable 한다.

하지만 nested spin_lock_irq()에서도 문제가 발생할 수 있다.

문제는 한번의 spin_unlock_irq만으로도 interrupt를 enable 시켜버린다는 것이다. 이때 preemption만 아직 불가능하다.

lockb를 unlock하는 순간에 이미 interrupt가 끼어들 수 있는 상황이 되어버린다. 즉 이때부터 race condition이 발생할 수 있다.

 

6. Spinlock APIs - spin_lock_irqsave()

spin_lock_irqsave를 사용하면 이 문제를 해결할 수 있다. interrupt state를 기록했다가 하나씩 restore한다.

 

7. Spinlock APIs 정리

spin_lock()

spinlock이 나머지 둘 중에 performance는 가장 좋다. 하지만 interrupt를 disable하지 않기 때문에 race condition이 발생할 수 있다.

하지만 interrupt service routing의 critical section이 내 task의 critical section과 겹치지 않는다면 굳이 interrupt까지 disable 시킬 필요는 없을 수 있다.

 

spin_lock_irq()

interrupt handler와 task가 spinlock을 서로 공유한다면 spin_lock_irq를 사용하는 편이 더 안전하다.

명심해야 할 것은 unlock한번으로 interrupt가 몇번 disable됐느지랑 상관없이 enable된다는 것이다.

 

spin_lock_irqsave()

interrupt state도 저장해야 하므로 셋중 가장 느리다. 확신이 없을때는 이게 가장 안전한 방법이다.

 

8. spinlock structure in Linux Kernel

architecture 별로 구현된 arch_spinlock_t에 따라 추상화되어있다.

spin_lock_init을 호출하면 아래로 차례대로 호출된다.

이렇게 쭈루룩 호출된다 중요한 건 가장 먼저 preempt_disable()을 한다는 것이다.

728x90
반응형

'ComputerScience > Linux' 카테고리의 다른 글

Linux 10. Synchronization  (1) 2023.10.22
Linux 9. Thread  (1) 2023.10.22
Linux8. Process  (1) 2023.10.09
Linux6. Makefile  (0) 2023.09.28
Linux4. Design Principle of Linux Kernel  (0) 2023.09.28