AVISO

AVISO: ESTE É MEU ANTIGO BLOG, QUE NÃO É MAIS ESCRITO DESDE 2011. O CONTEÚDO AQUI EXPRESSO PODE NÃO REPRESENTAR MEUS PENSAMENTOS E OPINIÕES DE IDADE ADULTA. PARA CONTEÚDOS NOVOS E RELEVANTES ACESSE BLOG.BRUNO.TODAY




quinta-feira, 23 de outubro de 2008

Desenvolvimento de Módulos Para o Kernel do Linux

Palavras-chave: Linux, C, Kernel

Introdução

A pedido de muitos fãs e leitores deste blog(muitos fãs e leitores = 2 pessoas, o Havacci e o Filipe), vou escrever um pouco sobre desenvolvimento de módulos para o Kernel do Linux aqui.

Neste primeiro artigo(se eu for escrever tudo num artigo só eu morro escrevendo) eu vou apenas começar a introduzir o assunto. Pretendo mostrar pro pessoal que não tem segredo nem macumba nisso, e o caminho para o pessoal começar esta "jornada ao centro do Linux". Futuramente colocarei diversos outros conteúdos.

Primeiramente vou responder a pergunta que certamente alguns farão: o que é possível desenvolver como módulo do kernel?? Pra que servem eles?? Vejamos uma lista das principais respostas a essas perguntas:
  • Device Drivers(o primeiro que vem na mente de todos);
  • Extensões aos diversos frameworks e subsystemas do kernel;
  • Desenvolvimento de novos modelos de segurança;
Falando de maneira introdutória, o Linux é um sistema operacional com um Kernel Monolítico, totalmente modularizado[1]. Um ambiente com Linux pode ser dividido em duas partes isoladas, ligadas por meio de alguns artifícios: o espaço do kernel(kernel space) e o espaço do usuário(user space).

A ligação entre as duas partes ocorre principalmente através de system calls. Outros instrumentos que fazem esta ligação são o sistema de arquivos virtual /proc, o sistema de arquivos virtual /sys, e os arquivos de devices(no /dev).

Não vou entrar em detalhes sobre os utilitários de módulo do kernel, ou mais conceitos de módulo. Para maiores informações leia neste link sobre o conceito de módulo e como utilizá-los.

Agora, a próxima sessão vai falar um pouco mais dos módulos mesmo, além de começar a comprovar que módulos para o kernel são códigos triviais como quaisquer outros, e não há nada que necessite ser um gênio da computação para que possa ser desenvolvido.

Estrutura Básica de Um Módulo




-->


Quando carregamos um módulo com insmod(ou modprobe), ele é inserido numa região de memória reservada para o kernel space. Logo quando descarregado, com rmmod, as referências a esta região de memória e seus conteúdos são retiradas. Mas isso você provavelmente já viu ou pode ver em mais detalhes no link que eu coloquei ali em cima.

Podemos dizer de maneira simples que um módulo é apenas um "programa"(não como os do user-space, mas computacionalmente falando não deixa de ser um programa, ou seja, um conjunto de instruções do processador) que possui um código a ser executado quando este é carregado, e outro a ser executado quando este é descarregado. De maneira bem "grosseira", para quem é acostumado com o user-space, podemos dizer que o módulo tem "dois mains": o de inicialização e o de finalização.

Normalmente, a idéia é que o primeiro deles, o init_module(), seja responsável pelo carregamento das estruturas de dados do módulo, bem como o registro de suas funcionalidades perante o kernel. Em outras palavras, você carrega o modulo e ele informa ao kernel o que ele faz e quando ele faz, para que o kernel passe a ele "a parte dele do serviço".

O "segundo main", cleanup_modules(), é a parte do modulo responsável pela sua descarga. Ela simplesmente "desfaz" o que o init_module() fez, desregistrando suas estruturas e desalocando eventuais regiões alocadas de memória.

Finalmente o Desenvolvimento dos Módulos

Primeiramente é importante saber algumas coisas antes da "aventura" começar. A mais obvia dela é que durante os testes com os módulos que você estiver escrevendo, caso haja algo de errado nos módulos, sua máquina pode ficar instável ou até travar(experimente criar um módulo que tenha um loop infinito dentro dele pra você ver o que acontece com o sistema).

Ao contrário de programas convencionais, o módulo do kernel não está e nem pode estar linkado a biblioteca nenhuma, de forma que você não poderá fazer uso nem mesmo da libc. As suas únicas bibliotecas são os frameworks do kernel.

Todo módulo do kernel deve ser desenvolvido em C. Até é possível desenvolver módulos em C++, Objective-C, Assembly, ou, inclusive, outras linguagens. Mas, infelizmente, a estrutura do kernel não foi feita para isso. Além de dar muito mais trabalho, pois desde a compilação e a linkedição, você já terá que criar suas próprias rotinas de compilação, ao invés de simplesmente reutilizar as existentes. Além do mais, os recursos do C++, por exemplo, são todos na sua maioria recursos de biblioteca. Você perderia já de cara a STL e o tratamento de excessões. Além disso, você teria que lidar com programação estruturada, pois as estruturas do kernel são estruturas estruturadas. E lá se vão as vantagens de se programar em C++ para o kernel, só restando o trabalho árduo para insistir nisso.

O uso da API do kernel é o único recurso com o qual trabalhamos nos módulos. Sendo assim, este link aponta para uma página contendo documentação de parte da API do kernel. Recomendo dar uma atenção aos capítulos 1 e 3 inicialmente. Lá temos, respectivamente, funções básicas do kernel e algumas funções de interface igual ou semelhante às principais da libc. Já o capítulo 2 fala de algumas estruturas de dados que são implementadas pelo kernel para serem utilizadas por módulos. Elas podem ser importantes em determinadas situações, de acordo com a necessidade do desenvolvedor(atualmente esta frase estaria no singular, pois só temos listas duplamente encadeadas lá).

No capítulo 5, temos a descrição dos recursos de gerenciamento de memória. Lembre-se, o gerenciamento de memória não serve apenas para a alocação dinâmica(kmalloc e kfree), mas também para acesso a buffers de memória do user-space, que se fazem necessários para trocar dados com este. Nos capítulos 9, 10 e 11, temos a manipulação dos principais sistemas de arquivos virtuais do kernel(procfs, sysfs e debugfs respectivamente).

Nos capítulos 23 e 24, temos respectivamente a parte de utilização de dispositivos de bloco e de caractere.

Eu espero ter explicado pelo menos por cima o que era o que, e o que serve pra que. Recomendo a quem não está familiarizado com o que eu falei, ler este artigo que fala um pouco sobre configuração e compilação do kernel. Ele está um tanto até desatualizado, mas serve como ponto de partida.

Como defendo muito a idéia do "aprenda com exemplos", não teria nada melhor do que um exemplo para mim colocar aqui. E como exemplo, utilizarei um código que eu mesmo desenvolvi para testes, como uma espécie de "brincadeira". Falando bem a verdade, não tenho vergonha de dizer que meu conhecimento acerca do desenvolvimento do kernel não passa de conhecimento teórico. Ainda não cheguei a precisar utilizar este conhecimento na minha vida profissional. Mas, mesmo assim, acho que ele é um complemento que faria falta para meu conhecimento de como o sistema operacional funciona.

O meu "brinquedo" é apenas uma espécie de "console de erros". Daí seu nome, 'errcon'. Ele simplesmente cria um dispositivo de caractere de major 254 e minor 0, no qual seriam escritas mensagens de erro. Na leitura do arquivo, ele mostra o ultimo erro. Sendo assim, vamos aos arquivos do meu "brinquedo".

errcon.h - o header:

/********************************************************************
* Parte responsavel pela leitura do erro...                         *  
* Autor: Bruno Moreira Guedes <bruno@bruno.inf.br>                  *
* Aproveitando o momento para atualizar o e-mail..                  *
* Atualizado em 2007-07-06, Criado em algum dia qualquer de 2006... *
*********************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>

#include <asm/uaccess.h>

#define EC_PROCFILE_BUF_SIZE 16386
#define EC_PROCFILE_NAME "errorconsole"

#define EC_MODULE_AUTHOR "Bruno Moreira Guedes <bruno@bruno.inf.br"
#define EC_MODULE_LICENSE "GPL"
#define EC_MODULE_VERSION "0.1.0"

#define EC_DEVICE_NAME "error"

#define SUCCESS 0

static const char ec_lf = '\n'; /* Line-feed necessario para limitar   *
          *  a mensagem de erro a uma linha...  */

char *perr;
char **p;

unsigned long long int ec_error_ct = 0; /*Contagem de erros registrados... */

static int ec_openc = 0; /* Processos lendo device(nunca passara de 1)... */

char ec_errors[EC_PROCFILE_BUF_SIZE];  /* Ponteiro para a sequencia de
    * ponteiros
                                      * para strings aonde os erros estão
                                      * armazenados...*/

char *ec_error[4];


static struct proc_dir_entry *ec_procfile;


static ssize_t ec_devwrite (struct file *f,
      const char __user *buf,
      size_t len,
      loff_t *offset);


static ssize_t ec_devread (struct file *f,
     const char __user *buf,
     size_t len,
     loff_t *offset);

static int ec_devopen(struct inode *i,
       struct file *f);


static int ec_devclose(struct inode *i,
        struct file *f);

static struct file_operations opers = {
.read = ec_devread,
.write = ec_devwrite,
.release = ec_devclose,
.open = ec_devopen
};


int ec_major;

int ec_procread(char *buf,
 char **where_buf,
 off_t offset,
 int buflen,
 int *eof,
 void *dt);


int ec_procwrite(struct file *f,
  const char *buf,
  unsigned long cnt,
  void *dt);


int init_module(void);
void cleanup_module(void);




errcon.c: a implementação

/********************************************************************
* Parte responsavel pela leitura do erro...                         *  
* Autor: Bruno Moreira Guedes <bruno@bruno.inf.br>                  *
* Atualizado em 2008-10-22, Criado em algum dia qualquer de 2005... *
* Agora tambem disponivel no blog http://brunildz.blogspot.com      *
*                                                                   *
* CHANGELOG:                                                        *
* 2008-10-22 Bruno Moreira Guedes:                                  *
*  -Correcao de violacao de const                                   *
*  -Dado um aspecto "decente" para a sintaxe                        *
*********************************************************************/

#include "errcon.h"

static ssize_t ec_devwrite (struct file *f,
      const char __user *buf,
      size_t len,
      loff_t *offset)
{
char *ps = NULL; //Ponteiro para string
        char *pb = NULL; //Ponteiro para o buffer... unsigned int i;

ps = ec_error[ec_error_ct % 4];
pb = buf;

for (i = 0; i < len && i < (EC_PROCFILE_BUF_SIZE / 4) -1; i++) {
 /* Percorre os buffers copiando um para o outro,
  * obtendo os buffer de origem do user space
  */

 get_user(*ps, pb++);

 if (*ps == 0 || *ps++ == ec_lf) {
  break;
  }
        }

*ps = 0; //Adiciona fim da string... ec_error_ct++;

return i+1;
}

static ssize_t ec_devread (struct file *f,
     const char __user *buf,
     size_t len,
     loff_t *offset)
{
int read = 0;
      char *tm_buf = buf;

if (!p) {
 /* Se a leitura nao tiver iniciado previamente... */

 p = ec_error;
 perr = *p;
 }

if (!*perr) {
 return 0; /* Indica o EOF quando terminar de mostrar os *
            * buffers...                                 */
 }

while(p - ec_error < 4 && len && *perr) {

 while (*perr && len) {
  put_user(*perr++, tm_buf++);
  len--;
  read++;
  }

 p++;
}

return read;
}


static int ec_devopen(struct inode *i,
       struct file *f)
{

if (ec_openc) {
 return -EBUSY; /*Evita que device seja escrito por dois
   *processos ao mesmo tempo. Isto terá que
   *ser futuramente aperfeicoado, de modo que
   *somente retorne o erro quando o device for
   *aberto para escrita.*/

 }

ec_openc++;
p = NULL; //Reseta os ponteiros...
try_module_get(THIS_MODULE);

return SUCCESS;
}

static int ec_devclose(struct inode *i,
        struct file *f)
{
ec_openc--; //Pronto, o dispositivo ja pode ser usado...
 module_put(THIS_MODULE); //E o modulo descarregado! return SUCCESS;
}

int ec_procread(char *buf,
 char **where_buf,
 off_t offset,
 int buflen,
 int *eof,
 void *dt)
{
return sprintf(buf, "Registered Errors: %llu\nLast Error: %s\n",
        ec_error_ct, ec_error[(ec_error_ct - 1) % 4]);
}


int ec_procwrite (struct file *f,
   const char *buf,
   unsigned long cnt,
   void *dt)
{
char opt;
char *pe = NULL;

if (copy_from_user(&opt, buf, 1)) {
 return -EFAULT;
 }

if (opt == 'c') {
 /* escrevendo 'c' no arquivo proc apagamos os erros*/

 pe = ec_errors;

 while (pe - ec_errors < EC_PROCFILE_BUF_SIZE) {
  *pe++ = 0;
  }

 ec_error_ct = 0;
 }

return 1;
}

int init_module(void)
{
int i;

for (i = 0; i < 4; i++) {
 ec_error[i] = ec_errors + (EC_PROCFILE_BUF_SIZE / 4);
 }

ec_procfile = create_proc_entry(EC_PROCFILE_NAME, 0644, NULL);

if (ec_procfile == NULL){
 /* Algo deu errado, arquivo nao criado... */

 remove_proc_entry(EC_PROCFILE_NAME, &proc_root); /* Remove */

 printk(KERN_ALERT "FATAL: Error creating /proc/errorconsole\n");

 return -ENOMEM;
 }

ec_major = register_chrdev(0, EC_DEVICE_NAME, &opers);

if (ec_major < 0) {
 printk(KERN_ALERT "FATAL: Registering device failed\n");
 remove_proc_entry(EC_PROCFILE_NAME, &proc_root); /* Remove... */

 return ec_major;
 }

ec_procfile->read_proc = ec_procread;
ec_procfile->owner = THIS_MODULE;
ec_procfile->mode = S_IFREG | S_IRUGO;
ec_procfile->uid = 0;
ec_procfile->gid = 0;

printk(KERN_ALERT "errcon: Starting Error Console\n");
printk(KERN_ALERT "errcon: Device Major: %d\n",ec_major);
printk(KERN_ALERT "errcon: Device Minor: 0\n");
printk(KERN_ALERT "errcon: Proc File: %s\n", EC_PROCFILE_NAME);

return SUCCESS;
}


void cleanup_module (void) {
int ret = unregister_chrdev(ec_major, EC_DEVICE_NAME);

if (ret < 0) {
 printk(KERN_ALERT "FATAL: Error unregistering device\n");
 }

remove_proc_entry(EC_PROCFILE_NAME, &proc_root);
}


MODULE_LICENSE(EC_MODULE_LICENSE);
MODULE_AUTHOR(EC_MODULE_AUTHOR);
MODULE_SUPPORTED_DEVICE(EC_DEVICE_NAME);
MODULE_DESCRIPTION("Error Console Device and Statistics");




Makefile - rotinas de compilação

################################################################################ Criado: Na epoca do errcon.c                                                ## Autor:  Bruno Moreira Guedes                                                ## Changelog:                                                                  #
#                                                                             ## 2008-10-22 Bruno Moreira Guedes:                                            ##  -Adicao de targeta htmlcode, para gerar HTML dos arquivos                  ################################################################################
obj-m += errcon.o

ENSCRIPT = /usr/bin/enscript
ENSCRIPT_FLAGS = -i 3 --color --language=html
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


htmlcode:
$(ENSCRIPT) $(ENSCRIPT_FLAGS) -Ec -o errcon.h.html errcon.h
$(ENSCRIPT) $(ENSCRIPT_FLAGS) -Ec -o errcon.c.html errcon.c
$(ENSCRIPT) $(ENSCRIPT_FLAGS) -Emakefile -o Makefile.html Makefile
Bom, agora acho que todos já tem diversão por um bom tempo!! Qualquer dúvida postem aí... É um assunto complexo, não sei se expliquei bem... Mas não se acanhem gente!!

Confiram também o Próximo Artigo da Série

[1] Modularizado ou não, você também pode compilar ele sem suporte a modulos, mas isso normalmente acontece apenas em sistemas embarcados, pois costuma ser desvantajoso em grande parte dos casos

Classificação do conteúdo: SÉRIO
Sobre Bruno Moreira Guedes:
Curriculum Vitae
Site Pessoal

11 comentários:

Unknown disse...

Muito legal o artigo, porém tem um problema... o código não compila com o último git.

E outra coisa, por que tu declaras as variáveis estáticas num .h ao invés de declarar no .c? Não parece muito lógico...

E para todos os que quiseres começar a programar no Linux, não esqueçam de ler também: /usr/src/linux/Documentation/CodingStyle

Anônimo disse...

"A pedido de muitos fãs e leitores deste blog(muitos fãs e leitores = 2 pessoas, o Havacci e o Filipe), vou escrever um pouco sobre desenvolvimento de módulos para o Kernel do Linux aqui."

Bom, se o assunto for ficar no desenvolvimento pro kernel do Linux então você terá 3 leitores.

Anônimo disse...

Excelente tópico!
Parabéns!

Att,
Renato

Anônimo disse...

Muito bom! Por favor! Continue postando sobre o assunto!
Abraços.
(Vc ganhou mais um leitor!)

brunOld disse...

Opa!!

Medaglia, vou baixar o ultimo git e descobrir porque não compila!! Quanto as variáveis estáticas declaradas no .h foi só uma questão de convenção mesmo... Declarar as variáveis globais nos headers.

E agradeço a todos que visitaram/comentaram, fico feliz de ter conseguido escrever algo que teve uma aceitação do público. Isso é sinal de que o que eu escrevi pode servir para alguma coisa, ehehhe...

Abraços!!

Anônimo disse...

Legal o artigo, gostei de ver o exemplo! Vou acompanhar os próximos artigos também.

Parabéns.

Anônimo disse...

Meus parabéns... Só faltou comentar o código :)
Valeu

brunOld disse...

Medaglia,

Eu baixei o ultimo kernel apartir do git aqui e compilou normalmente!!

O que ocorre na compilação??

brunOld disse...

anônimo,

Vou escrever mais sobre ele hoje!!

brunOld disse...

Pessoal,

Demorou mais saiu!!

A explicação do código deste post no novo post que escrevi: http://brunildz.blogspot.com/2008/10/desenvolvimento-de-mdulos-para-o-kernel_28.html

Danilo (Alucard) disse...

Muito, legal... tava precisando disso.

Obrigado!

Sobre Bruno Moreira Guedes