SMP가 활성화되어있거나 (멀티프로세서), 선점을 지원하는 경우에는 여러 개의 스레드 간에 자원에 동시에 접근할 수 있다. 리눅스 커널에서는 이러한 문제를 해결하기 위한 매커니즘을 제공하며, 이번 글에서는 가장 기본적인 spinlock을 소개하겠다.


자료구조

spinlock은 include/linux/spinlock_types.h에 spinlock_t로 정의되어있다. 한 번 살펴보자.

/* include/linux/spinlock_types.h */
typedef struct spinlock {
	union {
		struct raw_spinlock rlock;

/* 디버깅과 관련된 부분 */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;

#ifdef ~ #endif 부분은 config에 따라서, 디버깅 관련 기능을 활성화한 경우에 추가되는 코드이다. 그 부분을 제외하면 spinlock_t는 raw_spinlock_t만을 갖고 있다.

/* include/linux/spinlock_types.h */

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;

/* 디버깅과 관련된 부분 */
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;

raw_spinlock_t는 arch_spinlock_t를 갖고 있으며 이는 아키텍처에 종속적인 부분이다. 

선점 비활성화

linux kernel에서 preempt_disable은 선점을 비활성화하는 함수이다. 프로세스 선점이 가능하면 lock을 들고있는 도중에 다른 프로세스가 실행되어 critical section에 동시에 여러 프로세스가 접근할 수 있게 된다. 정확히 말하면 프로세스 A가 spinlock을 획득하고, 휴면 상태로 전환한 뒤 프spin 비활성화해야한다.

인터럽트 비활성화

spinlock으로 보호하는 자원이 인터럽트 핸들러에서도 사용되는 경우, 인터럽트를 비활성화해야만 한다. 선점을 비활성화해도 여전히 인터럽트는 발생할 수 있다. spinlock을 획득한 상태에서 인터럽트가 발생하고, 인터럽트 핸들러에서 해당 lock이 필요한 경우에는 데드락이 발생하게 된다. 이를 위해 커널 단에서 spin_lock_irqsave, spin_lock_irqrestore 함수를 통해 인터럽트를 비활성화하고, spinlock을 획득하는 함수를 지원한다. 만약 인터럽트 핸들러에서 절대로 해당 spinlock을 사용하지 않는단걸 알고 있다면 spin_lock, spin_unlock을 사용해도 된다. 이 경우엔 인터럽트가 비활성화되지 않는다.

softirq 비활성화

spinlock으로 보호하는 자원을 softirq에서 사용하는 경우에는 spin_lock_bh / spin_unlock_bh 함수를 사용할 수 있다.

사용법

spinlock을 사용한다면 다음과 같이 사용할 수 있다.

#include <linux/spinlock.h>

DEFINE_SPINLOCK(lock);

spin_lock(&lock);

/* Critical Section! */

spin_unlock(&lock);

spinlock을 얻으면서 인터럽트도 비활성화시키려면 spin_lock_irq / spin_unlock_irq를 사용할 수 있다.

#include <linux/spinlock.h>

/* 정적으로 spinlock 정의 */
DEFINE_SPINLOCK(lock);

spin_lock_irq(&lock);

/* Critical Section! */

spin_unlock_irq(&lock);

그런데, 인터럽트를 비활성화 하려면 현재 인터럽트가 활성화되어있다는 걸 알아야한다. 따라서 현재 인터럽트 활성화 여부에 관계 없이, 활성화된 경우에만 인터럽트를 비활성화하려면 spin_lock_irqsave / spin_unlock_irqrestore 를 사용하면 된다. 

#include <linux/spinlock.h>

/* 정적으로 spinlock 정의 */
DEFINE_SPINLOCK(lock);
unsigned long flags; // 현재 상태에 대한 플래그

spin_lock_irqsave(&lock, flags);

/* Critical Section! */

spin_unlock_irqrestore(&lock, flags);

spinlock의 장단점

장점

인터럽트 컨텍스트에서도 사용할 수 있다. 인터럽트 컨텍스트에선 휴면이 불가능하므로 뮤텍스, 세마포어 같은 휴면 가능한 락은 사용할 수 없다.

단점

spinlock을 들고있는 동안은 선점, 인터럽트를 비활성화하므로 오랫동안 잡고있으면 안된다. 단, 휴면을 하고, 다시 돌아오는 시간 (context switching 2번)이 락을 잡고있는 시간보다 길다면 spinlock을 사용하는게 좋다.

참고자료

https://elixir.bootlin.com/linux/latest/source/Documentation/locking/spinlocks.rst

https://stackoverflow.com/questions/14723924/using-spinlocks-in-user-space-application

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=18480581

https://en.wikipedia.org/wiki/Spinlock

https://0xax.gitbooks.io/linux-insides/content/SyncPrim/linux-sync-1.html