Instituto de Computação - UNICAMP

MC504/MC514 - Sistemas Operacionais

Implementação de um Pseudo-Device Driver

Islene Calciolari Garcia

Ambiente de testes

Este exercício será feito de maneira semelhante ao experimento com chamadas de sistema. Se você já está com tudo configurado, passe para a etapa de testes com os drivers.

Teste com o kernel 3.17.2

Atenção: como estes arquivos são muito grandes, deveremos fazer nossos testes no /tmp das máquinas do laboratório.

Antes de fazer o teste, para não ter problemas com a quota ou de desempenho, configure o diretório CCACHE utilizando o comando:

$ export CCACHE_DIR="/tmp/.ccache"

Copiando um kernel já compilado

Esta estratégia não funciona sempre!:-( Em muitos casos vai recompilar novamente! :-( :-(
Devido ao tempo que a primeira compilação estava levando para alguns alunos, deixei um kernel já compilado em mc504-linux-compilado.tar.xz. Se estivermos na rede do IC-03 podemos executar o seguinte comando:
$  cp /home/staff/islene/public_html/mc504-linux-compilado.tar.xz .
  • Após obtermos o arquivo, devemos executar:
    $ tar xJpvf mc504-linux-compilado.tar.xz
    
    Atenção: a opção p é muito importante para preservar as marcas de tempo e evitar as recompilações. Surgirá um diretório contendo a versão 3.17.2 do kernel Linux compilada com o arquivo de configuração config-3.17.2 e uma imagem mc504.img que foi criada por Glauber de Oliveira Costa para a turma do 1s2008 de sistemas operacionais.

  • Teste o kernel com a imagem utilizando o QEMU:
      qemu-system-i386 -hda mc504.img -kernel linux-3.17.2/arch/i386/boot/bzImage -append "ro root=/dev/hda" 
    

  • Quando o sistema entrar poderemos fazer login com
        usuário: root
        senha:   root
      
    Problemas com o teclado no ambiente do QEMU? Tente o seguinte:

    Teste de drivers

    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 e um Makefile simplificado. As aplicações de teste estão em apps.tar.xz

    Devemos mover o diretório ofd para linux-3.17.2/drivers e alterar o Makefile do diretório drivers acrescentando a linha

    obj-y += ofd/
    
    A seguir, você deverá deverá executar make no diretório linux-3.17.2 que é a raiz dos fontes do kernel.
     $ make -j 5 ARCH=i386
    

    Precisaremos incluir os arquivos .ko no ambiente do QEMU. Para isso, criaremos um novo sistema de arquivos ext2 com os seguintes comandos:

     $ dd if=/dev/zero of=drivers.img bs=4k count=20
     $ mkfs.ext2 drivers.img
    
    Depois, vamos copiar os arquivos .ko na imagem drivers.img com o debugfs.
     $ debugfs -w drivers.img
       debugfs: write linux-3.17.2/drivers/ofd/ofd.ko ofd.ko
       debugfs: write linux-3.17.2/drivers/ofd/ofcd.ko ofcd.ko
       debugfs: write linux-3.17.2/drivers/ofd/ofcd-null.ko ofcd-null.ko
    
    No próximo teste com o QEMU, teremos de acrescentar esta imagem como um novo disco:
      qemu-system-i386 -hda mc504.img -kernel linux-3.14.4/arch/i386/boot/bzImage -append "ro root=/dev/hda" -hdb drivers.img
    
    No QEMU, a imagem drivers.img deverá ser montada com o seguinte comando:
    $ mkdir drivers
    $ mount -t ext2 /dev/hdb drivers/
    

    ofd: primeiro driver

    Neste teste, o driver tem apenas as função init e exit. Vocé pode testar se o driver foi incluído dinamicamente. No QEMU, execute os comandos
      $ insmod drivers/ofd.ko
      $ lsmod
       ...
      $ rmmod drivers/ofd.ko
      $ dmesg | grep ofd
    

    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 minors. Você pode conferir isso com os comandos:
      $ insmod drivers/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á estas operações, com a instalação automática do arquivo em /dev.
      $ insmod drivers/ofcd-null.ko
      $ ls /dev/
      $ cat /dev/ofcd-null
      $ echo "teste" > /dev/ofcd-null
    

    Fazendo alterações

    Em Decoding Character Device File Operations, o autor ensina como manter o último caractere escrito. Como você faria para manter o buffer completo da última escrita? Note que ao alterar um dos drivers, vocé deve reexecutar make no diretório linux-3.17.2.

    Primeira tentativa com crash

    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 as aplicações teste-lastchar e teste-lastchar-invalid-buffer.

    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 teste-lastchar e teste-lastchar-invalid-buffer com o driver ofcd-lastchar.

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

    Sugestão: tente implementar uma versão deste driver que armazene a última escrita com kmalloc e kfree.

    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?