Em sistemas embarcados, diversos recursos de hardware precisam ser acessados de forma exclusiva por um único processo por vez. Quando isso é necessário, os demais processos devem aguardar ou serem rejeitados. O desenvolvimento do driver deve contemplar essa restrição para evitar conflitos de acesso.
Cenário Prático:
Considere um driver para controle de LEDs com 4 unidades. Durante a operação de abertura, o driver configura os pinos do hardware. Sem nenhum mecanismo de exclusão, múltiplos processos conseguem abrir o dispositivo simultaneamente, resultando em inicializações redundantes do hardware. Se quatro instâncias do programa de teste forem executadas, cada uma obtém um descritor de arquivo válido e a função de abertura do driver é invocada quatro vezes consecutivas.
Para garantir que apenas um processo utilize o driver por vez, é necessário implementar controle de acesso na função de abertura.
Abordagem com Variável Atômica
Declaração da Variável de Controle
static atomic_t recurso_disponivel = ATOMIC_INIT(1);
Função de Abertura do Driver
static int meu_driver_open(struct inode *inode, struct file *filp)
{
if (!atomic_dec_and_test(&recurso_disponivel)) {
atomic_inc(&recurso_disponivel);
return -EBUSY;
}
/* código de inicialização do hardware */
return 0;
}
A lógica funciona da seguinte forma: na primeira chamada, o valor atômico é decrementado de 1 para 0, e atomic_dec_and_test retorna verdadeiro, permitindo a abertura. Em chamadas subsequentes, o valor já é zero ou negativo, a verificação falha, o valor é restaurado e o código retorna -EBUSY.
Função de Fechamento do Driver
static int meu_driver_release(struct inode *inode, struct file *filp)
{
atomic_inc(&recurso_disponivel);
/* código de liberação de recursos */
return 0;
}
Ao fechar o dispositivo, a variável atômica é incrementada novamente, sinalizando que o recurso está livre para uso.
Comportamento Observado
Com essa implementação, a primeira abertura ocorre normalmente. Qualquer tentativa subsequente de abertura recebe imediatamente o retorno -EBUSY, indicando que o dispositivo está ocupado. Trata-se de uma abordagem não bloqueante, onde o processo não aguarda.
Abordagem com Semáforo (Mutex)
Inicialização do Semáforo
static DEFINE_SEMAPHORE(semaforo_leds, 1);
A macro DEFINE_SEMAPHORE declara e inicializa o semáforo com valor 1, funcionando como um mutex.
Função de Abertura do Driver
static int meu_driver_open(struct inode *inode, struct file *filp)
{
if (down_interruptible(&semaforo_leds))
return -ERESTARTSYS;
/* código de inicialização do hardware */
return 0;
}
A função down_interruptible tenta adquirir o semáforo. Caso o recurso já esteja ocupado, o processo é colocado em estado de suspensão interrompível até que o semáforo seja liberado. Se o processo receber um sinal durante a espera, a operação retorna -ERESTARTSYS.
Função de Fechamento do Driver
static int meu_driver_release(struct inode *inode, struct file *filp)
{
/* código de liberação de recursos */
up(&semaforo_leds);
return 0;
}
Comportamento Observado
Quando um segundo processo tenta abrir o dispositivo, ele entra em suspensão aguardando a liberação do semáforo. O processo permanece dormindo até que o primeiro processo feche o dispositivo e invoque up(). Essa é uma abordagem bloqueante.
Diferenças entre as Abordagens
| Característica | Variável Atômica | Semáforo |
|---|---|---|
| Comportamento ao conflito | Não bloqueante — retorna erro imediatamente | Bloqueante — processo fica suspenso aguradando |
| Uso de CPU durante espera | Nanhum | Processo dorme, liberando a CPU |
| Complexidade | Simples | Moderada |
| Adequação | Situações onde o erro imediato é aceitável | Situações onde o processo deve aguardar o recurso |
Para obter comportamento não bloqueante com semáforos, utilize down_trylock() em substituição a down_interruptible(). Essa função retorna imediatamente com valor diferente de zero caso o semáforo não esteja disponível, sem colocar o processo em suspensão.
Estados de Processo no Linux
Ao monitorar processos com o comando ps, os estados relevantes são:
- S — Suspensão interrompível (aguardando evento, pode ser interrompido por sinais)
- D — Suspensão não interrompível (aguardando E/S de hardware, não responde a sinais)