Administração de Sistemas e Redes
Sistemas de Operação II

http://www.ncc.up.pt/~rslopes

Kernel Modules

Linux Device Drivers

Cap. 1

O que é um
“Kernel Module”

Módulos são blocos de código (.o ou .ko) que podem ser carregados para o Kernel do sistema para estender a sua funcionalidade sem ser necessário fazer um “reboot”.

Podemos consultar quais são os módulos que estão a ser usados pelo sistema com o comando lsmod ou com cat /proc/modules

Exemplos:

Filesystems: ext2, vfat, ntfs,…

Block devices: disco duro, drive disquetes, …

Char devices: placa de som, …

Protocolos: smb, ipx, …

Quais as vantagens de usar
“Kernel Modules” ?

Fica mais simples desenvolver novas funcionalidades para o kernel:

evita-se recompilar o kernel

não é necessário fazer reboot

a instalação/remoção é simples

modprobe nome_do_modulo ou insmod nome_do_modulo.ko

rmmod nome_do_modulo

O Kernel fica mais pequeno

Em /etc/modules e /etc/modules.conf podemos especificar quais os módulos que devem ser carregados durante o arranque do sistema.

Cuidados a ter ao usar
“Kernel Modules” ?

O módulos correm em KernelSpace e como tal o código corre em modo Supervisor (ao contrário dos programas que correm em modo User).

Só devemos carregar para o nosso sistema módulos de confiança…

Só podemos usar as funções definidas no Kernel.

Por exemplo, as função da “stdlib” não se encontram disponíveis, por isso, o printf não pode ser usado.

Podemos consultar o ficheiro /proc/ksyms para saber quais os símbolos exportados pelo kernel.

Podemos saber quais são as system_call’s que um programa faz usando o comando strace.

As funções e variáveis globais devem ser declaradas como static para evitar possíveis colisões com outros módulos/código do Kernel.

Devemos usar apenas os módulos que foram compilados com a versão do Kernel que o sistema está a correr.

O meu primeiro
Módulo para o Kernel

Para criar um módulo precisamos de ter no mínimo duas funções:

iniciar o módulo definida com __init

parar o módulo definida com __exit

Os módulos precisam de ser compilados com certos símbolos definidos:

criar uma Makefile com a entrada:
obj-m := modulo.o

compilar o modulo usando:
make -C /usr/src/linux… SUBDIRS=$PWD modules

Para imprimir mensagens deve usar a função printk. As mensagens podem ser consultadas usando a função dmesg ou consultando /var/log/messages.

VER EXEMPLO 1

MODULE_LICENSE

MODULE_LICENSE( tipo );

"GPL"

"GPL v2"

"GPL and additional rights"

"Dual BSD/GPL"

"Dual MPL/GPL"

"Proprietary"

Como passar parâmetros
para um Módulo

Para podermos passar parâmetros para um módulo devemos:

Declarar variáveis globais para receber os argumentos.

Usar a macro module_param para fazer a atribuição dos valores passados como argumentos para as variáveis.

A macro module_param recebe três argumentos:

o nome da variável/parâmetro;

o seu tipo: byte, short, ushort, int, uint, long, ulong, charp, bool

e se vai usar o sysfs. Usar zero neste argumento.

Exemplo:

static int exemplo = 0;	module_param(exemplo, int, 0);

static char *texto=“inicial”; module_param(texto, charp, 0);

Inserir o módulo usando: insmod ./teste.ko exemplo=2 texto=sim

VER EXEMPLO 2

Device Drivers

Device Drivers pertencem ao grupo de módulos que permitem controlar o Hardware.

No Linux os processos comunicam com o hardware através de um entrada (ficheiro) existente no directório /dev.

Por exemplo, um computador com uma placa de som Ensoniq ES1370, usa o módulo es1370.o, que fica associado ao /dev/sound. Um processo como o xmms usa o /dev/sound sem precisar de saber os detalhes da placa de som que o computador tem.

Device Drivers

Os device drivers dividem-se por duas classes:

Character devices: (exemplo: porta série)

Não têm buffers intermediários.

Aceitam e retornam a informação byte-a-byte sem usar qualquer cache.

Block devices: (exemplo: uma controladora disco IDE)

Têm um buffer para satisfazer os pedidos.

Aceitam e retornam a informação em blocos de tamanho constante.

Device Driver
Major e Minor Numbers

Se fizermos #ls –l /dev/hda[1-3] obtemos:
brw-rw---- 1 root disk 3, 0 … /dev/hda
brw-rw---- 1 root disk 3, 64 … /dev/hdb
brw-rw---- 1 root disk 22, 0 … /dev/hdc

b indica que é um block device (teríamos c para um char device)

O primeiro número, major number (3 e 22), indica qual é o device driver que é usado para aceder ao hardware.

O segundo número, minor number (0 e 64), serve para o driver distinguir qual é a peça de hardware que controla.

Para criar um device driver no /dev usamos o comando mknod:
Exemplo: mknod /dev/hda1 b 3 1

Para saber quais os major numbers que já estão atribuídos, basta consultar o ficheiro: /usr/src/linux/Documentation/devices.txt

Criando um
Device Driver…

As aplicações usam o driver como interface para o hardware. O modo de acesso é semelhante ao acesso a um simples ficheiro.

Ao criar um device driver, é necessário criar funções para cada uma das operações definidas na estrutura file_operations em linux/fs.h

No entanto, um driver só precisa de definir aquelas funções que fazem sentido para o hardware em questão. Por exemplo: read / write.

struct file_operations I

llseek – serve para mudar a posição corrente de leitura/escrita.

read – serve para ler dados do device

write – serve para enviar dados para o device

readdir – É NULL para os devices. Serve para ler directórios (usado por filesystems).

poll - serve para determinar qual é o estado de um device.

ioctrl – serve para poder enviar comandos específicos do hardware. Por exemplo, eject num cdrom.

mmap – usado para pedir um mapeamento da memória do hardware para um endereço para que possa ser usado por um processo.

struct file_operations II

open – serve para inicializar o hardware.

flush – é usado quando um processo fecha a sua cópia do descritor do ficheiro do device.

release – é usado quando a estrutura é libertada (por exemplo usando a instrução close()).

fsync – serve para fazer flush dos dados pendentes

fasync – serve para informar o device que houve uma alteração na flag FASYNC.

lock – serve para implementar o bloqueio a ficheiros.

readv / writev - read and write vector

Registo de um device

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

Se passarmos zero como argumento para major, a função devolve um inteiro que representa um major number que foi atribuído dinamicamente ao device.

Se o inteiro devolvido for menor do que zero significa que o registo falhou.

O problema em usar o registo dinâmico, é que só ficamos a saber o major number depois de inicializar o driver, e como tal a entrada no /dev/ só pode ser criada depois.

Remover um device

int unregister_chrdev(unsigned int major, const char *name);

Um device não pode ser removido do kernel em qualquer altura, pois pode estar a ser usado por um processo.

Podemos consultar o ficheiro /proc/modules, e pelo terceiro campo saber se o driver está a ser usado.

Erros ao executar um módulo (p.e. segmentation fault) podem levar a que o driver não possa ser removido da memória (a não ser com um reboot).

Passando dados da memória de um processo para um device…

No Linux a memória está dividida em Kernel Space e User Space.

Um dado processo não consegue ler nem escrever de uma área de memória que não a sua.

Para passar informação do Kernel Space para o User Space é necessário usar as seguintes macros:

#include <asm/uaccess.h>

get_usr(void *x,const void *addr) /* user(addr)->kernel(x) */

copia sizeof(addr) bytes a partir do user space para x;

put_user(void *x,const void *addr) /* kernel(x)->user(addr) */
copia sizeof(addr) bytes de x para o user space;

copy_to_user(void *to, void *from, unsigned long size)

copy_from_user(void *to, void *from, unsigned long size)

VER EXEMPLO 3

2001/2002 Aplicações Distribuídas e Internet

Ricardo Lopes

2001-09-26T18:23:03