Do struktury semafora dodany zostaje spinlock.
struct semaphore{ struct task *queue; unsigned int value; spinlock_t spinlock; }; typedef struct semaphore semaphore_t; void semaphore_init(semaphore *sem) { sem->queue = 0; sem->value = 1; spinlock_init(&sem->spinlock); } void semaphore_lock(semaphore_t *sem); void semaphore_unlock(semaphore_t *sem);
Rozważmy rozwiązanie, korzystające wyłącznie ze spinlocka.
void semaphore_lock(semaphore_t *sem) { spinlock_lock(&sem->spinlock); if (sem->value==1) { sem->value=0; } else { add_last(sem->queue, current); remove(run_queue, current); scheduler(); } spinlock_unlock(&sem->spinlock); } void semaphore_unlock(semaphore_t *sem) { spinlock_lock(&sem->spinlock); if (empty(sem->queue)) { sem->value = 1; } else { struct task *next = remove_first(sem->queue); add_last(run_queue, next); } spinlock_unlock(&sem->spinlock); }
To rozwiązanie jest niepoprawne (zarówno na jednoprocesorwej jak i wieloprocesorowej maszynie) - powoduje, że zadanie czeka na obudzenie trzymając blokadę potrzebną do obudzenia.
Warto zastanowić się także nad rozwiązaniem, które oddaje spinlock przed oddaniem procesora (przełączeniem się na inne zadanie).
void semaphore_lock(semaphore_t *sem) { spinlock_lock(&sem->spinlock); if (sem->value==1) { sem->value=0; spinlock_unlock(&sem->spinlock); } else { add_last(sem->queue, current); remove(run_queue, current); spinlock_unlock(&sem->spinlock); scheduler(); } } void semaphore_unlock(semaphore_t *sem) { spinlock_lock(&sem->spinlock); if (empty(sem->queue)) { sem->value = 1; } else { struct task *next = remove_first(sem->queue); add_last(run_queue, next); } spinlock_unlock(&sem->spinlock); }
To rozwiązanie jest także niepoprawne (zarówno na jednoprocesorwej jak i wieloprocesorowej maszynie) - jeśli przerwanie (wskutek którego przełączone zostanie zadanie - przerwanie od zegara) przyjdzie do tego procesu po tym jak usunie się on z kolejki run_queue, a przed oddaniem blokady spinlock to już nigdy nie będzie wykonywany i nigdy nie zwolni blokady spinlock (potrzebnej do obudzenia go - przeniesienia do kolejki run_queue).