Hello world linux kernel module
Tags: linux, lkm, drivers
Published: 2023-09-13 20:06:49


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](https://github.com/GuilhermeGiacomoSimoes/introducao_dev_driver_linux) 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`.