Anatomia dos Módulos Kernel Carregáveis do Linux

Uma Perspectiva do Kernel 2.6

Os módulos de kernel Linux®carregáveis, introduzidos na versão 1.2 do kernel, são algumas das mais importantes inovações no kernel Linux. Eles oferecem um kernel escalável e dinâmico. Descubra o que há por trás dos módulos carregáveis e saiba como esses objetos independentes se tornam, dinamicamente, parte do kernel Linux.

M. Tim Jones, Consultant Engineer, Emulex Corp.

M. Tim JonesM. Tim Jones é um arquiteto de firmwares embarcados e autor de Inteligência Artificial: Sistemas de Abordagem, GNU/Linux, Programação de Aplicativos AI (atualmente em sua segunda edição), Programação de Aplicativos AI (em sua segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Sua formação em engenharia vai desde o desenvolvimento de kernels para nave espacial geossincrônica até a arquitetura de sistema embarcado e o desenvolvimento de protocolos de interligação de redes. Tim é um Engenheiro Consultor para a Emulex Corp. em Longmont, Colorado.


nível de autor Contribuidor do
        developerWorks

16/Jul/2008

O kernel Linux é conhecido como kernel monolítico, o que significa que a maior parte da funcionalidade do sistema operacional é chamada de kernel e é executada em um modo privilegiado. Isto é diferente de um microkernel, que é executado somente em funcionalidade básica como o kernel (Inter-process Communication [IPC], escalonamento, entrada/saída [E/S] básicas, gerenciamento de memória) e tira de seu espaço privilegiado outras funcionalidades (drivers, pilha de rede, sistemas de arquivos). Seria possível achar que o Linux é um kernel muito estático, mas na verdade é o contrário. O Linux pode ser alterado dinamicamente durante sua execução pelos módulos do kernel Linux (LKMs).

Dinamicamente alterável significa que é possível carregar uma nova funcionalidade no kernel, descarregar uma funcionalidade dele e até incluir novos LKMs que utilizem outros LKMs. Para os LKMs, a vantagem é que é possível minimizar o uso de memória de um kernel, carregando apenas os elementos necessários (o que pode ser um recurso importante em sistemas embarcados).

O Linux não é o único kernel monolítico que pode ser dinamicamente alterado (e não foi o primeiro). O suporte para módulos carregáveis será encontrados em variantes do Berkeley Software Distribution (BSD), Sun Solaris, em kernels mais antigos como OpenVMS e outros sistemas operacionais populares como Microsoft® Windows® e Apple Mac OS X.

Anatomia de um Módulo de Kernel

Um LKM apresenta algumas diferenças fundamentais a partir de elementos que compilam diretamente no kernel e também programas típicos. Um programa típico tem um elemento principal, enquanto um LKM tem uma função de entrada e saída do módulo (na versão 2.6 é possível nomear como quiser essas funções). A função de entrada é chamada quando o módulo é inserido no kernel e a função de saída é chamada quando ele é removido. Como as funções de entrada e de saída são definidas pelo usuário, existem macros module_init e module_exit que definem quais são essas funções. Um LKM também inclui um conjunto obrigatório e um opcional de macros de módulo. Elas definem a licença do módulo, seu autor, uma descrição e mais. A Figura 1 fornece uma visualização de um LKM muito simples.

Figura 1. Visão do Código Fonte de um LKM Simples
Visão do Código Fonte de um LKM Simples

A versão 2.6 do kernel Linux oferece um método novo (mais simples) para a construção de LKMs. Quando criado, é possível utilizar as ferramentas de usuário típicas para gerenciar os módulos (embora as ferramentas internas tenham mudado): os comandos padrão insmod (instalando um LKM), rmmod (removendo um LKM), modprobe (wrapper para insmod e rmmod), depmod (para criar dependências de módulos) e modinfo (para localizar os valores para macros de módulos). Para informações adicionais sobre a construção de LKMs na versão 2.6 do kernel, verifique a seção Recursos.


Anatomia de um Objeto de Módulo de Kernel

Um LKM não é nada mais do que um arquivo de objetos ELF (Executable and Linkable Format) especial. Normalmente, os arquivos de objetos são ligados para solucionar seus símbolos e este link se torna um executável. Como um LKM não pode resolver seus símbolos até estar carregado no kernel, o LKM permanece como um objeto ELF. É possível utilizar ferramentas de objetos padrão em LKMs (que, na versão 2.6, têm o sufixo .ko, para objetos de kernel). Por exemplo, se o utilitário objdump foi utilizado em um LKM, é possível encontrar várias seções familiares, como .text (instruções), .data (dados inicializados), e .bss (Block Started Symbol, ou dados não-inicializados).

Também serão encontradas seções adicionais em um módulo, que oferecem suporte à sua natureza dinâmica. A seção .init.text contém o código module_init e .exit.text contém o código module_exit (consulte a Figura 2). A seção .modinfo contém os vários textos de macro que indicam a licença do módulo, seu autor, sua descrição, etc.

Figura 2. Um Exemplo de um LKM com Várias Seções ELF
Um Exemplo de um LKM com Várias Seções ELF

Assim, com essa introdução dos fundamentos dos LKMs, vamos nos aprofundar e conhecer como os módulos inserem-se no kernel e são gerenciados internamente.


Ciclo de Vida de um LKM

O processo de carregamento do módulo começa no espaço de usuário com insmod (insira o módulo). O comando insmod define o módulo a carregar e solicita a chamada do sistema em espaço de usuário init_module para iniciar o processo de carregamento. O comando insmod para a versão 2.6 do kernel tornou-se extremamente simples (70 linhas de código), com base em uma alteração para fazer mais trabalhos no kernel. Em vez de insmod fazer qualquer resolução de símbolo necessária (trabalhando com kerneld), o comando insmod simplesmente copia o binário do módulo no kernel através da função init_module e o kernel cuida do resto.

A função init_module funciona através da camada de chamada de sistema e no kernel até uma função dele, chamada sys_init_module (consulte a Figura 3). Esta é a função principal do carregamento de módulos, que utiliza diversas outras funções para realizar o trabalho difícil. De forma semelhante, o comando rmmod resulta em uma system call paradelete_module, que, por fim, encontra o caminho até o kernel com uma chamada para sys_delete_module para remover o módulo do kernel.

Figura 3. Comandos e Funções Primários Envolvidos no Carregamento e Descarregamento do Módulo
Comandos e Funções Primários Envolvidos no Carregamento e Descarregamento do Módulo

Durante o carregamento e descarregamento do módulo, o subsistema de módulo mantém um conjunto simples de variáveis de estado para indicar a operação de um módulo. Se ele estiver sendo carregado, o estado será MODULE_STATE_COMING. Se o módulo tiver sido carregado e estiver disponível, será MODULE_STATE_LIVE. Ao contrário, se o módulo estiver sendo descarregado, o estado será MODULE_STATE_GOING.


Detalhes do Carregamento do Módulo

Vejamos agora as funções internas do carregamento de módulo (consulte a Figura 4). Quando a função do kernel sys_init_module é chamada, ela inicia a verificação de permissões para ver se o responsável pela chamada pode realmente executar essa operação (por meio da função capable). Depois, a função load_module é chamada e cuida para que o trabalho mecânico traga o módulo para o kernel e execute o arranjo necessário (examinarei isto em breve). A função load_module retorna uma referência de modulo, que consulta o módulo recém-carregado. Este módulo é inserido em uma lista duplamente ligada de todos os módulos no sistema e todos os threads que estiverem esperando no momento pela alteração no estado do módulo são notificados pela lista de notificação. Finalmente, a função init() do módulo é chamada e o estado do módulo é atualizado para indicar que está carregado e ativo.

Figura 4. O Processo Interno (Simplificado) de Carregamento de Módulo
O Processo de Carregamento do Módulo Interno (Simplificado)

Os detalhes internos de carregamento do módulo são análise e manipulação do módulo ELF. A função load_module (que reside em./linux/kernel/module.c ./linux/kernel/module.c) começa alocando um bloco de memória temporária para manter todo o módulo ELF. O módulo ELF é então lido do espaço de usuário para a memória temporária usando copy_from_user. Como um objeto ELF, este arquivo apresenta uma estrutura muito específica, que pode ser facilmente analisada e validada.

A etapa seguinte é executar um conjunto de verificações de erros na imagem carregada (é um arquivo ELF válido? ele está definido para a atual arquitetura?, etc). Quando essas verificações de erros são concluídas, a imagem ELF é analisada e um conjunto de variáveis de conveniência é criado para cada cabeçalho da seção, de modo a simplificar posteriormente seu acesso. Como os objetos ELF se baseiam no deslocamento 0 (até a relocação), as variáveis de conveniência incluem o deslocamento relativo no bloco de memória temporário. Durante o processo de criação das variáveis de conveniência, os cabeçalhos da seção ELF também são validados, para assegurar que um módulo válido esteja sendo carregado.

Quaisquer argumentos opcionais de módulo são carregados a partir do espaço de usuário para outro bloco alocado da memória do kernel (etapa 4), e o estado do módulo é atualizado para indicar que está sendo carregado (MODULE_STATE_COMING). Se forem necessários dados por CPU (como determinado nas verificações de cabeçalho da seção), um bloco por CPU é alocado.

Nas primeiras etapas, as seções do módulo eram carregadas na memória (temporária) do kernel e sabia-se também quais eram persistentes e quais poderiam ser removidas. A próxima etapa (7) é alocar a locação final para o módulo na memória e mover as seções necessárias (indicadas nos cabeçalhos do ELF por SHF_ALLOC ou as seções que ocupam memória durante a execução). Outra alocação é então executada do tamanho necessário para as seções do módulo solicitadas. Cada seção no bloco ELF temporário é novamente iterada e as que precisam estar por perto para execução são copiadas no novo bloco. Em seguida, uma manutenção adicional é executada. Também ocorre a resolução de símbolo, que pode resultar em símbolos residentes no kernel (compilados na imagem do kernel) ou símbolos provisórios (exportados de outros módulos).

O novo módulo é, então, iterado para cada seção restante e são efetuadas relocações. Esta etapa depende da arquitetura e, portanto, conta com as funções de ajuda definidas para essa arquitetura (./linux/arch/<arch>/kernel/module.c). Finalmente, o cache de instruções é esvaziado (porque foram utilizadas as seções temporárias .text), mais manutenção é executada (libera a memória temporária do módulo, configura o sysfs) e o módulo retorna, enfim, para load_module.


Detalhes do Descarregamento do Módulo

O descarregamento do módulo é, essencialmente, um espelho do processo de carregamento, exceto pela ocorrência obrigatória de várias verificações de erros que asseguram uma remoção segura do módulo. O descarregamento de um módulo começa em espaço de usuário com a chamada do comando rmmod (remova o módulo). Dentro do comando rmmod, uma chamada de sistema é feita para delete_module, que eventualmente resulta em uma chamada para sys_delete_module dentro do kernel (lembre-se da Figura 3). A Figura 5 ilustra a operação básica do processo de remoção do módulo.

Figura 5. O Processo Interno (Simplificado) de Descarregamento de Módulo
O Processo de Descarregamento do Módulo Interno (Simplificado)

Quando a função do kernel sys_delete_module é chamada (com o nome do módulo a ser removido, transmitido como o argumento), a primeira etapa é assegurar que o responsável pela chamada tenha permissões. Em seguida, verifica-se uma lista para ver se outros módulos dependem deste módulo. Ali existe uma lista chamada modules_which_use_me que contém um elemento por módulo dependente. Se a lista estiver vazia, não existem dependências do módulo, que passa a ser um candidato à remoção (caso contrário, é retornado um erro). O próximo teste é verificar se o módulo está carregado. Nada proíbe um usuário de chamar rmmod em um módulo que esteja sendo instalado no momento, assim, essa verificação garante que o módulo se encontra ativo. Depois de mais algumas verificações de manutenção, a penúltima etapa é chamar a função de saída do módulo (fornecida dentro do próprio módulo). Finalmente, a função free_module é chamada.

Quando free_module é chamado, o módulo já pode ser removido removido com segurança. Agora não há dependências para o módulo e o processo de limpeza do kernel pode começar para esse módulo. Esse processo inicia-se pela remoção do módulo das várias listas em que foi colocado durante a instalação (sysfs, lista de módulos, etc). Em seguida, uma rotina de limpeza específica da arquitetura é chamada (pode ser encontrada em ./linux/arch/<arch>/kernel/module.c). Em seguida, faça a iteração dos módulos que dependem de você e remova esse módulo de suas listas. Finalmente, com a limpeza concluída—da perspectiva do kernel,—as diversas memória alocadas para o módulo são liberadas, inclusive a memória de argumento, a memória por CPU e as memórias ELF do módulo (core e init).


Otimizando o Kernel para o Gerenciamento de Módulo

Em muitas aplicações, a necessidade de carregamento dinâmico dos módulos é importante, mas, quando carregados, os módulos não precisam ser descarregados. Isso permite que o kernel seja dinâmico na inicialização (carrega os módulos com base nos dispositivos encontrados), mas não seja dinâmico durante a operação. Se não for necessário o descarregamento de um módulo após o carregamento, será possível fazer várias otimizações para reduzir a quantidade de código necessária ao gerenciamento de módulo. É possível "desconfigurar" a opção de configuração do kernel CONFIG_MODULE_UNLOAD para remover uma quantidade considerável de funcionalidades do kernel relacionadas ao descarregamento do módulo.


Indo Além

Esta foi uma visualização de alto nível do processo de gerenciamento de módulo no kernel. Para detalhes mais complexos do gerenciamento de módulo, a melhor documentação é o próprio código fonte. Para as funções principais envolvidas no gerenciamento de módulo, consulte ./linux/kernel/module.c (e o arquivo de cabeçalho associado em ./linux/include/linux/module.h). É possível encontrar diversas funções específicas da arquitetura em ./linux/arch/<arch>/kernel/module.c. Por fim, é possível ver a função de carregamento automático do kernel (que carrega automaticamente um módulo a partir do kernel, com base em sua necessidade) em ./linux/kernel/kmod.c. Este recurso é ativado através da opção de configuração CONFIG_KMOD.

Recursos

Aprender

  • Acompanhe o blog de Rusty Russell "Bleeding Edge" sobre seus atuais desenvolvimentos do kernel Linux. Rusty é um destacado desenvolvedor da nova arquitetura de módulo Linux.
  • O Linux Kernel Module Programming Guide, embora um pouco desatualizado, oferece uma grande quantidade de informações detalhadas sobre os LKMs e seu desenvolvimento.
  • Consulte "Access the Linux Kernel using the /proc filesystem" (developerWorks, março de 2006) para obter uma visão detalhada sobre a programação LKM com o sistema de arquivos /proc.
  • I Saiba mais sobre os detalhes por trás das chamadas de sistema em "Kernel command using Linux system calls" (developerWorks, março de 2007).
  • Para saber mais sobre o kernel Linux, leia "Anatomia de Kernel Linux de Tim (developerWorks, junho de 2007), o primeiro artigo desta série, para ter uma visão geral de alto nível do kernel Linux, juntamente com alguns de seus pontos mais interessantes.
  • Leia uma ótima introdução ao ELF em "Standards and specs: An unsung hero: the hardworking ELF" (developerWorks, dezembro de 2005). O ELF é o formato de objeto padrão para Linux. Ele é um formato de arquivo flexível que abrange imagens executáveis, objetos, bibliotecas compartilhadas e até mesmo core dumps. Também é possível encontrar informações mais detalhadas nesta referência de formato (documento PDF) e no livro detalhado sobre formatos ELF.
  • O Captain's Universe oferece uma ótima introdução à construção do LKM com makefiles de amostra. O processo para construir LKMs mudou (para melhor) com o kernel versão 2.6.
  • Há um pequeno número de utilitários de módulo para inserir, remover e gerenciar módulos. Os módulos são inseridos no kernel com o comando insmod e removidos com o comando rmmod. Para consultar os módulos atualmente no kernel, utilize o comando lsmod. Como os módulos podem depender da presença de outros módulos, o comando depmod está disponível para criar um arquivo de dependência. Para carregar automaticamente os módulos dependentes antes do módulo de interesse, é possível utilizar o comando modprobe (um wrapper sobre insmod). Por fim, é possível ler as informações do módulo de um LKM utilizando o comando modinfo.
  • O artigo de Linux Journal, "Linkers and Loaders" (novembro de 2002) oferece uma ótima introdução aos objetivos além dos ligadores e carregados usando arquivos ELF (inclusive resolução e relocação de símbolos).
  • Na zona Linux do developerWorks, encontre mais recursos para desenvolvedores Linux e varra nossos artigos e tutoriais mais conhecidos.
  • Consulte todas as dicas de Linux e tutoriais de Linux no developerWorks.
  • Fique atualizado com eventos técnicos e Webcasts do developerWorks.

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=382584
ArticleTitle=Anatomia dos Módulos Kernel Carregáveis do Linux
publish-date=07162008