詳解Linuxカーネルの5章カーネルの同期処理に関連する、セマフォについて調べた。
カーネルバージョン
v4.4
セマフォとは
ロックが取れない場合に、休止状態で実行を待たせる機能。スピンロックよりはCPUを有効活用出来るパターンもあるんだろうと思う。
いろいろ調べた後にに気づいたが、この機構はほとんど使われていない・・・古い機能なのかな?
構造体
解放と取得の関数はスピンロックで保護されている。いろんな関数がラップしているが__up()
や__down_common()
が操作する関数の本体になる。
wait_list
にはstruct semaphore_waiter
が繋がっていて、task_struct
が辿れるようになっている。
/* Please don't access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
初期化論理
nは同時に取れるセマフォの数、DEFINE_SEMAPHORE
マクロでラップされていて、その場合nは1になる。
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
セマフォの解放
__up()
ではwait_listにつながって待っている最初のプロセスを起床させるだけで、特に難しいことはない。
countはゼロになるまではデクリメントするだけで良い。ここはスピンロックをとっているからアトミック操作になっている。
/**
* up - release the semaphore
* @sem: the semaphore to release
*
* Release the semaphore. Unlike mutexes, up() may be called from any
* context and even by tasks which have never called down().
*/
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);
セマフォの取得
down()
はdeprecatedで、down_interruptible()
か、down_killable()
を使えとなっている。それぞれの違いはなんのシグナルに反応するかで、全部のシグナルか、SIGKILLだけかを選べる。
結局のところ、どの関数も__down_common()
の内部で、次のforループで自分がupされるまでまつ。timeoutか指定のシグナルを受け取った場合は、ロックを取らずに返る。
スピンロックはschedule_timeout()
の前で解除しているので、スピンロックをとりっぱなしにはならない。
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_task_state(task, state);
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout);
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0;
}