Mudanças entre as edições de "LKMPG"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
Linha 3: Linha 3:
 
= Tutoriais sobre programação de módulos do kernel =
 
= Tutoriais sobre programação de módulos do kernel =
  
* [http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/]
+
* [https://gitlab.com/bashrc2/LKMPG Linux Kernel Module Programming Guide (um clássico atualizado)]
* [https://gitlab.com/bashrc2/LKMPG Linux Kernel Module rogramming Guide (um clássico atualizado)]
 
 
* [https://medium.com/@navaneethrvce/learning-linux-kernel-module-programming-4f11dbec44f1 Um guia mais leve]
 
* [https://medium.com/@navaneethrvce/learning-linux-kernel-module-programming-4f11dbec44f1 Um guia mais leve]
  
Linha 15: Linha 14:
  
 
Vocês podem iniciar o estudo usando o próprio kernel do sistema operacional que está em seus laptops. Se for Ubuntu ou Debian, instalem o pacote ''linux-headers'' para poderem criarem seus módulos.
 
Vocês podem iniciar o estudo usando o próprio kernel do sistema operacional que está em seus laptops. Se for Ubuntu ou Debian, instalem o pacote ''linux-headers'' para poderem criarem seus módulos.
 +
 +
== Preparando o ambiente de desenvolvimento do kernel ==
 +
 +
Para realizar este estudo sobre o desenvolvimento de software no kernel Linux, é necessário preparar um ambiente para desenvolvimento do kernel. Há duas opções:
 +
# Obter o código-fonte de uma versão do kernel, descompactá-lo, configurar as opções de compilação do kernel e então compilá-lo. O kernel obtido deve ser instalado de forma que seu computador (ou VM) possa executá-lo no boot. Com isso, podem-se escrever módulos do kernel e compilá-los usando esse código-fonte.
 +
# No caso de sistemas Linux baseados no Debian ou Ubuntu, deve-se obter o pacote de sofwtare ''linux-headers'' para a versão do kernel instalada em seu computador ou VM ([http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/ Writing a Linux Kernel Module - um guia simplificado]):
 +
## Verifique a versão do seu kernel: <syntaxhighlight lang=bash>
 +
uname -r
 +
</syntaxhighlight>
 +
## Obtenha o pacote ''linux-headers'' para sua versão: <syntaxhighlight lang=bash>
 +
sudo apt install linux-headers-$(uname -r)
 +
</syntaxhighlight>
 +
 +
A primeira opção (obter o código-fonte do kernel) é mais complexa e trabalhosa. Porém ela possibilita um grande aprendizado a respeito da estrutura do kernel, suas funcionalidades e características selecionáveis por meio de opções de compilação, e o processo de compilação e instalação. Acho esta experiência de grande valia, mas fica a seu critério ir direto para a segunda opção, que é bem mais simples. Talvez seja mais produtivo iniciar pela opção 2, e depois, quando se sentir mais familizarizada, testar a opção 1. Os guias a seguir ajudam a entender o processo de compilação do kernel (dica: tente a versão do kernel 3.10.49, que é usada nos equipamentos da Intelbras):
 +
* [https://www.linux.com/tutorials/how-compile-linux-kernel-0/ How to Compile a Linux Kernel]
 +
* [https://www.freecodecamp.org/news/building-and-installing-the-latest-linux-kernel-from-source-6d8df5345980/ How to build and install the latest Linux kernel from source]
 +
* [https://www.kernel.org/doc/html/latest/ The Linux Kernel documentation (oficial)]
 +
 +
Uma vez tendo-se o ambiente de compilação, veja o exemplo para criar o [https://learning.oreilly.com/library/view/linux-device-drivers/0596005903/ch02.html#linuxdrive3-CHP-2-SECT-2 módulo ''hello world''].
 +
 +
= Roteiro de módulos e device drivers =
 +
 +
As etapas a seguir sugerem pequenos desafios para guiar seu estudo sobre módulos do kernel e device drivers.
  
 
== Etapa 1 ==
 
== Etapa 1 ==
  
Como primeiro módulo, experimentem criar um módulo que implemente um device driver para dispositivo orientado a caractere (ex: serial), que é o tipo mais simples de driver. Os livros inclusive usam como primeiro exemplo um driver desse tipo.
+
Como primeiro módulo, experimente criar um módulo que implemente um device driver para dispositivo orientado a caractere (ex: serial), que é o tipo mais simples de driver. Os livros inclusive usam como primeiro exemplo um driver desse tipo. Iincie com o dispositivo scull, conforme descrito no capítulo 3 do livro Linux Device Drivers:
* [[Arquivo:Lkm.zip]]: exemplo de módulo apresentado nas partes 1 e 2 do livro ''Linux Device Drivers''.
+
* [https://learning.oreilly.com/library/view/linux-device-drivers/0596005903/ch03.html Char Drivers (dispositivos orientados a caractere)]
 +
 
 +
Reproduza os passos mostrados no livro, obtendo ao final um device driver na forma de um módulo carregável.
  
 
== Etapa 2 ==
 
== Etapa 2 ==
  
Nesse novo exercício, o dispositivo virtual da etapa 1 guarda os dados nele escritos, e possibilita que sejam lidos por processos que o abram e façam chamadas ''read''. Note o seguinte:
+
Na segunda etapa, o dispositivo virtual criado na etapa 1 deve se tornar capaz de guardar os dados nele escritos, e possibilitar que sejam lidos por processos que o abram e façam chamadas ''read''. Note o seguinte:
  
 
# A capacidade de armazenamento do dispositivo deve ser limitada .... se ele ficar cheio, uma nova tentativa de escrita deve falhar com algum código de erro (ex: EAGAIN).
 
# A capacidade de armazenamento do dispositivo deve ser limitada .... se ele ficar cheio, uma nova tentativa de escrita deve falhar com algum código de erro (ex: EAGAIN).
Linha 68: Linha 92:
 
== Etapa 5 ==
 
== Etapa 5 ==
  
O próximo passo envolve entender a temporização de tarefas dentro do kernel. O kernel fornece facilidades para medição de tempo decorrido (jiffies), espera ocupada ou bloqueada (timeout), execução de uma ação em momento específico (timer), e execução de ação em momento oportuno (tasklet e workqueue). Cada mecanismo tem sua aplicação e suas limitações, como se pode ler no [ttps://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch07.html capítulo 7 do livro ''Linux Device Drivers'']:
+
O próximo passo envolve entender a temporização de tarefas dentro do kernel. O kernel fornece facilidades para medição de tempo decorrido (jiffies), espera ocupada ou bloqueada (timeout), execução de uma ação em momento específico (timer), e execução de ação em momento oportuno (tasklet e workqueue). Cada mecanismo tem sua aplicação e suas limitações, como se pode ler no [https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch07.html capítulo 7 do livro ''Linux Device Drivers'']:
  
Entendo-se os mecanismos de temporização, a próxima tarefa envolve modificar o seu device driver para que as informações gravadas na fila tenham um certo tempo de vida ... Cada conjunto de bytes gravados na fila deve lá permanecer por, no máximo, algum tempo predefinido (ex: 5 segundos). Se esse tempo passar e aqueles bytes ainda estiverem na fila, eles devem ser retirados e descartados. Notem que o controle de tempo decorrido deve ser feito
+
Tendo entendido os mecanismos de temporização, a próxima tarefa envolve modificar o seu device driver para que as informações gravadas na fila tenham um certo tempo de vida ... Cada conjunto de bytes gravados na fila deve lá permanecer por, no máximo, algum tempo predefinido (ex: 5 segundos). Se esse tempo passar e aqueles bytes ainda estiverem na fila, eles devem ser retirados e descartados. Notem que o controle de tempo decorrido deve ser feito com base no instante em que os dados entraram na fila: se um processo gravar 4 bytes no dispositivo no instante 0, depois 7 bytes no instante 2, e finalmente 3 bytes no instante 4, os primeiros 4 bytes devem ser descartados no instante 5, os 7 bytes no instante 7, e os 2 bytes finais no instante 9 (caso não tenham sido lidos do dispositivo).
com base no instante em que os dados entraram na fila: se um processo gravar 4 bytes no dispositivo no instante 0, depois 7 bytes no instante 2, e finalmente 3 bytes no instante 4, os primeiros 4 bytes devem ser descartados no instante 5, os 7 bytes no instante 7, e os 2 bytes finais no instante 9 (caso não tenham sido lidos do dispositivo).
 

Edição das 14h27min de 19 de dezembro de 2019

Uma rápida compilação de materiais sobre o kernel Linux e seus device drivers.

Tutoriais sobre programação de módulos do kernel

Introdução

Para começarem a entender como desenvolver módulos e drivers para o kernel Linux, podem se basear neste livro:

Ele não é recente, e se baseia no kernel 2.6.x, mas muito do que ali está ainda é válido.

Vocês podem iniciar o estudo usando o próprio kernel do sistema operacional que está em seus laptops. Se for Ubuntu ou Debian, instalem o pacote linux-headers para poderem criarem seus módulos.

Preparando o ambiente de desenvolvimento do kernel

Para realizar este estudo sobre o desenvolvimento de software no kernel Linux, é necessário preparar um ambiente para desenvolvimento do kernel. Há duas opções:

  1. Obter o código-fonte de uma versão do kernel, descompactá-lo, configurar as opções de compilação do kernel e então compilá-lo. O kernel obtido deve ser instalado de forma que seu computador (ou VM) possa executá-lo no boot. Com isso, podem-se escrever módulos do kernel e compilá-los usando esse código-fonte.
  2. No caso de sistemas Linux baseados no Debian ou Ubuntu, deve-se obter o pacote de sofwtare linux-headers para a versão do kernel instalada em seu computador ou VM (Writing a Linux Kernel Module - um guia simplificado):
    1. Verifique a versão do seu kernel:
      uname -r
      
    2. Obtenha o pacote linux-headers para sua versão:
      sudo apt install linux-headers-$(uname -r)
      

A primeira opção (obter o código-fonte do kernel) é mais complexa e trabalhosa. Porém ela possibilita um grande aprendizado a respeito da estrutura do kernel, suas funcionalidades e características selecionáveis por meio de opções de compilação, e o processo de compilação e instalação. Acho esta experiência de grande valia, mas fica a seu critério ir direto para a segunda opção, que é bem mais simples. Talvez seja mais produtivo iniciar pela opção 2, e depois, quando se sentir mais familizarizada, testar a opção 1. Os guias a seguir ajudam a entender o processo de compilação do kernel (dica: tente a versão do kernel 3.10.49, que é usada nos equipamentos da Intelbras):

Uma vez tendo-se o ambiente de compilação, veja o exemplo para criar o módulo hello world.

Roteiro de módulos e device drivers

As etapas a seguir sugerem pequenos desafios para guiar seu estudo sobre módulos do kernel e device drivers.

Etapa 1

Como primeiro módulo, experimente criar um módulo que implemente um device driver para dispositivo orientado a caractere (ex: serial), que é o tipo mais simples de driver. Os livros inclusive usam como primeiro exemplo um driver desse tipo. Iincie com o dispositivo scull, conforme descrito no capítulo 3 do livro Linux Device Drivers:

Reproduza os passos mostrados no livro, obtendo ao final um device driver na forma de um módulo carregável.

Etapa 2

Na segunda etapa, o dispositivo virtual criado na etapa 1 deve se tornar capaz de guardar os dados nele escritos, e possibilitar que sejam lidos por processos que o abram e façam chamadas read. Note o seguinte:

  1. A capacidade de armazenamento do dispositivo deve ser limitada .... se ele ficar cheio, uma nova tentativa de escrita deve falhar com algum código de erro (ex: EAGAIN).
  2. Quando um processo lê do dispositivo, a quantidade de bytes lidos deve ser retirada da memória.
  3. Cada novo read deve iniciar a partir da posição onde o read anterior terminou
  4. Se não houver dados para ler, read deve falhar com algum erro (ex: EAGAIN)

Note que, pela descrição acima, a memória do dispositivo deve funcionar como uma fila circular.

OBS: Códigos de erro estão em /usr/include/asm-generic/errno-base.h

Etapa 3

Nesta nova versão, o dispositivo feito na etapa 2 deve ter estas extensões:

  1. Se uma escrita for realizada e a memória estiver cheia, então o processo que tentou a escrita deve ser bloqueado. Quando houver espaço na memória, o processo deve ser desbloqueado para terminar a operação de escrita.
  2. Mesma coisa quando se tentar ler e a memória do dispositivo estiver vazia


A cópia de valores entre buffers do driver e do processo que o acessa deve levar em conta que:

  • o buffer do driver está em espaço de kernel, que usa endereçamento real
  • o buffer do processo está em espaço de usuário, que usa endereçamento virtual

Por isso a cópia não pode ser feita diretamente com memcpy, strncpy, ou funções similares. A cópia precisa ser feita com funções da API do kernel, as quais traduzem os endereços virtuais do processo para reais (para isso elas usam a tabela de páginas do processo em questão). As funções são:


Existem outras, que podem ser vistas nestas documentações:

Dica: usar kfifo seria uma boa. No livro Linux Kernel Development tem uma seção que explica como funciona kfifo, e como se pode usá-la - ver Queues no capítulo 6.

Etapa 4

A próxima capacidade do driver é torná-lo capaz de bloquear um processo que tente ler o dispositivo, mas não existam dados para ler. E o mesmo deve ocorrer se um processo tentar escrever no dispositivo, mas ele estiver cheio. Lembre que a ideia é que o dispositivo represente um named pipe, o que implica haver uma fila.

Este tutorial explica de forma bastante clara como fazer com que processos esperem por alguma condição dentro de um driver:

https://sysplay.in/blog/linux-kernel-internals/2015/10/waiting-blocking-in-linux-driver/

Etapa 5

O próximo passo envolve entender a temporização de tarefas dentro do kernel. O kernel fornece facilidades para medição de tempo decorrido (jiffies), espera ocupada ou bloqueada (timeout), execução de uma ação em momento específico (timer), e execução de ação em momento oportuno (tasklet e workqueue). Cada mecanismo tem sua aplicação e suas limitações, como se pode ler no capítulo 7 do livro Linux Device Drivers:

Tendo entendido os mecanismos de temporização, a próxima tarefa envolve modificar o seu device driver para que as informações gravadas na fila tenham um certo tempo de vida ... Cada conjunto de bytes gravados na fila deve lá permanecer por, no máximo, algum tempo predefinido (ex: 5 segundos). Se esse tempo passar e aqueles bytes ainda estiverem na fila, eles devem ser retirados e descartados. Notem que o controle de tempo decorrido deve ser feito com base no instante em que os dados entraram na fila: se um processo gravar 4 bytes no dispositivo no instante 0, depois 7 bytes no instante 2, e finalmente 3 bytes no instante 4, os primeiros 4 bytes devem ser descartados no instante 5, os 7 bytes no instante 7, e os 2 bytes finais no instante 9 (caso não tenham sido lidos do dispositivo).