Começa aqui a série que pretendo fazer sobre o desenvolvimento de device drivers para o kernel linux.
Esse texto é um conteúdo bem introdutório sobre o assunto, porém é esperado que você tenha conhecimento em linguagem C.
Aqui está o link para o repositório com os códigos contidos nessa série.
O linux é conhecido por ser um Kernel monolito. Porém, na verdade, podemos afirmar que ele é monolito e ao mesmo tempo modular. Porque temos a possibilidade de "injetar" códigos no kernel, sem precisar recompilar o kernel toda vez que fizermos uma mudança.
Existe um pacote chamado linux-headers que contém alguns utilitários para injetar um código no kernel sem que ele esteja na árvore do código.
Para instalar o linux-headers:
sudo apt install linux-headers
Caso esteja em um raspberry também ira precisar desse pacote:
sudo apt install raspberrypi-kernel-headers
Para fins de estudo, também recomendo clonar o repositório do linux
git clone https://github.com/torvalds/linux
Para começar essa BREVÍSSIMA introdução de como escrevermos módulos de drivers pra kernel linux, vou começar com o famigerado hello world. E nem será um driver em si , será somente um arquivo que vamos injetar no kernel e printar a mensagem "hello world" nos logs do kernel.
Então aqui esta nosso código do nosso hello_world.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_AUTHOR("SEU NOME <seu_email@email.com>");
MODULE_DESCRIPTION("Introducao a criacao de driver pra linux");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
static int __init hello_world_init(void)
{
pr_info("Hello World\n");
return 0; /* successo */
}
static void __exit hello_world_exit(void)
{
pr_info("Adeus mundo cruel\n");
}
module_init(hello_world_init);
module_exit(hello_world_exit);
Vamos a explicação desse pequeno trecho de código:
Começando pelos imports, esses 3 primeiros imports são necessarios para um modulo linux.
O primeiro <linux/init.h>
fica no caminho include/linux/init.h
da árvore de código do kernel, e ela é responsável por nos dar a possibilidade de usarmos a macro __init
nas funções de inicialização do modulo bem como a macro __exit
nas funções de destruição do modulo.
O import <linux/module.h>
fica no caminho include/linux/module.h
, nos da as implementações das funções module_init()
e também module_exit()
para informarmos ao kernel, quais funções são responsáveis pela criação e destruição do modulo. Também, esse cabeçalho contém as funções MODULE_AUTHOR
, MODULE_DESCRIPTION
, MODULE_LICENSE
e MODULE_VERSION
.
O cabeçalho <linux/kernel.h>
que fica no caminho include/linux/kernel.h
é um tanto quanto confuso, porque ele contém algumas coisas que não tem relação entre si, e no momento de escrita desse texto, a divisão dessas coisas ja esta em andamento, e o kernel.h é mantido para garantir a compatibilidade com os módulos que estão usando esse header. Aqui vamos usar esse cabeçalho única e exclusivamente porque eu, infelizmente, ainda não tenho conhecimento dos arquivos que estão recebendo o split do kernel.h nesse momento.
Quando inserirmos esse modulo no kernel a função hello_world_init
sera invocada, e quando removermos o modulo do kernel a função hello_world_exit
será invocada. Isso é uma regra de todo módulo linux.
A função pr_info()
serve para registrar uma mensagem nos logs do kernel. Você pode ver essas mensagens rodando o comando sudo dmesg
.
No desenvolvimento do kernel, não temos acesso as bibliotecas que normalmente temos quando desenvolvemos um sistema em C no espaço de usuário. No nível do kernel, você deve usar as libs do próprio kernel, ou fazer suas próprias libs. Você com certeza está habituado com a lib <stdio.h>
, ela permite que usemos a função printf()
e scanf()
, mas como mencionado, ela não está disponível para nós aqui. Então, temos a implementação de algo muito parecido no kernel: As funções pr_info()
, pr_err()
, pr_emerg()
, pr_alert()
, pr_crit()
, pr_warning()
, pr_devel()
e pr_notice()
. Cada uma dessas funções vai printar no console do kernel com alguma cor diferente e uma etiqueta, diferente. Aqui está um exemplo de um pr_emerg:
[ 257.712077] Hello world @ log-level KERN_EMERG [0]
Sem mais delongas, para injetar esse codigo ao kernel, primeiro vamos compilar esse arquivo:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
.
Após isso, será gerado o binario chamado hello_world.ko e podemos inserir ele no kernel: sudo dmesg -C && sudo insmod hellow_world.ko
.
Pronto, inserimos um codigo ao kernel, sem que necessariamente ele estivesse na arvore de codigo do kernel.
Claro que no futuro, caso desejarmos que esse módulo faça parte da arvore, podemos simplesmente mover ele para dentro da arvore, compilar o kernel e instalar ele na maquina. Mas isso vai ficar para um outro texto.
Agora se rodarmos o comando sudo dmesg
poderemos ver o log do kernel, com a mensagem "Hello world".
Pronto, fizemos nosso primeiro modulo para o kernel do Linux. Agora, para removermos esse codigo do kernel, podemos rodar o comando sudo rmmod hello_world
e quando checarmos os logs com sudo dmesg
podemos ver a mensagem "Adeus mundo cruel".
Agora, somente para efeitos de facilidade, vamos criar um Makefile para esse cara, e automatizar nossa compilação do modulo:
obj+= hello_world.o
all: run
run:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Agora quando formos compilar, basta rodar o comando make.
Revisão
- Aprendemos que o Kernel Linux pode ser modular, apesar de ser conhecido por ser um kernel monólito.
- Aprendemos como escrever um código extremamente simples e inserir ele no kernel sem que ele esteja realmente na árvore de código do kernel.
- Aprendemos que no espaço do kernel não existe bibliotecas como
stdio.h
e precisamos usar as bibliotecas do próprio kernel ou implementar nossas próprias bibliotecas. - Aprendemos como visualizar os logs do kernel com o comando
sudo dmesg
.
Top comments (0)