A fim de representar o uso de semáforos, utilizaremos como exemplo a implementação feita de um Sistema Operacional para Arduino: o ArdOS, que está disponível no link deixado no fim desta página. O Arduino Operating System (ArdOS), lançado em 2013, é um sistema operacional escrito especialmente para o Arduíno ATmega168 e ATmega328. Esse SO possui um núcleo que pode executar várias tarefas ao mesmo tempo.
Nele temos vários mecanismos para o controle de tarefas, tais como semáforos, mutex locks e variáveis condicionais. A seguir, veremos e analisaremos os códigos referentes ao semáforo e ao cadeado mutex, onde estes são o sema.cpp e o mutex.cpp respectivamente. A linguagem utilizada em sua implementação é C++.
O código sema.cpp cria semáforos que são úteis na coordenação de tarefas. A função OSCreateSema define o valor inicial (0 ou 1 para semafóro binário, qualquer inteiro positivo, incluindo 0 para um semafóro de contagem) e o tipo do semáforo.
void OSCreateSema(TOSSema *sema, unsigned int initval, unsigned char isBinary) { unsigned char sreg; OSMakeAtomic(&sreg); sema->isBinary=isBinary; if(!isBinary) sema->semaval=initval; else if(initval) sema->semaval=1; else sema->semaval=0; initQ(sema->tasklist, _maxTasks, &sema->taskQ); OSExitAtomic(sreg); }
A função OSTakeSema recebe como parâmetro um ponteiro *sema para o semáforo que será adquirido. Retorna se sema não for zero, caso contrário, bloqueia.
void OSTakeSema(TOSSema *sema) { unsigned char sreg; OSMakeAtomic(&sreg); if(sema->semaval>0) sema->semaval--; else { _tasks[_running].status |= _OS_BLOCKED; prioEnq(_running, _tasks, &sema->taskQ); OSExitAtomic(sreg); OSSwap(); } OSExitAtomic(sreg); }
A função OSGiveSema recebe como parâmetro um ponteiro *sema para o semáforo que será entregue. Desbloqueia uma task com maior prioridade se ela estiver esperando no semáforo. Caso contrário, incrementa semaval (ou define-o para 1 se é binário).
void OSGiveSema(TOSSema *sema) { unsigned char sreg; OSMakeAtomic(&sreg); unsigned char tsk=procDeq(&sema->taskQ); if(tsk != 255) { _tasks[tsk].status &= ~(_OS_BLOCKED); procEnq(tsk, _tasks, &_ready); OSExitAtomic(sreg); OSPrioSwap(); } else if(sema->isBinary) sema->semaval=1; else sema->semaval++; OSExitAtomic(sreg); }
O mutex.cpp é responsável por promover o suporte ao mutex a as variáveis condicionais. Concede acesso exclusivo ao recurso compartilhado de apenas uma thread. A função OSCreateMutex recebe como parâmetro um ponteiro *mutex que é o cadeado mutex que será inicializado. Essa função tem como objetivo iniciar um cadeado mutex.
void OSCreateMutex(OSMutex *mutex) { unsigned char sreg; OSMakeAtomic(&sreg); mutex->val=1; initQ(mutex->procList, _maxTasks, &mutex->blocked); OSExitAtomic(sreg); }
A função OSTakeMutex recebe como parâmetro o mesmo ponteiro *mutex da função anterior. Se o cadeado estiver livre, esta função retorna imediatamente. Se o cadeado foi tomado por outra task, essa chamada será bloqueada até que o cadeado seja liberado.
void OSTakeMutex(OSMutex *mutex) { unsigned char sreg; OSMakeAtomic(&sreg); if(!mutex->val) { procEnq(_running, _tasks, &mutex->blocked); _tasks[_running].status |= _OS_BLOCKED; OSExitAtomic(sreg); OSSwap(); } else mutex->val=0; OSExitAtomic(sreg); }
A função OSGiveMutex recebe como parâmetro o mesmo ponteiro *mutex das funções anteriores. Essa chamada libera um cadeado mutex. Se alguma task estiver bloqueando o cadeado, a task com maior prioridade é desbloqueada e torna-se pronto para iniciar. A task chamando a função OSGiveMutex será pré-empilhada se a tarefa desbloqueada for de uma maior prioridade.
void OSGiveMutex(OSMutex *mutex) { unsigned char sreg; OSMakeAtomic(&sreg); unsigned char wakeProc=procDeq(&mutex->blocked); if(wakeProc!=255) { _tasks[wakeProc].status &= ~_OS_BLOCKED; procEnq(wakeProc, _tasks, &_ready); OSExitAtomic(sreg); OSPrioSwap(); } else mutex->val=1; OSExitAtomic(sreg); }