Exclusão Mútua em Drivers Linux para Acesso Exclusivo de Processos

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)

Tags: Linux kernel driver atomic semaphore

Publicado em 7-3 23:17