Instituto de Computação - UNICAMP

MC504 - Sistemas Operacionais

Implementação de (pseudo) device drivers

Islene Calciolari Garcia

Introdução

Antes de implementar os nossos drivers, vamos testar os exemplos disponibilizados na série Linux Device Drivers do site Open Source forU. No arquivo ofd.tar.xz você encontrará um diretório ofd com arquivos retirados do site citado. As aplicações de teste estão em apps.tar.xz

Ambiente 1: QEMU, imagem e kernel compilado externamente

Veja configuração inicial em Ambiente de testes: QEMU. Você deve colocar sua implementação no subdiretório drivers e fazer o build completo.

Ambiente 2: Qualquer máquina em que você possa ser um superusuário

Neste caso, você irá compilar os drivers e depois incorporá-los dinamicamente com o comando insmod ou modprobe. Utilize, preferencialmente, uma partição dedicada só para isso ou uma máquina virtual. Caso contrário, sua implementação pode corromper o kernel.


Outros exemplos

O código para os exemplos está disponível em ofd.tar.xz. Siga passos semelhantes aos já explicados anteriormente e para rodá-los no ambiente escolhido.

ofcd: primeiro driver de caracteres

Os drivers são identificados por números major e um minor. O driver ofcd requisita dinamicamente um número major para o sistema e reserva três números do tipo minor. Você pode conferir isso com os comandos:
  $ insmod ofcd.ko
  $ lsmod
   ...
  $ dmesg | tail
  $ cat /proc/devices
   ...
   252 ofcd
É possível criar as entradas em /dev explicitamente:
  $ mknod /dev/ofcd0 c 252 0
  $ mknod /dev/ofcd1 c 252 1
  $ mknod /dev/ofcd2 c 252 2
  $ ls /dev
No entanto, ainda não conseguimos utilizar estes arquivos.
  $ cat /dev/ofcd0

ofcd-null

O arquivo ofcd-null.c tem o código que permitirá operações de leitura e escrita, com a instalação automática do arquivo em /dev.
  $ insmod ofcd-null.ko
  $ ls /dev/
  $ cat /dev/ofcd-null
  $ echo "teste" > /dev/ofcd-null

ofcd-lastchar-bug

Em Decoding Character Device File Operations, o autor ensina como manter o último caractere escrito. Na parte 6 da série, Shweta tenta armazenar o último caractere da seguinte maneira:
static char c;

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Driver: read()\n");
    buf[0] = c;
    return 1;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Driver: write()\n");
    c = buf[len - 1];
    return len;
}
Esta abordagem está codificada no arquivo ofcd-lastchar-bug.c. Você pode testar com a aplicação bug-app.

ofcd-lastchar: copy_to_user e copy_from_user

Ainda na parte 6 da série, Shweta descobre as funções copy_to_user e copy_from_user.
static char c;

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Driver: read()\n");
    if (copy_to_user(buf, &c, 1) != 0)
        return -EFAULT;
    else
        return 1;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off)
{
    printk(KERN_INFO "Driver: write()\n");
    if (copy_from_user(&c, buf + len - 1, 1) != 0)
        return -EFAULT;
    else
        return len;
}

Refaça os testes com a aplicação lastchar-app.

Se você procurar a documentação sobre copy_to_user e copy_from_user verá que estas funções podem dormir. Por quê?

Exercício simples (não é sugestão de tema para projeto): tente implementar uma versão deste driver que armazene a última escrita com kmalloc e kfree.

query_ioctl

Na parte 9 da série, o autor ilustra o uso da função ioctl (i/o control) para controle dos dispositivos. O driver query_iocl tem três variáveis (status, dignity, ego) que são controladas a partir de chamadas da aplicação query_app.
./query_app      to display the driver variables
./query_app -c   to clear the driver variables
./query_app -g   to display the driver variables
./query_app -s   to set the driver variables
Você deve rodar este exemplo e entender o funcionamento de ioctl. Como posso alterar o tipo de argumento passado para ioctl? Esta função permite grande flexibilidade..

No capítulo 4 do Kernel Hacking: ioctls: Not writing a new system call podemos encontrar a seguinte informação:

A system call generally looks like this

asmlinkage long sys_mycall(int arg)
{
        return 0;
}
First, in most cases you don't want to create a new system call. You create a character device and implement an appropriate ioctl for it. This is much more flexible than system calls, doesn't have to be entered in every architecture's include/asm/unistd.h and arch/kernel/entry.S file, and is much more likely to be accepted by Linus.

No entanto, nem todos amam este design. Você consegue dizer algumas desvantagens?