Sunteți pe pagina 1din 15

Periféricos e Interfaces

had

5 Introdução aos módulos do kernel Linux

5.1 Introdução
Neste capítulo será apresentada uma introdução à programação de módulos do kernel Linux.
Um módulo do kernel, ou LKM de Linux Kernel Module, é um segmento de código que pode
ser inserido ou removido a quente, i.e., sem necessidade de reinicializar o sistema, e que
adiciona funcionalidade ao kernel.

Os módulos correm no chamado espaço do kernel (kernel space), enquanto uma aplicação
corre no espaço de utilizador (user space), mesmo que o utilizador corrente seja o supervisor.
No espaço do kernel tudo é permitido ao passo que no espaço de utilizador existe protecção
quanto ao acesso não autorizado a recursos como periféricos e memória.
Esta funcionalidade é possível porque os processadores de hoje em dia implementam pelo
menos dois níveis, ou modos de operação. A família x86 tem mesmo mais do que dois níveis
de operação. Nos níveis inferiores algumas operações não são permitidas.

Assim em Linux, e de um modo geral em Unix, o kernel executa-se no nível mais alto do
CPU, também chamado modo supervisor (supervisor mode), enquanto as aplicações são
executadas no nível mais baixo, o nível de utilizador, onde o processador regula o acesso
directo aos recursos de hardware.
Cada modo de operação pode ter o seu próprio mapeamento de memória, isto é o espaço de
endereçamento diferente. Endereços virtuais iguais em modo de utilizador e modo supervisor
podem ser mapeados em endereços físicos diferentes.

O sistema operativo transfere a execução do espaço de utilizador quando:

1. A aplicação efectua uma chamada ao sistema


2. Ocorre uma interrupção de hardware

No primeiro caso o kernel opera em benefício do processo que efectuou a chamada, e é capaz
de aceder a dados no espaço de endereçamento do processo.
No segundo caso, o código que trata as interrupções não está sincronizado com nenhum
processo. Poderá estar a responder a uma interrupção de um periférico.

Um tipo de módulo é o device driver, que permite ao kernel aceder a dispositivos de entrada-
saída. Este tipo de módulos serão abordados num capítulo posterior.

5.2 Inserção e remoção de módulos no kernel


Para se determinar quais os módulos inseridos no kernel num dado instante pode ser dado no
interpretador de comandos a instrução:

lsmod

Sendo apresentada uma lista com o seguinte formato:


Module Size Used by
8250 33796 0 [permanent]
serial_core 15208 1 8250
usb_storage 83120 0 [permanent]
usbcore 96224 1 usb_storage,[permanent]
nls_iso8859_1 3624 0 [permanent]
nls_cp850 4424 0 [permanent]

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 1/15


Periféricos e Interfaces
had

Na primeira coluna é indicado o nome do modulo, que normalmente é o nome do ficheiro sem
extensão. Na segunda coluna é indicado o seu tamanho em bytes, na terceira um contador de
utilização do módulo. Tipicamente refere-se a dispositivos abertos ou sistemas de ficheiros
montados. Um módulo não pode ser removido enquanto o contador de utilização não for zero.
A última coluna indica que módulos dependem do módulo corrente. No exemplo anterior o
módulo usb_storage depende do módulo usbcore, isto é de algum modo usb_storage acede
a alguma funcionalidade disponibilizada por usbcore.
Para remover módulos com dependências, primeiro terão de ser removidos os módulos que
dele dependem.
Se esta coluna indicar [permanet] o módulo não pode ser removido. Se indicar (unused) o
módulo nunca foi usado, i.e, que nunca esteve numa situação em que não poderia ser
removido.

O comando lsmod apenas formata o conteúdo de /proc/modules. Também seria possível


identificar os módulos presentes no kernel, com:

cat /proc/modules

smbfs 54680 1 [permanent], Live 0xc889e000


8250 33796 0 [permanent], Live 0xc8894000
serial_core 15208 1 8250, Live 0xc884d000
usb_storage 83120 0 [permanent], Live 0xc887e000
usbcore 96224 1 usb_storage,[permanent], Live 0xc8865000
nls_iso8859_1 3624 0 - Live 0xc8845000
nls_cp850 4424 0 [permanent], Live 0xc8842000

Para inserir um modulo pode-se usar o comando insmod que como argumento espera o
caminho do código objecto do módulo a inserir. como o hello.ko deve-se usar:

modprobe <caminho para código objecto do módulo>

A partir da versão 2.6 do kernel o código objecto dos módulos tem a extensão *.ko.
Para remover é usado o comando rmmod onde o argumeno é o nome do módulo como
indicado por lsmod.

rmmod <nome do módulo como listado por lsmod>

A inserção de módulos pode ser efectuada de um modo inteligente pelo comando insmod:

modprobe <nome do ficheiro com o código objecto do módulo sem extensão>

Neste caso o argumento é apenas o nome do ficheiro com o código objecto do módulo sem
extensão. O caminho completo do ficheiro com o código objecto do módulo encontra-se no
ficheiro:

/lib/modules/<versão do kernel/modules.dep

Neste ficheiro também estão especificadas as dependências de cada módulo.


Por exemplo se o módulo: /lib/modules/2.6/a.ko depende dos módulos b.ko e c.ko na mesma
directoria e além disso o módulo c.ko depende também de b.ko, o ficheiro modules.dep
poderia ser:

/lib/modules/2.6/a.ko: /lib/modules/2.6/c.ko /lib/modules/2.6/b.ko


/lib/modules/2.6/b.ko:
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 2/15
Periféricos e Interfaces
had

/lib/modules/2.6/c.ko: /lib/modules/2.6/b.ko

Assim após o caminho para o código do módulo, tem-se as lista de dependências precedida
por dois pontos (:).
Deste modo, o comando modprobe antes de carregar o módulo pedido carrega os módulos na
lista de dependências, da direita para a esquerda. A remoção será efectuada em sentido
inverso.

Este ficheiro pode ser automaticamente criado com o comando depmod. Este comando
analisa todos os módulos na directoria:

/lib/modules/<versão do kernel

procurando os símbolos exportados e os símbolos que um dado módulo necessita.

Os módulos podem ser automaticamente carregados pelo kernel quando necessário. Para isso
existe o processo kerneld, que quando é necessário um módulo que não está carregado
executa modprobe para o carregar e indica ao kernel se a operação teve ou não sucesso.

5.3 Estrutura elementar dos módulos


O código seguinte representa um módulo muito simples, mas ainda com alguma
funcionalidade:

/* hello.c
*/
#include <linux/module.h> /* porque é um módulo */
#include <linux/kernel.h> /* para KERN_ALERT */

int init_module(void) {
printk("<1>Hello world\n");
return 0; // Um valor não nulo indica que init_module falhou
} // não tendo sido carregado.

void cleanup_module(void) {
printk(KERN_ALERT "Goodbye world\n");
}

Em termos elementares o módulo é dividido em duas funções:

int init_module(void)

void cleanup_module(void)

que respectivamente são chamadas quando o módulo é inserido e quando é removido. A


primeira é usada como ponto de entrada para inicialização do módulo. Esta função poderá
chamar ainda outras funções.
Repare-se no entanto que numa aplicação tipíca, quando a função de entrada: main( ),
termina, termina também a aplicação. No entanto num módulo a função de entrada,
init_module ( ) serve apenas para registar o módulo e indicar quais as funções que este
disponibiliza. Após a execução desta função o módulo fica inactivo à espera de pedidos.

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 3/15


Periféricos e Interfaces
had

Ao ser removido o módulo a função cleanup_module ( ) deve desfazer tudo o que foi feito
por init_module ( ).

Um uso possível também para os módulos é substituir o código de uma função do kernel.
Normalmente é efectuado qualquer operação e depois chamada a função original. Este desvio
da chamada para código do módulo e depois para a função original é efectuado por
init_module ( ). Assim, ao ser removido o módulo deve repôr o estado original do sistema de
modo que este não fique instável. Tal deve ser efectuado por cleanup_module ( ).

A novas versões do kernel definem duas macros que permitem alterar o nome destas duas
funções para outro definido pelo programador. Se o objectivo fosse alterar o nome das
funções de entrada e saída respectivamente para hello_init ( ) e hello_exit ( ), deveriam ser
colocadas no final do código as macros definidas em <linux/init.h>:

module_init (hello_init);
module_exit (hello_exit);

Como já foi referido este módulo é muito simples mas tem alguma funcionalidade, imprime
uma mensagem quando é inserido e outra quando é removido, respectivamente:

Hello world e
Goodbye world

Ambas as funções usam printk ( ) para imprimir as mensagens. No entanto esta função
consiste num mecanismo de log para o kernel. De facto todas as mensagem “impressas” com
printk são adicionadas ao registo que se encontra em /var/log, e que pode ser consultado com
o comando dmesg.
Assim as mensagens são impressas apenas se o módulo for inserido ou removido numa
consola TTY (modo texto). Se os comandos forem dados num terminal virtual como por
exemplo o xterm as mensagens não são impressas, tendo de ser usado dmesg para as
visualizar. O prótotipo de printk ( ) é semelhante ao de printf:

int printk(const char * fmt, ...)

No entanto, como mostra o exemplo anterior, no ínicio da string de formatação fmt, pode ser
indicado um nível de prioridade numérico, ou usada uma macro como definido em
<linux/kernel.h>:

#define KERN_EMERG "<0>" /* System is unusable */


#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages

Se a prioridade for menor que o valor de int console_loglevel, a mensagem é impressa na


consola (terminal TTY), senão é apenas adicionada ao registo (assumindo que os processos
syslogd e klog) estão activos.
Foi usada a prioridade KERN_ALERT para garantir que a mensagem é apresentada na
consola.

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 4/15


Periféricos e Interfaces
had

Para compilar os módulos pode ser usada o makefile:

#Compilar hello.c para kernel 2.6


obj-m += hello.o

#chama Makefile na directoria com as fontes do kernel


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

#Limpa os ficheiros gerados pela compilação, mantendo o código fonte


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

Se o ficheiro acima se chamar Makefile bastando executar o commando make na consola. Se


tiver outro nome ter´s de ser especificado:

make –f <nome do ficheiro>

Supondo que o modulo estava dividido em dois ficheiros, por exemplo a função
init_module() num ficheiro chamado init.c e a função cleanup_module() num ficheiro
chamado exit.c, bastaria alterar as linhas que indicam os ficheiros a compilar, no início do
makefile:

#Compilar hello.c para kernel 2.6


obj-m += hello.o
hello-objs := init.o exit.o

Após compilar o módulo pode-se aceder a informação sobre este com o comando:

modinfo hello.ko

filename: hello.ko
vermagic: 2.6.14-mini 386 gcc-3.3
depends:

Que indica o nome do ficheiro, a versão do kernel, o processador para o qual foi compilado
(neste caso seria para 386 e sucessores), e qual o compilador em vermagic:.
Na última linha indicaria dependências se existissem. Para módulos mais complexos este
comando pode retornar mais informação.

Agora ao inserir o módulo, repare-se na mensagem impressa na consola:

hello: module license 'unspecified' taints kernel.


Hello world

que indica que o kernel pode ter sido corrompido. Esta mensagem aparece porque não foi
especificada uma licença no módulo. Este mecanismo surgiu no kernel 2.4 para indicar se o
código é aberto ou proprietário. A licença pode ser indicada com a macro seguinte. Pode ser
colocada logo após a inclusão dos headers no início do código, mas não é obrigatório:
MODULE_LICENSE("GPL");

ou

MODULE_LICENSE("Proprietary");

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 5/15


Periféricos e Interfaces
had

5.3.1 Informação dos módulos


Além da informação sobre a licença referida no ponto anterior, o programador de um módulo
pode também indicar o autor, uma descrição do módulo, a versão do módulo, e uma nome
alternativo:

MODULE_AUTHOR("Nome do autor");
MODULE_DESCRIPTION("Simples módulo de demonstração: Hello world");
MODULE_VERSION("1.0");
MODULE_ALIAS("Olá!");

As macros acima indicadas, e de um modo geral todas as começadas por MODULE_ podem
ser usadas em qualquer parte do código, existe no entanto uma convenção de as colocar no
fim do código. É conveniente que quando se desenvolvem módulos as 3 primeiras macros
sejam usada.

A informação dada por modinfo será:

filename: hello.ko
author: Nome do autor
description: Simples módulo de demonstração: Hello world
version: 1.0
alias: Olá
vermagic: 2.6.14-mini 386 gcc-3.3
depends:
srcversion: 6D52F7CBC1A3718D85C980E

O campo srcversion é uma soma dos ficheiros que compõe os módulos. Isto ajuda quem
desenvolve os códigos a verificar se os ficheiros fonte disponíveis foram os usados para
compilar um dado módulo, pois é possível que um programador faça alguma alteração num
módulo e não indique uma nova versão com MODULE_VERSION.

5.3.2 Exportação de símbolos


Por defeito todos os símbolos de um módulo, identificadores de variáveis e funções, não são
visíveis no kernel, isto é são locais ao módulo. A única necessidade de um módulo exportar
símbolos, é para que outros módulos possam usá-los, i.e., para módulos que dependem de
outros módulos.

Fig. 5-1: Empilhamento de módulos do driver do porto paralelo (in Corbet et. al. 2005)

Na figura anterior têm-se a representação esquemática do empilhamento de drivers para o


porto paralelo. Este empilhamento pode ser visto como uma decomposição do driver em
camadas de abstracção de modo a simplificar o desenvolvimento do mesmo driver para
hardware não totalmente compatível. É assim comum ter um modulo com funções de baixo
nível dependentes do hardware que exporta símbolos para módulos de mais alto nível.
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 6/15
Periféricos e Interfaces
had

No caso do porto paralelo, o módulo de mais baixo nível parport disponibiliza funções de
baixo nível para os módulos de mais alto nível: parport_pc e lp, o driver da impressora.
Todos os módulos exportam também símbolos para a API do kernel.
Nestes casos é conveniente usar o comando modprobe para carregar um módulo como por
exemplo lp, e todos os outros do qual este módulo depende.
Como já foi referido, as dependências podem ser verificas com o comando modinfo ou no
ficheiro /lib/modules/<versão do kernel/modules.dep.

Quando um módulo é carregado, quaisquer símbolos exportados são colocados na tabela de


símbolos do kernel (Kernel Symbol Table). Para exportar símbolos são usadas as seguintes
macros:

EXPORT_SYMBOL (identificador);
EXPORT_SYMBOL_GPL (identificador);

A segunda faz com que o símbolo seja acessível fora do módulo apenas para módulos com
licença GPL.

Assim nem todas as funções da libc estão disponíveis para módulos. Estes usam apenas os
símbolos disponibilizados pelo kernel. Estes símbolos poderão ser listados com:

cat /proc/kallsyms

Repare-se que parte das funções da libc são wrappers para chamadas ao sistema, e não é
necessário usá-las em modo kernel. Por exemplo a função puts() efectua a chamada write. Se
se pretender determinar quais as chamadas ao sistema efectuadas por uma aplicação, pode-se
usar o comando strace. Por exemplo compilando o programa seguinte:

/* hello.c
*/
#include <stdio.h>

main () {
puts(“Hello world”);
}

E executando:

strace hello

Têm-se um traçado das chamadas ao sistema. As primeiras têm a ver com a inicialização do
processo pelo sistema operativo. No final encontra-se uma linha com a chamada write
efectuada por puts().

execve("./hello", ["./hello"], [/* 13 vars */]) = 0


uname({sys="Linux", node="balanca", ...}) = 0
(...)
write(1, "Hello world\n", 12Hello world
) = 12
munmap(0xb7f2f000, 4096) = 0
semget(12, 3086077511, IPC_CREAT|IPC_EXCL|IPC_NOWAIT|0xb7f1c000|0

Repare-se que a string é impressa no meio do traçado. O valor 12 retornado indica os


caracteres que foram escritos.
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 7/15
Periféricos e Interfaces
had

Para módulos não é possível determinar este traçado com strace.

5.3.3 Inicialização e encerramento


Já foi referido que o nome das funções de entrada e saída podem ser alteradas usando macros
definidas em <init.h>. Neste header estão definidas ainda outras macros que podem ser usadas
na inicialização e no encerramento do módulo.

A definição da função de inicialização, embora não seja obrigatório, deve ser no formato:

static int __init initialization_function(void) {


/* Código de inicialização */
}
module_init(initialization_function);

Como já foi visto por defeito não os símbolos locais dos módulos não são exportados, por isso
não mesmo que a função não seja declarada como static não é visível fora do módulo.

O modificador __init, indica ao carregador de módulos que após o módulo ter sido
inicializado, a função de inicialização pode removida da memória, deixando-a disponível.
Existe também uma etiqueta semelhante __initdata para libertar da memória dados utilizados
apenas na incialização:

static int umInteiro __initdata = 1;

Deve-se tomar cuidado para não se utilizar __init e __initdata com funções e dados
necessários após a inicialização.

A função de encerramento, que liberta recursos utilizados pelo módulo antes deste ser
descarregado, é definida como:

static void __exit cleanup_function(void)


{
/* liberta recursos */
}
module_exit(cleanup_function);

O modificador __exit, indica que se o módulo for montado directamente no kernel ou se a


opção de remoção de módulos do kernel estiver desactivada, o código da função não é
carregado para a memória durante a inserção do módulo. Se o módulo não pode ser removido
não hás necessidade ocupar memória com a função de encerramento.

5.4 Passagem de parâmetros para os módulos


A operação do driver pode variar de acordo com o sistema onde será instalado. Pode ser
necessário especificar os endereços de E/S de um dado periférico ou os seus portos. Para que
o driver se adapte ao sistema é possível passar-lhe argumentos na linha de comandos quando é
inserido com modprobe ou insmod:

insmod modulo.ko count = 10 str = "uma string"

Para que os argumentos sejam passados quando o módulo é carregado, os parâmetros


correspondentes devem ser declarados com a macro module_param, definida em
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 8/15
Periféricos e Interfaces
had

<linux/moduleparam.h >. Esta macro recebe três parâmetros: o nome da variável, o seu tipo e
as permissões para expôr os parâmetros do módulo numa entrada sysfs, se o valor for
diferente de zero.

Convém referir que sysfs é um sistema de ficheiros virtual, que surgiu com o kernel 2.6, e que
exporta informação sobre dispositivos e drivers para o espaço de utilizador. Pode ser usado
também para configuração.

Se o valor deste último parâmetro for nulo não existe entrada em sysfs. Para dar permissões
devem ser usadas as macros definidas em <linux/stat.h>. Por exemplo:

S_IRUGO torna o valor do parâmetro visível mas não alterável.


S_IRUGO | S_IWUSR torna o valor do parâmetro visível e alterável pelo root.

De qualquer modo alterar os parâmetros do módulo poderá ter implicações na sua


funcionalidade, de modo que se não for estritamente necessário aconselha-se a que não sejam
dadas permissões, ou apenas visualização dos valores dos parâmetros com S_IRUGO.

Então para se poder aceder aos argumentos passados no exemplo anterior, o código seria:

static int count = 0;


static char *str = “qq coisa”;

module_param(count, int, S_IRUGO);


module_param(str, charp, 0);
MODULE_PARM_DESC(str, “Uma cadeia de caracteres”);

Repare-se que os nomes dos parâmetros da linha de comando têm de coincidir com as
variáveis do código. Além disso devem ser dados valores por defeito aos argumentos, pois o
módulo poderá ser carregado sem que todos os parâmetros sejam especificados na linha de
comando.

A macro MODULE_PARAM_DESC é usada para descrever os parâmetros. Esta informação


pode ser acedida com modinfo. Para o exemplo acima seria apresentado:

(...)
depends:
parm: str:Uma cadeia de caracteres (charp)
parm: count:int

Assim se não for usada esta macro é apenas indicado o nome do parâmetro e o seu tipo. Se for
usada é também apresentada a descrição.

Vários tipos de parâmetros podem ser especificados:

bool toma valores true ou false, e guarda-os numa variável inteira.


invbool inverte o valor booleano passado.
charp Um ponteiro para character. Memória é alocada para as strings passadas pelo
utilizador e o ponteiro aponta para o seu ínicio
int
long
short
uint
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 9/15
Periféricos e Interfaces
had

ulong
ushort Inteiros. O prefixo u indica sem sinal.

Podem também ser definidos arrays como parâmetros, onde os valores são separados por
vírgulas:
insmod modulo.ko array = 10,3,4

Para declarar estes parâmetros é usada a macro module_param_array. Os primeiros dois


parâmetros e o último são idênticos a module_param, o terceiro é um ponteiro para uma
variável que guarda o número de itens do array passados na linha de comando.

static int arr_count = 0;


static int array[3] = {-1, -1, -1};
module_param_array(array, int, &arr_count, 0);
MODULE_PARM_DESC(array, “Vector para um máximo de 3 inteiros”);

Se o tentar utilizador passar mais itens do que a dimensão do array um erro é apresentado na
consola e o módulo não é carregado. Por isso talvez seja boa prática descrever estes
parâmetros indicando também a sua capacidade.
Seguidamente é apresentado o exemplo de um módulo completo:

/* modpar.c
* Demonstração de passagem de argumentos da linha de comandos para módulo.
*/
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/stat.h>

static int count = 0;


static char *str = "qq coisa";
static int arr_count = 0;
static int array[3] = {-1, -1, -1};

module_param(count, int, S_IRUGO);


module_param(str, charp, S_IRUGO);
module_param_array(array, int, &arr_count, 0);
MODULE_PARM_DESC(count, "Inteiro 32 bits");
MODULE_PARM_DESC(str, "Cadeia de caracteres");
MODULE_PARM_DESC(array, "Vector inteiros com max 3 elementos");

static int __init start(void) {


int i;
printk(KERN_ALERT "modpar inserido\n");
printk(KERN_ALERT "count= %d\n", count);
printk(KERN_ALERT "str = %s\n", str);
printk(KERN_ALERT "itens no array = %d\nitems:", arr_count);
for (i=0; i<arr_count; ++i) printk(" %d ", array[i]);
return 0;
}

static void __exit stop(void) {


printk(KERN_ALERT "modpar removido!\n");
}

module_init(start);
module_exit(stop);

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 10/15


Periféricos e Interfaces
had

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Helder Daniel");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Demonstração de passagem de argumentos da linha de
comandos para módulo.");

Se o módulo for inserido com:

insmod modpar.ko count=4 str="Teste" array=1,2

Ter-se-á no registo do sistema ou na consola:


modpar inserido
count= 4
str = Teste
itens no array = 2
items: 1 2 <1>modpar removido!

E a informação do módulo dada por modinfo será:


filename: modpar.ko
license: GPL
author: Helder Daniel
version: 1.0
description: Demonstração de passagem de argumentos da linha de comandos
para módulo.
vermagic: 2.6.14-mini 386 gcc-3.3
depends:
srcversion: F22A0B1E5C7103268020556
parm: array:Vector inteiros com max 3 elementos (array of int)
parm: str:Cadeia de caracteres (charp)
parm: count:Inteiro 32 bits (int)

Agora em sysfs é possível obter informação sobre o módulo. Para visualizar o valor dos
parâmetros basta editar o conteúdo dos ficheiros com o mesmo nome, por exemplo::

cat /sys/module/modpar/parameters/count

5.5 Notas sobre programação do kernel


A programação do kernel difere da programação no espaço de utilizador de muitos modos,
como será referido ao longo do texto. Neste ponto serão indicadas algumas diferenças
fundamentais.

As aplicações, i.e, processos em modo de utilizador, são mapeadas na memória virtual com
uma pilha muito grande. Nesta pilha é colocado não só o endereço de retorno das funções,
mas também todas as variáveis automáticas criadas pelas funções, i.e., os argumentos das
funções e as variáveis declaradas localmente nas funções.
O kernel por outro lado tem uma pilha que pode ser tão pequena como uma única página de
4KBytes. As funções de um módulo partilham esta pilha com as funções de todos os outros
módulos e do restante código do kernel. Por isso não se deve declarar variáveis locais muito
grandes, como por exemplo:

int init_module(void) {
unsigned char localbuffer [1000];
// ....
}

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 11/15


Periféricos e Interfaces
had

A alocação de variáveis desta natureza deve ser efectuada dinamicamente quando o módulo
for chamado para efectuar uma qualquer função.

O código do kernel não pode efectuar operações de vírgula flutuante. Refira-se que não são
necessárias na programação do kernel e que a sua utilização implica uma sobrecarga. É fácil
verificar a natureza desta sobrecarga quando um processo efectua uma chamada ao sistema.
Antes de se entrar em modo kernel o contexto do processo deve ser preservado, pelo menos os
registo do CPU, pois não é possível determinar quais registos o código do kernel vai utilizar.
Ao terminar a chamada, o conteúdo dos registos é reposto antes de se devolver a execução ao
processo.
Assim existiria uma perda de tempo desnecessária se se tivesse de guardar e repor também os
valores dos registos de vírgula flutuante.

Normalmente um módulo, por exemplo um driver, executa uma dada tarefa a pedido de um
processo que corre em modo utilizador. Este é o caso de uma chamada ao sistema como open
ou write. Um modo de se aceder a partir do módulo ao descritor do processo que invocou a
chamada, i.e, à estrutura task_struct, definida em <linux/sched.h>, é através do ponteiro
global current, definido em <asm/sched.h>. O seguinte código permite retornar o nome do
processo corrente, i.e, os primeiros 15 caracteres do nome do ficheiro executável, e o seu PID:

printk(KERN_INFO "The process is \"%s\" (pid %i)\n",


current->comm, current->pid);

5.6 Compilação de módulos para kernel pré-compilado


Antes de mais deve-se referir que o sistema onde se pretende desenvolver módulos deve ter
um kernel compilado com suporte para inserção de módulos. Para configurar o kernel pode
ser usado o comando:

make menuconfig

Sendo aberta a janela seguinte:

Fig. 5-2: Menu principal da configuração do kernel

Escolhendo a opção indicada entra-se no menu de configuração do suporte para módulos


carregáveis:
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 12/15
Periféricos e Interfaces
had

Fig. 5-3: Configuração do suporte para módulos carregáveis

Neste menu devem estar assinaladas as opções indicadas acima:

Enable loadable module support para habilitar o suporte.


Module unloading para que os módulos possam ser removidos com o comando rmmod.
Force Module unloading para que se possa força a remoção de módulos com rmmod –f.
Durante o desenvolvimento de módulos é possível que devido a algum erro no código o
módulo não possa ser removido. Se esta opção não for ligada a única alternativa para a
remoção do módulo é reinicializar o sistema.
Automatic kernel module loading para que os módulos possam ser automaticamente
carregados pelo kernel se necessário. Esta última opção não é estritamente necessária mas
poderá ser util, mesmo que o sistema não seja usado para desenvolvimento de módulos.

Após ser efectuada a configuração é necessário compilar o kernel usando a série de


comandos:
make install #compila o kernel
make module_install #compila os módulos

Para que se possa inserir um módulo no kernel a versão do módulo e do kernel devem ser
iguais, mais precisamente a vermagic. A vermagic do kernel pode ser determinada com:

uname –r

e a do módulo com o comando modinfo. Para o exemplo anterior tinha-se:

vermagic: 2.6.14-mini 386 gcc-3.3

Sendo apenas a primeira string: 2.6.14-mini a que interessa em termos de verificação da


possibilidade de inserção do módulo. Se forem iguais é possível inserir o módulo com
insmod. Se não forem pode-se usar o comando:

modprobe --force-vermagic hello.ko

Para que este comando possa ser utilizado, como já foi referido, pelo menos o caminho para
módulo terá de estar referido no ficheiro:
http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 13/15
Periféricos e Interfaces
had

/lib/modules/2.6.14-mini/modules.dep

Além disso este comando é potencialmente inseguro, pois permite forçar a instalação de um
módulo compilado para uma versão anterior do kernel presente no sistema.

Um meio mais seguro consiste em instalar no sistema as fontes do kernel que está a ser
executado é alterar a vermagic para que seja igual. Repare-se que a vermagic original do
kernel indica a sua versão, e é tão simples como por exemplo 2.6.14, no entanto algumas
distribuições alteram a vermagic. Por exemplo a distribuição Mandrake adiciona –mdk ao
número da versão: 2.6.14-mdk.

Para alterar a vermagic pode-se aceder à configuração do kernel, como já foi referido, e no
menu principal escolher a opção General Setup, entrando-se no menu:

Fig. 5-4: Configuração geral do kernel 2.6.14

A primeira opção permite que seja adicionada uma string à versão do kernel. No exemplo será
–mini, ficando 2.6.14-mini.
Agora é necessário actualizar os ficheiros do código fonte. Para isso basta executar make até
que seja compilada a 3ª linha SPLIT include/linux/autoconf.h -> include/config/*,
interrompendo a compilação com <ctrl-c>:

CHK include/linux/version.h
UPD include/linux/version.h
SPLIT include/linux/autoconf.h -> include/config/*
HOSTCC scripts/mod/modpost.o
HOSTLD scripts/mod/modpost
CC init/main.o

Pois o símbolo UTS_RELEASE do ficheiro /usr/src/linux/include/linux/version.h já foi


actualizado nas duas primeiras linhas. Alternativamente pode-se editar este ficheiro e alterar a
string UTS_RELEASE.

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 14/15


Periféricos e Interfaces
had

Como nota final deve ser referido que a opção Module versioning support no menu de
configuração apresentado na figura Fig. 5-3, é possível inserir alguns módulos mesmo que as
vermagics sejam diferentes. No entanto esta opção é experimental, não garante que todos os
módulos possam ser inseridos. Também não existe garantia que um kernel pré-compilado, o
tenha sido com esta opção habilitada.

5.7 Bibliografia
Corbet, Jonathan, Alessandro Rubini e Kroah-Hartman, Greg (2005). “Linux Device Drivers
3rd edition”, O’Reilly, http://lwn.net/Kernel/LDD3/

Salzman, Peter Jay, Michael Burian and Ori Pomerantz (2005). “The Linux Kernel Module
Programming Guide”, http://www.tldp.org/LDP/lkmpg/2.6/lkmpg.pdf

Linux man pages

http://w3.ualg.pt/~hdaniel/pin PIN 2005-6 T05 - 15/15

S-ar putea să vă placă și