内核源码结构
Linux内核文件数目将近2万个,除去其他架构CPU的相关文件,支持S3C2410,S3C2440这两款芯片的完整内核文件有1万多个。这些文件的组织结构并不复杂,它们分别位于顶层目录下的17个子目录,各个目录功能独立。如下表
目录名 | 描述 |
---|
arch | 体系结构相关代码,对于每个架构的CPU,arch目录下有一个对应的子目录,比如arch/arm,arch/i386 | block | 块设备的通用函数 | crypto | 常用加密和散列算法(如AES,SHA等),还有一些压缩和CRC校验算法 | drivers | 所有的设备驱动程序,里面每一个子目录对应一类驱动程序,比如drivers/block为块设备驱动程序,drivers/char为字符设备驱动程序,drivers/mtd为NOR Flash,NAND Flash等存储设备的驱动程序。 | fs | Linux支持的文件系统代码,每个子目录对应一种文件系统,比如fs/jffs2,fs/ext2,fs/ext3 | include | 内核头文件,有基本头文件(存放在include/linux/目录下),各种驱动设备或功能部件的头文件(比如icnlude/media,include/mtd,include/net),各种体系相关的头文件(比如 include/asm-arm,include/asm-i386)。当配置内核后,include/asm是某个include/asm-xxx/(比如include/asm-asm)的链接 | init | 内核的初始化代码(不是系统的引导代码),其中的main.c文件中的start_kernel函数是内核引导后运行的第一个函数 | ipc | 进程间通信的代码 | Kernel | 内核管理的核心代码,与处理器相关的代码位于arch/*/kernel目录下 | lib | 内核用到的一些库函数代码,比如crc32.c,string.c,与处理器相关的库函数代码位于arch/*/lib目录下 | mm | 内存管理代码,与处理器相关的内存管理代码位于arch/*/mm目录下 | net | 网络支持代码,每个子目录对应于网络的一个方面 | security | 安全,密钥相关的代码 | sound | 音频设备的驱动程序 | usr | 用来制作一个压缩cpio归纳文档;initrd的镜像,它可以作为内核启动后挂接(mount)的第一个文件系统(一般用不到) | Documentation | 内核文档 | scipts | 用于配置,编译内核的脚本文件 |
对于ARM架构的S3C2410,S3C2440,其体系相关的代码在arch/arm目录下,在后面进行Linux移植时,开始的工作正是修改这个目录下的文件。如下图所示为内核代码的层次结构。 有点长,给大家展示参考书籍的原图
Linux Makfile分析
内核中的那些文件将被编译?它们是怎样被编译的?它们连接时的顺序如何确定?那个文件在最前面?那些文件或函数先执行?这些都是通过Makefile来管理的。从最简单的角度来总结Makefile的作用,有以下3点。
- 决定编译那些文件
- 怎样编译这些文件
- 怎样连接这些文件,最重要的是它们的顺序如何?
Linux内核源码中含有很多个Makefile文件,这些Makefile文件又要包含其他一些文件(比如配置信息,通用规则等。)这些文件构成了Linux的Makefile体系,可以分为如下五类
名称 | 描述 |
---|
顶层Makefile | 它是所有Makefile文件的核心,从总体上控制着内核的编译,连接 | .config | 配置文件,在配置内核时生成。所有Makefile文件(包括顶层目录及各级子目录)都是根据.config来决定使用那些文件 | arch/$(ARCH)/Makefile | 对应体系结构的Makefile,它用来决定那些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映象 | scripts/Makefile.* | Makefile共用的通用规则,脚本等 | kbuild Makefiles | 各级子目录下的Makefile,它们相对简单,被上一层Makefile调用来编译当前目录下的文件 |
内核文档Documentation/kbuild/makefiles.txt对内核中Makefile的作用,用法讲解得非常透彻,以下根据前面总结的Makefile的3大作用分析这5类文件。
决定编译那些文件
Linux内核的编译过程从顶层Makefile开始,然后递归地进入各级子目录调用它们的Makefile,分为3个步骤。
- 顶层Makefile决定内核根目录下那些子目录将被编进内核。
- arch/$ (ARCH)/Makefile决定arch/$ (ARCH)目录下那些文件,那些目录将被编译到内核。
- 各级子目录下的Makefile决定所在目录下那些文件将被编进内核,那些文件将被编成模块(即驱动程序),进入那些子目录继续调用它们的Makefile。
先看步骤1,在顶层Makefile中可以阿看到如下内容 可见,顶层Makefile将这13个子目录分为5类;init-y、drivers-y、net-y、libs-y和core-y。上面的表格中有17个子目录,除去include目录和后面两个不包含内核代码的目录外,还有一个arch目录没有出现在内核中。它在arch/$(ARCH)/Makefile中被包含进内核,在顶层Makefile中直接包含了这个Makefile,如下: 对于ARCH变量,可以在执行make命令时传入,比如“make ARCH=arm …”。另外,对于非X86平台,还需要指定交叉编译工具,这也可以在执行make命令时传入,比如“make CROSS_COMPILE=arm-linux…”。为了方便,常在顶层Makefile中进行如下修改。 修改前: 修改后: 对于步骤2的 arch/ $(ARCH)/Makefile,以ARM体系为例,在arch/arm/Makefile中可以看到如下内容: 从第94行可知,除前面的5类子目录外,又出现了另一类:head-y,不过它直接以文件名出现。MMUEXT在arch/arm/Makefile前面定义,对于没有MMU的处理器。MMUEXT的值为-nommu,使用文件head-nommu.S;对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。 arch/arm/Makefile/中类似第171,172,173行的代码进一步扩展了core-y的内容,第191行扩展了libs-y的内容,这些都是体系结构相关的目录。第173~175行中的CONFIG_ARCH_S3C2410在配置内核时定义,它的值有3种:y、m或空。y表示编进内核,m表示编进模块,空表示不使用。 编译内核时,将依次进入init-y、core-y、libs-y、drivers-y和net-y所列出的目录中执行它们的Makefile,每个子目录都会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件)。最后,head-y所表示的文件将和这些built-in.o、lib.a一起被连接成内核映象文件vmlinux。 最后,看一下步骤3是怎么进行的。 在配置内核时,生成配置文件.config(具体的配置过程在后面进行讲解)。内核顶层Makefile使用如下语句间接包含.config文件,以后就根据.config中定义的各个变量决定编译那些文件。之所以说是"间接"包含,是因为包含的是include/config/auto.conf文件,而它只是将.config文件中的注释去掉,并根据顶层Makefile中定义的变量增加一些变量而已。 include/config/auto.conf文件的生成过程不再描述,它与.config的格式相同,摘选部分内容如下(注意,下面以“#”开头的行是本书加的注释):
CONFIG_ARCH_SMDK2410=y
CONFIG_ARCH_S3C2440=y
# .config中没有下面这行,它是根据顶层Makefile中定义的内核版本号增加的
CONFIG_KERNELVERSION="2.6.22"
#.config中没有下面这行,它是根据顶层Makefile中定义的ARCH变量增加的。
CONFIG_ARCH="arm"
CONFIG_JFFS2_FS=y
CONFIG_LEDS_S3C24XX=m
在include/config/auto.conf文件中,变量的值主要有两类:“y"和"m”。各级子目录的Makefile使用这些变量来决定那些文件被编进内核中,那些文件被编成模块(即驱动程序),要进入那些下一级子目录继续编译,这通过以下4种方法来确定(obj-y,obj-m,lib-y是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定义的函数将按照它们的连接顺序被调用。
例1
当下面的CONFIG_ISDN、CONFIG_ISDN_PPP_BSDCOMP在.config中被定义为y时,isdn.c或isdn.S,isdn_bsdcomp.c或isdn_bsdcomp.S被编译成isdn.o、isdn_bsdcomp.o。这两个.o文件被组合进built-in.o文件中,最后被连接进入内核。假如isdn.o、isdn_bsdcomp.o中分别用module_init(A)、module_init(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文件由那些文件组成。
例2
当下面的CONFIG_ISDN_PPP_BSDCOMP在.config文件中被定义为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
例3
当下面的CONFIG_ISDN在.config文件中被定义为m时,将会生成一个isdn.o文件,它由isdn-objs中定义的isdn_net_lib.o、isdn_v110.o、isdn_common.o等3个文件组合而成。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.a。 同时出现在obj-y,lib-y中的.o文件,不会被包含进lib.a中。 要把这个lib.a编进内核中,需要在顶层Makefile中libs-y变量中列出当前目录。要编成库文件的内核代码一般都在这两个目录下:lib/、arch/$(ARCH)/lib/。
obj-y、obj-m还可以用来指定要进入的下一层目录。
Linux中一个Makefile文件只负责生成当前目录下的目标文件,子目录下的目标文件由子目录的Makefile生成。Linux的编译系统会自动进入这些子目录调用它们的Makefile,只是在这之前指定这些子目录。 这里要用到obj-y,obj-m,只要在其中增加这些子目录名即可。
例4
fs/Makefile中有如下一行,当CONFIG_JFFS2_FS被定义为y或m时,在编译时将会进入jffs2/目录进行编译。Linux的编译系统只会根据这些信息决定是否进入下一级目录,而下一级目录中的文件如何编译成built-in.o或模块由它的Makefile决定。
怎样编译这些文件
即编译选项,连接选项是什么,这些选项分3类:全局的,适用于整个内核代码树;局部的,仅适用于某个Makefile中的所有文件;个体的,仅适用于某个文件。 全局选项在顶层Makefile和arch/$(ARCH)/Makefile中定义,这些选项的名称为:CFLAGS、AFLAGS、LDFLAGS、ARFLAGS,它们分别是编译C文件的选项,编译汇编文件的选项,连接文件的选项,制作库文件的选项。 需要使用局部选项时,它们在各个子目录下定义,名称为:EXTRA_CFLAGS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS,它们的用途与前述选项相同,只是使用范围比较小,它们针对当前Makefile中的所有文件。 另外,如果想针对某个文件定义它的编译选项,可以使用CFLAGS_ $@、AFLAGS_ $@。前者用于编译某个C文件,后者用于编译某个汇编文件。 $@表示某个目标文件名。比如以下代码表示编译ahal152x.c时,选项中要额外加上“-DAHA152X_STAT-DAUTOCONF”。
#drivers/scsi/Makefile
CFLAGS_ahal152x.o = -DAHA152X_STAT -DAUTOCONF
需要注意的是,这3类选项是一起使用的,在scripts/Makefile.lib中可以看到。
_c_flags =$(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(basetarget).o)
怎样连接这些文件,它们的顺序如何。
前面分析有那些文件要编译进内核时,顶层Makefile和arch/$(ARCH)/Makefile定义了6类目录(或文件):head-y、init-y、drivers-y、net-y、libs-y和core-y。它们的初始值如下(以ARM体系为例) arch/arm//Makfile中 顶层Makefile中
可见,除head-y,其余的init-y,drivers-y等都是目录名。在顶层Makefile中,这些目录名的后面直接加上built-in.o或lib.a,表示要连接进内核的文件,如下所示
上面的patsubst是个字符串处理函数,它的用法如下:
$(patsubst pattern ,replacement, text)
表示寻找“text”中符合格式“pattern”的字,用“replacement”替换它们。比如上面的init-y初值为“init/”,经过第567行的交换后,“init-y”变为"init/built-in.o"。 顶层Makefile中,再往下看。
第604行的vmlinux-all表示所有构成内核映象文件vmlinux的目标文件,从602~604行可知这些目标文件的顺序为:head-y、init-y、core-y、libs-y、drivers-y、net-y、即arch/arm/kernel/head.o(假设有MMU,否则为head-nommu.o)、arch/arm/kernel/init_task.o、init/built-in.o、usr/built-in.o等。 第605行表示连接脚本为arch/$(ARCH)/kernel/vmlinux.lds。对于ARM体系,连接脚本就是arch/arm/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,规则再scripts/Makefile.build中,如下所示: 现将生成的arch/arm/kernel/vmlinux.lds摘录如下
总结
下面对Makefile的结果进行一下总结。
- 配置文件.config中定义了一系列的变量,Makefile将结合它们来决定那些文件被编进内核、那些文件被编成模块、涉及那些子目录。
- 顶层Makefile和arch/$(ARCH)/Makefile决定根目录下那些子目录、arch/ $(ARCH)目录下那些文件和目录将被编进内核。
- 最后,各级子目录下的Makefile决定所在目录下那些文件将被编进内核,那些文件将被编成模块(即驱动程序),进入那些子目录继续调用它们的Makefile。
- 顶层Makefile和arch/ $(ARCH)/Makefile设置了可以影响所有文件的编译,连接选项:CFLAGS,AFLAGS,LDFLAGS,ARFLAGS.
- 各级子目录下的Makefile中可以设置能够影响当前目录下所有文件的编译,连接选项;EXTRA_CFLAGS,EXTRA_AFLAGS,EXTRA_LDFLAGS,EXTRA_ARFLAGS;还可以设置可以影响某个文件的编译选项:CFLAGS_$@,AFLAGS_ $@。
- 顶层Makefile按照一定的顺序组织文件,根据连接脚本arch/$ (ARCH)/kernel/vmlinux.lds生成内核映象文件vmlinux。
|