본문 바로가기
Kernel/Locking Primitives

[Linux Kernel] semaphore (1) - semaphore의 개념과 사용법

by hyeyoo 2021. 4. 22.
※ 이 블로그의 글은 글쓴이가 공부하면서 정리하여 쓴 글입니다.
※ 최대한 내용을 검토하면서 글을 쓰지만 틀린 내용이 있을 수 있습니다.
※ 만약 틀린 부분이 있다면 댓글로 알려주세요.

 

 

[kernel/locking] spinlock (1) - spinlock 사용법

앞선 글에서 lock이란 무엇이고, lock의 필요성에 대해서 알아보았다. 이번 글에서는 가장 기본적인 spinlock을 리눅스 커널에서 어떻게 구현했는지 알아볼 것이다. 우선 관련된 개념을 몇 가지 살펴

hyeyoo.com

spinlock은 선점을 비활성화하고, 필요에 따라서 interrupt나 bottom half도 비활성화를 하므로 spinlock은 오랫동안 락을 들고있어선 안된다. 오랫동안 스케줄링과 인터럽트가 처리되지 않는다고 생각하면 정말 끔찍하다. spinlock은 그 특성상 락을 획득한 동안에는 sleep이 불가능하다. 이러한 상황에서는 spinlock 대신, "sleeping lock"인 semaphore를 사용할 수 있다.

개념

semaphore란

spinlock은 한 번에 하나의 스레드만 락을 획득할 수 있다. 따라서 상호배제(mutual exclusion, 한 번에 하나의 스레드만 critical section에 들어가는 것)이 가능하다. 이와 달리 semaphore는 그 구조상, 여러 개의 스레드가 critial section에 들어갈 수 있다. 이 개수를 count라고 하자. count > 0이면, 락을 획득할 수 있는 상태이며, count == 0이면, 다른 스레드가 락을 반환할 때까지 대기해야 한다. 락을 획득하여 count를 감소하는 것을 P 연산 (Proberen), 락을 반환하여 count를 증가하는 것을 V 연산 (Verhogen)이라고 한다.

counting semaphore vs binary semaphore

count == 1인 semaphore를 binary semaphore라고 하고, count > 1인 semaphore를 counting semaphore라고 부른다.

자, 근데 상식적으로 생각해봤을 때 count > 1인 semaphore는 mutual exclusion이 불가능하다. 아니, 두 개의 스레드가 동시에 critial section 안에서 실행중이라면, 락을 사용하는 이유가 없지 않나? 그래서 나도 아직 이걸 왜 사용하는지는 잘 모르겠다. 적어도 mutual exclusion을 위해서 사용하지는 않는다.

 

binary semaphore는 한 번에 하나의 스레드만 critial section에 진입할 수 있다. 따라서 우리가 원하는 동기화 매커니즘을 구현할 수 있다. binary semaphore를 MUTEX (MUTual EXclusion)이라고 부르기도 한다.

semaphore API

semaphore의 구조와 할당

/* Please don't access any members of this structure directly */
struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

semaphore 구조체에는 아까 우리가 이야기한 count와, 락을 기다리는 wait_list, 그리고 semaphore 구조체 자체를 보호하기 위한 spinlock이 존재한다.

/* static allocation (count == 1) */
DEFINE_SEMAPHORE(name);

/* dynamic allocation */
semaphore sem;
int count = 1;
sema_init(&sem, count);

down, down_killable, down_interruptible, down_trylock, down_timeout

리눅스 커널에서 P 연산에 대응되는 함수가 바로 down이다. down은 현재 count가 0이라면 락을 획득할 때까지 대기하며, count > 0이라면 락을 획득하고 count를 감소한다. 그런데 down 함수를 사용하면 TASK_UNINTERRUPTIBLE 상태가 되기 때문에, 시그널 등이 와도 반응하지 않는다. 그래서 죽일 수 없는 프로세스가 만들어지는데, 이를 방지하기 위해서 일반적으로 down_interruptible 또는 down_killable을 사용한다. 그 외에 down_trylock은,  현재 count > 0인 경우에만 락을 획득하며 count == 0이라면 락을 기다리지 않고 바로 함수를 리턴한다. down_timeout은 락을 기다리되 jiffies 동안만 대기한다.

 

자세한 설명은 kernel/locking/semaphore.c를 참고하자. 정말 문서화가 잘 되어있다.

void down(struct semaphore *sem);
int  down_interruptible(struct semaphore *sem);
int  down_killable(struct semaphore *sem);
int  down_trylock(struct semaphore *sem);
int  down_timeout(struct semaphore *sem, long jiffies);

up

V 연산에 대응되는 함수는 up이다. down 함수는 변종이 많지만, up 함수는 단 하나뿐이다. up 함수는 획득한 락을 반환한다. up 함수는 락을 획득한 프로세스가 아니더라도 호출할 수 있다. (up를 임의로 호출해서 count를 증가시킬 수 있다.)

void up(struct semaphore *sem)

간단한 예시

DEFINE_SEMAPHORE(sem);

down_interruptible(&sem);

/* critical section */

up(&sem);

댓글