Semafor (spinlock)

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