Análise de Makefiles do Kernel Linux

Este artigo apresenta uma análise prática dos Makefiles do kernel Linux, focando na aplicação prática para preparar o terreno futuro de portabilidade de kernel e drivers.

Sumário do Conteúdo

  1. Quais arquivos são compilados
  2. Como esses arquivos são compilados
  3. Como esses arquivos são ligados e sua ordem

Quais Arquivos São Compilados

Os exemplos deste artigo baseiam-se no código fonte do kernel "linux-2.6.30.4" adaptado para execução na placa de desenvolvimento TQ2440.

1. Makefile de nível superior determina quais subdiretórios são compilados no kernel


Linha 477: 
inicial-y        := init/
drivers-y    := drivers/ sound/ firmware/
rede-y        := net/
bibliotecas-y    := lib/
core-y        := usr/

Linha 650: 
core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

Durante a compilação do kernel, o sistema invoca recursivamente o scripts/Makefile.build para cada diretório listado em inicial-y, core-y, bibliotecas-y, drivers-y e rede-y, processando seus respectivos Makefiles. Cada subdiretório resultará em um arquivo built-in.o (ou possivelmente lib.a para diretórios sob bibliotecas-y).

2. Makefile de arch/$(ARCH)/ determina quais arquivos e diretórios específicos da arquitetura são compilados

Para a arquitetura ARM, o arquivo arch/arm/Makefile contém as seguintes definições:


Linha 100:
cabeca-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o

Linha 195:
# Se houver um diretório específico da máquina, inclua-o na compilação.
core-y                += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y                += $(diretorias_maquina) $(diretorias_plataforma)
core-$(CONFIG_FPE_NWFPE)    += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE)    += $(OBJETOS_FASTFPE)
core-$(CONFIG_VFP)        += arch/arm/vfp/

Linha 204: 
bibliotecas-y:= arch/arm/lib/ $(bibliotecas-y)

As variáveis cabeca-y e MMUEXT são definidas anteriormente em arch/arm/Makefile. Para processadores sem MMU, MMUEXT tem valor "-nommu", usando o arquivo head-nommu.S; para processadores com MMU, MMUEXT está vazio, usando head.S.

A partir da linha 195 em arch/arm/Makefile, o conteúdo de core-y é expandido, e na linha 204 o conteúdo de bibliotecas-y é estendido com diretórios específicos da arquitetura. O valor de CONFIG_FPE_NWFPE é definido durante a configuração do kernel e pode ser y (compilado no kernel), m (como módulo) ou vazio (não utilizado).

Durante a compilação, os arquivos indicados por cabeca-y e os built-in.o e lib.a definidos no Makefile de nível superior são ligados juntos para gerar o arquivo de imagem do kernel vmlinux.

3. Makefiles de subdiretórios determinam quais arquivos são compilados no kernel e quais como módulos

Ao configurar o kernel, é gerado um arquivo .config. O Makefile de nível superior e scripts/Makefile.build incluem indiretamente o arquivo .config, onde as variáveis definidas determinam quais arquivos são compilados. A inclusão é indireta porque o arquivo incluído é include/config/auto.conf.


Makefile de nível superior Linha 486: 
-include include/config/auto.conf

scripts/Makefile.build Linha 34: 
-include include/config/auto.conf

O processo de geração do arquivo include/config/auto.conf não será descrito aqui. Este arquivo é basicamente o .config sem comentários. Alguns exemplos:


Linha 36: CONFIG_ARQUI_S3C2410=y
Linha 40: CONFIG_ARQUI_S3C2440=y
Linha 253: CONFIG_VERSAO_LOCAL="-EmbedSky"

Linha 384: CONFIG_ARM=y
Linha 407: CONFIG_PLATA_TQ2440=y

Nos arquivos Makefile dos subdiretórios, as variáveis com valores y ou m em include/config/auto.conf determinam quais arquivos são compilados no kernel e quais como módulos (drivers).

① obj-y especifica quais arquivos são compilados no kernel (built-in)

Os arquivos .o definidos em obj-y são gerados a partir de arquivos .c ou .S no diretório atual. Eles são combinados com os built-in.o dos subdiretórios usando o comando $(LD) -r para formar o built-in.o do diretório atual, que será usado pelo Makefile do nível superior.

A ordem dos arquivos em obj-y é significativa, pois as funções definidas com module_init() ou __initcall são chamadas na ordem de ligação.

Por exemplo, quando CONFIG_ISDN e CONFIG_ISDN_PPP_BSDCOMP são definidos como y em .config, isdc.c ou isdn.S e isdn_bsdcomp.c ou isdn_bsdcomp.S são compilados em isdn.o e isdn_bsdcomp.o. Estes arquivos são combinados no built-in.o e finalmente ligados ao kernel. Se isdn.o e isdn_bsdcomp.o contiverem funções A e B definidas com module_init(A) e module_init(B) respectivamente, A será chamada primeiro durante a inicialização do kernel, seguida por B.


obj-$(CONFIG_ISDN)            += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP)    +=isdn_bsdcomp.o

② obj-m define quais arquivos são compilados como módulos carregáveis

Os arquivos .o definidos em obj-m são gerados a partir de arquivos .c ou .S no diretório atual, mas não são incluídos no built-in.o do kernel. Em vez disso, são compilados como módulos carregáveis (.ko).

Um módulo pode consistir em um ou vários arquivos .o.

Para módulos com um único arquivo de origem, basta adicionar seu .o em obj-m.

Para módulos com vários arquivos de origem, além de adicionar um .o em obj-m, é necessário definir uma variável <nome_do_m>-objs para informar ao Makefile quais arquivos compõem esse .o.</nome_do_m>

Exemplo 1: Quando CONFIG_ISDN_PPP_BSDCOMP é definido como m em .config, isdn_bsdcomp.c ou isdn_bsdcomp.S é compilado em isdn_bsdcomp.o, que é então transformado no módulo isdn_bsdcomp.ko:


#drivers/isdn/i41/Makefile:
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

Exemplo 2: Quando CONFIG_ISDN é definido como m em .config, é gerado um arquivo isdn.o composto por isdn_net_lib.o, isdn_v110.o e isdn_common.o conforme definido em isdn-objs. O arquivo isdn.o é então transformado no módulo isdn.ko:


#drivers/isdn/i41/Makefile
obj-$(CONFIG_ISDN) +=isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

③ lib-y define quais arquivos são compilados como bibliotecas

Os arquivos .o definidos em lib-y são gerados a partir de arquivos .c ou .S no diretório atual e são empacotados em bibliotecas. O código de kernel geralmente está nestes dois diretórios: lib/ e arch/$(ARCH)/lib/.

④ obj-y e obj-m também podem especificar subdiretórios a serem processados

Um Makefile do kernel é responsável apenas por gerar os arquivos de destino no diretório atual. Os arquivos de destino nos subdiretórios são gerados pelos Makefiles desses subdiretórios. O sistema de compilação do kernel entra automaticamente nesses diretórios para chamar seus Makefiles, desde que eles sejam especificados.

Isso é feito com obj-y e obj-m, adicionando simplesmente os nomes dos diretórios.

Por exemplo, em fs/Makefile:


obj-$(CONFIG_JFFS2_FS) +=jffs2/

Quando CONFIG_JFFS2_FS é definido como y ou m, o sistema de compilação entrará no diretório jffs2 durante a compilação. Como os arquivos são compilados em built-in.o ou módulos é determinado pelos respectivos Makefiles.

Como Compilar Esses Arquivos

As opções de compilação e ligação são classificadas em três categorias: globais (aplicadas a toda a árvore de código do kernel), locais (aplicadas apenas a todos os arquivos em um Makefile específico) e individuais (aplicados apenas a um arquivo específico).

1. Opções Globais

As opções globais são definidas no Makefile de nível superior e em arch/$(ARCH)/Makefile. Seus nomes são: CFLAGS, AFLAGS, LDFLAGS, ARFLAGS, que são respectivamente opções de compilação para arquivos C, opções de compilação para arquivos assembly, opções de ligação e opções de criação de bibliotecas.

2. Opções Locais

Quando são necessárias opções locais, elas são definidas em cada subdiretório com os nomes: EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS. Seus usos são idênticos às opções globais, mas com escopo restrito a todos os arquivos no Makefile atual.

3. Opções Individuais

Para definir opções de compilação específicas para um arquivo, podem ser usadas CFLAGS_@ e AFLAGS_@, onde @ representa o nome do arquivo de destino. Por exemplo, o código a seguir indica que ao compilar aha152x.c, devem ser adicionadas as opções "-DAHA152X_STAT -DAUTOCONF":


#drivers/scsi/Makefile
CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF

É importante notar que essas três categorias de opções são usadas juntas, como pode ser visto em scripts/Makefile.lib:


_opcoes_c  = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(alvo_base).o)

Como Ligar Esses Arquivos e Sua Ordem

No artigo "Análise Geral dos Makefiles do Kernel Linux", a seção 2 detalha as dependências de vmlinux e sua ordem de ligação:


Makefile de nível superior Linha 696:
vmlinux-inicial := $(cabeca-y) $(inicial-y)
vmlinux-principal := $(core-y) $(bibliotecas-y) $(drivers-y) $(rede-y)
vmlinux-todos  := $(vmlinux-inicial) $(vmlinux-principal)
vmlinux-lds  := arch/$(ARQUI)/kernel/vmlinux.lds

vmlinux-todos representa todos os arquivos de destino que compõem a imagem do kernel. Sua ordem de ligação é: cabeca-y, inicial-y, core-y, bibliotecas-y, drivers-y, rede-y, ou seja, arch/arm/kernel/head.o, arch/arm/kernel/init_task.o, init/built-in.o, usr/built-in.o, etc. A ordem completa pode ser exibida com o comando V=1:


root@placa:/workspace/linux-EmbedSky# make uImage V=1
  arm-linux-ld -EL  -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o 
  arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o  arch/arm/kernel/built-in.o  
  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/mach-s3c2410/built-in.o  
  arch/arm/mach-s3c2400/built-in.o  arch/arm/mach-s3c2412/built-in.o  arch/arm/mach-s3c2440/built-in.o  
  arch/arm/mach-s3c2442/built-in.o  arch/arm/mach-s3c2443/built-in.o  arch/arm/plat-s3c24xx/built-in.o  
  arch/arm/plat-s3c/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  
  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  
  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  net/built-in.o --end-group .tmp_kallsyms2.o

O script de ligação é arch/$(ARCH)/kernel/vmlinux.lds. Para a arquitetura ARM, o script é arch/arm/kernel/vmlinux.lds, gerado a partir do arquivo arch/arm/kernel/vmlinux.lds.S. A regra de geração está em scripts/Makefile.build:


Linha 268: 
$(obj)/%.lds: $(src)/%.lds.S FORCE
    $(call if_changed_dep,cpp_lds_S)

O arquivo arch/arm/kernel/vmlinux.lds define as seções da memória e a ordem de ligação dos objetos. Ele começa definindo a arquitetura de saída (OUTPUT_ARCH), o ponto de entrada (ENTRY) e o endereço inicial da seção .text. Em seguida, define várias seções como .text.head, .init, .text, .rodata, .data, .bss, etc., cada uma com suas características específicas.

As seções .init contêm código e dados de inicialização, enquanto .text contém o código principal do kernel. A seção .ro armazena dados somente leitura, .data armazena dados inicializados, e .bss armazena dados não inicializados.

O script também contém verificações de asserção para garantir que registros de CPU e máquina estejam presentes:


ASSERT((__proc_info_end - __proc_info_begin), "falta suporte a CPU")
ASSERT((__arch_info_end - __arch_info_begin), "nenhum registro de máquina definido")

Tags: kernel Linux makefiles compilacao arquitetura arm desenvolvimento de drivers

Publicado em 7-26 19:10