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).