Linux Makefile analysis for plain usr

时间:2023-12-30 11:49:38

一、本文主旨

  笔者写了一篇linux内核Makefile整体分析 ,测重于理论分析,对于实际应用不算对头,所以需要写一篇实用性较强的文章,为以后内核、驱动移植做好铺垫。

二、本文内容概要

1、编译哪些文件

2、怎样编译这些文件

3、怎样连接这些文件,它们的顺序如何

三、编译哪些文件

  本文的实验源码是对“linux-2.6.30.4”进行移植后的运行在TQ2440开发板上的源码包。

1、顶层Makefile决定内核根目录下哪些子目录被编进内核  

Line :
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/ Line :
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

  编译内核时,将依次通过调用scripts/Makefile.build进入init-y、core-y、libs-y、drivers-y、net-y所列出的目录中配合它们的Makefile进行编译。每个子目录都会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件)。

2、arch/$(ARCH)/Makefile决定与架构有关目录下的哪些文件、哪些目录被编译内核

  以ARM体系为例,在arch/arm/Makefile中可以看到如下内容:

Line :
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o Line :
# If we have a machine-specific directory, then include it in the build.
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += $(machdirs) $(platdirs)
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
core-$(CONFIG_VFP) += arch/arm/vfp/ Line 204:
libs-y:= arch/arm/lib/ $(libs-y)

  head-y,MMUEXT在arch/arm/Makefile前面定义,对于没有MMU的处理器,MMUEXT的值为-nommu,使用文件head-nommu.S;对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。

  arch/arm/Makefile中从第195行开始扩展了core-y的内容,第204行扩展了libs-y的内容,这些都是体系结构相关的目录。CONFIG_FPE_NWFPE在配置内核时定义,它的值有三种:y、m或空。y表示编进内核,m表示编为模块,空代表不使用。

  编译内核时,将head-y所表示的文件和顶层Makefile中定义的built-in.o、lib.a等文件一起连接生成内核映像文件vmlinux。

3、各级子目录下的Makefile决定所在目录下的哪些文件将被编进内核,哪些文件将被编成模块

  在配置内核时,生成.conf文件。顶层Makefile和scripts/Makefile.build间接包含.config文件,.config中定义的各个变量决定编译哪些文件。之所以说是“间接”包含,是因为包含的是include/config/auto.conf文件。

顶层Makefile Line :
-include include/config/auto.conf scripts/Makefile.build Line :
-include include/config/auto.conf

  include/config/auto.conf文件的生成过程不再描述,韦东山书上说:Linux 2.6.22.6版本的内核的auto.conf文件,是将.conf文件中的注释去掉,并根据顶层Makefile中定义的变量增加了一些变量生成的。据我观察,linux-2.6.30.4版本的内核的auto.conf文件就是将.conf文件中的注释去掉而已,没有添加变量。摘选部分内容如下:

Line : CONFIG_ARCH_S3C2410=y
Line : CONFIG_ARCH_S3C2440=y
Line : CONFIG_LOCALVERSION="-EmbedSky" Line : CONFIG_ARM=y
Line : CONFIG_ARCH_TQ2440=y

  在include/config/auto.conf文件中,变量的值主要有两类:y、m。各级子目录的Makefile就是使用这些变量来决定哪些文件被编进内核,哪些文件被编译成模块(即驱动程序)。

①obj-y用来指定哪些文件被编进内核(built-in)

  obj-y中定义的.o文件是由当期那目录下的.c或.S文件编译生成的,它们连同下级子目录下的built-in.o文件一起被组合成(使用$(LD) -r命令)当前目录下的built-in.o文件。这个built-in.o文件将被它的上一层Makefile使用。

  obj-y中各个.o文件的顺序是有意义的,因为内核中使用module_init()或__initcall定义的函数将按照它们的连接顺序被调用。

例如:当下面的CONFIG_ISDN、CONFIG_ISDN_PPP_BSDCOMP在.config中被定义为y时,isdc.c或isdn.S、idsn_bsdcomp.c或isdn_bsdcomp.S被编译成isdn.o、isdn_bsdcomp.o。这两个.o文件被组合成built-in.o文件中,最后被连接进入内核。假如isdn.o、isdn_bsdcomp.o中分别用module_init(A)、module(B)定义了函数A、B,则内核启动时A先被调用,然后才是B。

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

②obj-m用来定义哪些文件被编译成可加载模块(Loadable module)

  obj-m中定义的.o文件由当前目录下的.c或者.S文件生成,它们不会被编进内核built-in.o,而是被编成可加载模块。

一个模块可以由一个或者几个.o文件组成。

  对于只有一个源文件的模块,在obj-m中直接增加它的.o文件即可。

  对于由多个源文件组成的模块,除在obj-m中增加一个.o文件外,还要定义一个<module_name>-objs变量来告诉Makefile这个.o文件由哪些文件组成。

例1:当下面的CONFIG_ISDN_PPP_BSDCOMP在.cofig文件中被定义为m时,isdn_bsdcomp.c或isdn_bsdcomp.S将被编译成isdn_bsdcomp.o文件,它最后被制作成isdn_bsdcomp.ko模块,如下所示:

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

例2:当下面的CONFIG_ISDN在.config文件中被定义为m时,将会生成一个isdn.o文件,它由isdn-objs中定义的isdn_net_lib.o、isdn_v110.o、isdn_common.o等三个文件组合而成。isdn.o文件最后被制作成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用来定义哪些文件被编成库文件

  lib-y中定义的.o文件由当前目录下的.c或者.S文件编译生成,它们被打包成当前目录下的库文件的内核代码一般都在这两个目录下:lib/、arch/$(ARCH)/lib/。

④ obj-y、obj-m还可以用来指定要进入的下一层子目录

  Linux中的一个Makefile文件只负责生成当前目录下的目标文件,子目录下的目标文件由子目录下的Makefile生成。Linux的编译系统会自动进入这些子目录调用它们的Makefile,只是在这之前指定这些目录。

  这要用到obj-y、obj-m,只要在其中增加这些目录名即可。

例如:fs/Makefile中有如下一行,当CONFIG_JFFS2_FS被定义为y或者m时,在编译时会进入jffs2目录进行编译。Linux的编译系统只会根据这些信息决定是否进入下一级目录,而下一级的文件如何编译成built-in.o或模块有它们的Makefile决定。

obj-$(CONFIG_JFFS2_FS) +=jffs2/

四、怎样编译这些文件

  即编译选项、连接选项是什么。这些选项分3类:全局的,适用于整个内核源码树;局部的,仅适用于某个Makefile中的所有文件;个体的,仅适用于某个文件。

1、全局选项

  全局选项在顶层Makefile和arch/$(ARCH)/Makefile中定义,这些选项的名称为:CFLAGS、AFLAGS、LDFLAGS、ARFLAGS,它们分别是编译C文件的选项、编译汇编文件的选项、连接文件的选项、制作库文件的选项。

2、局部选项

  需要使用局部选项时,它们在各个子目录中定义,名称为:EXTRA_CFLAFS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS,它们的用途与前述选项相同。只是使用范围更小,只针对当前Makefile中的所有文件。

3、个体选项

  如果想只针对某个文件定义它的编译选项,可以使用CFLAGS_@,AFLAGS_@。前者由于编译某个C文件,后者用于编译某个汇编文件。@表示某个目标文件名,比如以下代码表示编译aha152x.c时,选项要额外加上“-DAHA152X_STAT -DAUTOCONF”。

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

  需要注意的是,这3类选项是一起使用的,在scripts/Makefile.lib中可以看到。

_c_flags  = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(basetarget).o)

五、怎样连接这些文件,它们的顺序如何

  在linux内核Makefile整体分析中的章节2、vmlinux的生成中详细讲了vmlinux的依赖,以及它们的顺序。

顶层Makefile Line :
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

  vmlinux-all表示所有构成内核映像的目标文件,它们的连接顺序为:head-y、init-y、core-y、libs-y、drivers-y、net-y,即arch/arm/kernel/head.o、arch/arm/kernel/init_task.o、init/built-in.o、usr/built-in.o等等。通过打印命令的方式显示它们完整的连接顺序。

root@daneiqi:/workspace/linux-EmbedSky# make uImage V=
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

  连接脚本为arch/$(ARCH)/kernel/vmlinux.lds。对于ARM体系,连接脚本就是arch/arm/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,生成规则在scripts/Makefile.build中,如下所示:

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

  先将生成的arch/arm/kernel/vmlinux.lds摘录如下:]

OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0xC0000000 + 0x00008000; /* 代码段起始地址,这是个虚地址 */
.text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
}
.init : { /* 内核初始化的代码和数据 */
*(.init.text) *(.cpuinit.text) *(.meminit.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN();
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .;
. = ALIGN();
__per_cpu_load = .;
__per_cpu_start = .;
*(.data.percpu.page_aligned)
*(.data.percpu)
*(.data.percpu.shared_aligned)
__per_cpu_end = .;
__init_begin = _stext;
*(.init.data) *(.cpuinit.data) *(.cpuinit.rodata) *(.meminit.data) *(.meminit.rodata)
. = ALIGN();
__init_end = .;
}
/DISCARD/ : { /* Exit code and data */
*(.exit.text) *(.cpuexit.text) *(.memexit.text)
*(.exit.data) *(.cpuexit.data) *(.cpuexit.rodata) *(.memexit.data) *(.memexit.rodata)
*(.exitcall.exit)
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
}
.text : { /* 真正的代码段 */
_text = .; /* 代码段和只读数据段的开始地址 */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
. = ALIGN(); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.text.unlikely)
. = ALIGN(); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
. = ALIGN(); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
. = ALIGN(); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
*(.got) /* Global offset table */
} /* 只读数据段 */
. = ALIGN(()); .rodata : AT(ADDR(.rodata) - ) { __start_rodata = .; *(.rodata) *(.rodata.*) *(__vermagic) *(__markers_strings) *(__tracepoints_strings) } .rodata1 : AT(ADDR(.rodata1) - ) { *(.rodata1) } .pci_fixup : AT(ADDR(.pci_fixup) - ) { __start_pci_fixups_early = .; *(.pci_fixup_early) __end_pci_fixups_early = .; __start_pci_fixups_header = .; *(.pci_fixup_header) __end_pci_fixups_header = .; __start_pci_fixups_final = .; *(.pci_fixup_final) __end_pci_fixups_final = .; __start_pci_fixups_enable = .; *(.pci_fixup_enable) __end_pci_fixups_enable = .; __start_pci_fixups_resume = .; *(.pci_fixup_resume) __end_pci_fixups_resume = .; __start_pci_fixups_resume_early = .; *(.pci_fixup_resume_early) __end_pci_fixups_resume_early = .; __start_pci_fixups_suspend = .; *(.pci_fixup_suspend) __end_pci_fixups_suspend = .; } .builtin_fw : AT(ADDR(.builtin_fw) - ) { __start_builtin_fw = .; *(.builtin_fw) __end_builtin_fw = .; } .rio_route : AT(ADDR(.rio_route) - ) { __start_rio_route_ops = .; *(.rio_route_ops) __end_rio_route_ops = .; } __ksymtab : AT(ADDR(__ksymtab) - ) { __start___ksymtab = .; *(__ksymtab) __stop___ksymtab = .; } __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - ) { __start___ksymtab_gpl = .; *(__ksymtab_gpl) __stop___ksymtab_gpl = .; } __ksymtab_unused : AT(ADDR(__ksymtab_unused) - ) { __start___ksymtab_unused = .; *(__ksymtab_unused) __stop___ksymtab_unused = .; } __ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - ) { __start___ksymtab_unused_gpl = .; *(__ksymtab_unused_gpl) __stop___ksymtab_unused_gpl = .; } __ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - ) { __start___ksymtab_gpl_future = .; *(__ksymtab_gpl_future) __stop___ksymtab_gpl_future = .; } __kcrctab : AT(ADDR(__kcrctab) - ) { __start___kcrctab = .; *(__kcrctab) __stop___kcrctab = .; } __kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - ) { __start___kcrctab_gpl = .; *(__kcrctab_gpl) __stop___kcrctab_gpl = .; } __kcrctab_unused : AT(ADDR(__kcrctab_unused) - ) { __start___kcrctab_unused = .; *(__kcrctab_unused) __stop___kcrctab_unused = .; } __kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - ) { __start___kcrctab_unused_gpl = .; *(__kcrctab_unused_gpl) __stop___kcrctab_unused_gpl = .; } __kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - ) { __start___kcrctab_gpl_future = .; *(__kcrctab_gpl_future) __stop___kcrctab_gpl_future = .; } __ksymtab_strings : AT(ADDR(__ksymtab_strings) - ) { *(__ksymtab_strings) } __init_rodata : AT(ADDR(__init_rodata) - ) { *(.ref.rodata) *(.devinit.rodata) *(.devexit.rodata) } __param : AT(ADDR(__param) - ) { __start___param = .; *(__param) __stop___param = .; . = ALIGN(()); __end_rodata = .; } . = ALIGN(());
_etext = .; /* 代码段和只读数据段的结束地址 */
. = ALIGN();
__data_loc = .;
.data : AT(__data_loc) { /* 数据段 */
_data = .; /* 数据段的起始地址 */
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
*(.data.init_task)
. = ALIGN();
__nosave_begin = .;
*(.data.nosave)
. = ALIGN();
__nosave_end = .;
/*
* then the cacheline aligned data
*/
. = ALIGN();
*(.data.cacheline_aligned)
/*
* The exception fixup table (might need resorting at runtime)
*/
. = ALIGN();
__start___ex_table = .;
*(__ex_table)
__stop___ex_table = .;
/*
* and the usual data section
*/
*(.data) *(.ref.data) *(.devinit.data) *(.devexit.data) . = ALIGN(); __start___markers = .; *(__markers) __stop___markers = .; . = ALIGN(); __start___tracepoints = .; *(__tracepoints) __stop___tracepoints = .; . = ALIGN(); __start___verbose = .; *(__verbose) __stop___verbose = .;
CONSTRUCTORS
_edata = .;/* 数据段的结束地址 */
}
_edata_loc = __data_loc + SIZEOF(.data);/* 数据段的结束地址 */
.bss : { /* bss段 */
__bss_start = .; /* bss段起始地址 */
*(.bss)
*(COMMON)
_end = .; /* bss段结束地址 */
}
/*调试信息段 */
.stab : { *(.stab) }
.stabstr : { *(.stabstr) }
.stab.excl : { *(.stab.excl) }
.stab.exclstr : { *(.stab.exclstr) }
.stab.index : { *(.stab.index) }
.stab.indexstr : { *(.stab.indexstr) }
.comment : { *(.comment) }
}
/*
* These must never be empty
* If you have to comment these two assert statements out, your
* binutils is too old (for other reasons as well)
*/
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")

六、总结

(1)顶层Makefile和arch/arm/Makefile决定根目录下哪些子目录、arch/arm目录下哪些文件和目录被编进内核

(2)配置文件.conf中定义了一系列的变量,子目录的Makefile将结合它们来决定子目录下哪些文件被编进内核、哪些文件被编成模块、涉及及哪些子目录。

(3)顶层Makefile和arch/$(ARCH)/Makefile设置了可以影响所有文件的编译、连接选项:CFLAGS、AFLAGS、LDFLAGS、ARFLAGS。

(4)各级子目录下的Makefile中设置能够影响当前目录下所有文件的编译、连接选项:EXTRA_CFLAFS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS;还可以设置影响某个文件的编译选项:CFLAFS_@、 AFLAGS_@。

(5)顶层Makefile按照一定的顺序组织文件,根据连接脚本arch/$(ARCH)/kernel/vmlinux.lds生成内核映像文件vmlinux。

参考:大部分摘录于《嵌入式Linux应用开发完全手册》

    linux2.6内核Makefile详解(documention/kbuild/makefiles.txt的中文版翻译)