obj-m += ofd.o
obj-y += ofd/
$ make -j 5 ARCH=i386
$ qemu-system-i386 -hda ../ArchLinux_mc504.cow -kernel arch/x86/boot/bzImage -append "rw root=/dev/hda" -drive format=raw,file=drivers/ofd/ofd.ko
$ cat /dev/hdb > ofd.ko $ insmod ofd.ko $ lsmod ... $ rmmod ofd.ko $ dmesg | grep ofd
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.
# dnf install kernel-develPara instalar headers que combinam com o kernel instalado.
# dnf install "kernel-devel-uname-r == $(uname -r)"
CC = gcc CONFIG_MODULE_SIG=n ifeq ($(KERNELRELEASE),) KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) .PHONY: build clean build: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c else $(info Building with KERNELRELEASE = ${KERNELRELEASE}) obj-m := ofd.o endif
$ 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 /devNo entanto, ainda não conseguimos utilizar estes arquivos.
$ cat /dev/ofcd0
$ insmod ofcd-null.ko $ ls /dev/ $ cat /dev/ofcd-null $ echo "teste" > /dev/ofcd-null
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.
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_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 variablesVocê 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?