写点什么

一文让你彻底了解 Linux 内核源码组织结构【建议小白收藏】

作者:Linux爱好者
  • 2022 年 4 月 08 日
  • 本文字数:5717 字

    阅读完需:约 19 分钟

一文让你彻底了解Linux内核源码组织结构【建议小白收藏】

概要: 本文内容包含 Linux 源码树结构分析Linux Makefile 分析Kconfig 文件分析Linux 内核配置选项分析。这些知识是为了理解内核文件的组织形式,为具体移植内核做知识准备。


嵌入式学习提升进阶,十五分钟讲清完整路线

2022年嵌入式开发想进互联网大厂,你技术过硬吗?

从事十年嵌入式转内核开发(23K到45K),给兄弟们的一些建议

原文地址:zhuanlan.zhihu.com/p/424205125 转载请注明来源。


一, Linux 源码树结构分析


对 Linux 源码树下个子目录内包含的内容进行列表罗列:

  1. arch: 体系结构相关的代码,每一个子目录代表一种架构

  2. block: 块设备的通用函数

  3. crypot: 常用加密和散列算法、压缩和 CRC 校核算法

  4. fs: Linux 支持的文件系统,每一个子目录代表一种文件系统

  5. include: 内核头文件:基本头文件(include/linux )、驱动或功能部件头文件(例:include/mtd )、体系相关头文件(linux/asm-arm )

  6. driver: 所有的驱动程序,每一个子目录代表一类驱动程序

  7. init:内核的初始化程序,其中 main.c 中的 start_kernel 函数是内核引导后执行的第一个函数

  8. ipc: 进程间通信代码

  9. kernel: 内核管理的核心代码,与体系相关的代码在/arch/$(ARCH)/kernel

  10. lib: 内核用到的库函数,与处理器相关的库函数位于/arch/$(ARCH)/lib

  11. mm: 内存管理代码,与处理器体系相关的位于/arch/$(ARCH)/mm

  12. net: 与网络相关的代码,每一个子目录对应于网络的一个方面

  13. security: 安全、密钥相关的代码

  14. sound: 音频相关的驱动程序

  15. usr: 用来制作一个压缩的 cpio 归档文件:initrd 的镜像,它可以作为内核启动后挂载的第一个文件系统

  16. script: 用于配置、编译内核的脚本文件

  17. Documet: 内核文档


二,Linux Makefile 分析


主要从三个方面讲解:编译哪些文件、如何编译文件、如何连接文件


(1)Linux Makefile 的分类


顶层 Makefile:总体上控制着内核的编译 arch/$(ARCH)/Makefile:决定哪些和体系相关的代码参加编译.config:配置文件,内核配置时产生,所有的 Makefile 都根据这个文件编译内核(包括顶层的和各分成的 Makefile)scripts/Makefile.*:Makefile 公用的通用规则、脚本等*/Makefile:负责该目录下文件的编译(2)编译哪些文件


顶层 Makefile 决定哪些目录中的文件将编译进内核


init-y      := init/drivers-y   := drivers/ sound/ firmware/net-y       := net/libs-y      := lib/core-y      := usr/...core-y      += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
复制代码


顶层 Makefile 将 13 个子目录分成 5 个部分:init-y、drivers-y、net-y、libs-y、core-y


顶层通过下列语句包含和体系架构有关的 Makefile。仔细观察可以看到/arch 子目录的根目录下是没有 Makefile 文件的,而其它各子目录都是有 Makefile。


include $(srctree)/arch/$(SRCARCH)/Makefile...SRCARCH     := $(ARCH)
复制代码


所以在编译内核之前先要确定 ARCH


ARCH        ?= $(SUBARCH)CROSS_COMPILE   ?=...SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \              -e s/arm.*/arm/ -e s/sa110/arm/ \              -e s/s390x/s390/ -e s/parisc64/parisc/ \              -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \              -e s/sh[234].*/sh/ )
复制代码


默认的 ARCH 不是我们需要的,所以要进行修改

  ARCH        ?= arm    CROSS_COMPILE   ?=arm-linux-
复制代码


$$(srctree)/arch/$(SRCARCH)/Makefile 对内核的内容进行了扩充

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/drivers-$(CONFIG_OPROFILE)      += arch/arm/oprofile/libs-y              := arch/arm/lib/ $(libs-y)...head-y      := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
复制代码

可以看到一个新元素 head-y,它还有一个特殊的地方,它是直接对应着两个文件,而不是目录。之所以分成两个是为了同时支持有无 MMU 的 CPU,它们对应着两个不同的 head$(MMUEXT).o 文件,由变量 MMUEXT 控制,可以在配置时设定。

至此我们知道了编译时将进入哪些文件进行编译。编译时依次进入 init-y、core-y、libs-y、drivers-y、net-y 中列的目录调用其中的 Makefile 进行编译,每一个子目录都会生成 build-in.o(libs-y 所列的目录下有可能生成 lib.a)。最后 head-y 列出的文件和 build-in.o、lib.a 一起连接成 vmlinux。


在配置内核时,将会产生.config 文件,Makefile 将会在.config 文件中添加下面两行。


CONFIG_KERNELVERSION = "2.6.32.2"CONFIG_ARCH = "arm"有可能是版本原因,在 2.6.32.2 版本中并没有上面两个语句,有下面两句。


#Linux kernel version = 2.6.32.2CONFIG_ARM = y 观察.config 文件会发现变量的值主要有两种 y、m,各级的 Makefile 将会根据这些变量的值来决定编译哪些文件,同时是编译进内核,还是作为内核模块存在。


obj-y 中定义的.o 文件将由当前目录下的.c、.S 文件及子目录下的 build-in.o 文件编译连接得到的。


注意:obj-y 中定义的.o 文件的顺序是由意义的。


下面是一段取自子目录中的 Makefile 文件内容,在该目录下有 ioat 和 ipu 子目录


obj-$(CONFIG_DMA_ENGINE) += dmaengine.oobj-$(CONFIG_NET_DMA) += iovlock.oobj-$(CONFIG_DMATEST) += dmatest.oobj-$(CONFIG_INTEL_IOATDMA) += ioat/obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.oobj-$(CONFIG_FSL_DMA) += fsldma.oobj-$(CONFIG_MV_XOR) += mv_xor.oobj-$(CONFIG_DW_DMAC) += dw_dmac.oobj-$(CONFIG_AT_HDMAC) += at_hdmac.oobj-$(CONFIG_MX3_IPU) += ipu/obj-$(CONFIG_TXX9_DMAC) += txx9dmac.oobj-$(CONFIG_SH_DMAE) += shdma.o
复制代码


obj-m 中定义的.o 文件是由的当前目录下的.c、.S 文件编译生成,它们不会与 build-in.o 一起编译进入内核。而是被编译成.ko 文件,作为模块存在。


当.o 文件由单文件编译而成时,用下面的语句:


obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o 当.o 文件由多文件编译而成时,用下面的语句:


obj-$(CONFIG_ISDN) +=isdn.oisdn-objs := isdn_net_lib.o isdn_v110.o isdn_commen.o 编写驱动程序时,也是以这种方式编写 Makefile。


lib-y 中定义的.o 文件是由的当前目录下的.c、.S 文件编译生成,他们被打包成当前目录下的 lib.a 文件。同时出现在 lib-y 和 obj-y 中的文件,不会被包含进 lib.a 文件。


obj-y 和 obj-m 可以用来指定进入下一级目录。


(3)怎么编译这些文件


怎么编译文件就是意味着编译选项和连接选项是什么。


这些选项分成 3 类:全局的(适用整个代码树)、局部的(适用单个 Makefile)、个体的(适用单个文件)。


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


局部选项在各自子目录中定义,名称为:EXTRA_CFLAGS、EXTRA_AFALGS、EXTRA_LDFALGS、EXTRA_ARFLAGS.


对单文件设定编译选项,可以用 CLFAGS_@、AFLAGS_@,前者对 C 文件,后者对汇编文件。


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


_c_flags = $(CFLAGS) $(EXTRA_CFLGAS) $(CFALGS_$(baseterget.o))


如何连接文件

在顶层 Makefile 文件中有如下语句:

init-y      := $(patsubst %/, %/built-in.o, $(init-y))core-y      := $(patsubst %/, %/built-in.o, $(core-y))drivers-y   := $(patsubst %/, %/built-in.o, $(drivers-y))net-y       := $(patsubst %/, %/built-in.o, $(net-y))libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))libs-y2     := $(patsubst %/, %/built-in.o, $(libs-y))libs-y      := $(libs-y1) $(libs-y2)
复制代码

可以看出以后的连接是相当于着五种 built-in.o 文件和 head-o 文件的连接。

之后对这些文件再次进行合并

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
复制代码

可以看出初始化代码由两部分组成 head-y 和 init-y 两部分组成,而且 head-y 是在 init-y 的前面。所以总的代码顺序是 arch/arm/kernel/head.o(假设有 MMU,没有的话是 head_nommu.o)、arch/arm/kernel/init_task.o、init/build-in.o。

连接脚本是 arch/$(SRCARCH)/kernel/vmlinux.lds,它由 arch/$(SRCARCH)/kernel/vmlinux.lds.S 生成。

具体连接细节可以查看上面的文件内容。


三,内核的 Kconfig 分析


内核配置工具读取各个 Kconfig 文件,生成配置界面共开放人员配置内核,最后生成配置文件.config。

关于 Kconfig 的最权威资料在/Documentations/Kbuild/kconfig-language.txt


Kconfig 语法分析:

  • Kconfig 的基本要素:config ;config 经常被其它条目包含,用来生成菜单和多项选择。

config JFFS2_FS_WBUF_VERIFY    bool "Verify JFFS2 write-buffer reads"    depends on JFFS2_FS_WRITEBUFFER    default n    help      This causes JFFS2 to read back every page written through the      write-buffer, and check for errors.
复制代码

上述代码是 config 的常用方式:

config JFFS2_FS_WBUF_VERIFY
复制代码

在配置界面中配置了该选项后,会在.config 中出现 CONFIG_JFFS2_FS_WBUF_VERIFY = y 或者 m.

bool "Verify JFFS2 write-buffer reads"
复制代码

在配置界面中将会显示 Verify JFFS2 write-buffer reads 选项,bool 是变量的类型,一共有 5 种变量类型:bool、tristate、 string 、hex 、int,bool 变量有两种取值 y,m;tristate 变量有三种取值 y,m,n;string 可以取字符串;hex 取十六进制数;int 取十进制数。

depends on JFFS2_FS_WRITEBUFFER
复制代码

代表只有在 JFFS2_FS_WRITEBUFFER 被配置时,才会进行该选项的配置。

default n
复制代码

代表默认的情况下是选择 n

select FS_POSIX_ACL
复制代码

代表在该选项被选种时,会将 FS_POSIX_ACL 也选种。

    help      This causes JFFS2 to read back every page written through the      write-buffer, and check for errors.
复制代码

当在配置时按 H 时会显示该信息。

menu 条目

配置界面的主界面是由根目录下 Makefile 中 ARCH 配置决定的,当选择 arm 时,/arch/arm 中的 Kconfig 文件将会用来生成主目录。

下面的内容摘自/arch/arm/Kconfig

mainmenu "Linux Kernel Configuration"
复制代码

设定主目录的名称

menu "System Type"
复制代码

将会创建 System Type 子目录

choice 条目

choice 将多个类似的配置选项组合在一起,供用户多选和单选

choice    prompt "Memory split"    default VMSPLIT_3G    help      Select the desired split between kernel and user memory.      If you are not absolutely sure what you are doing, leave this      option alone!    config VMSPLIT_3G        bool "3G/1G user/kernel split"    config VMSPLIT_2G        bool "2G/2G user/kernel split"    config VMSPLIT_1G        bool "1G/3G user/kernel split"endchoice    prompt "Memory split"
复制代码

上述代码给出提示信息,选中之后就可以进行选择配置

choice 条目中定义的变量类型只能是 bool 和 tristate,当配置的代码编译入内核时为 bool,只能有一个条目选择为 y;当编译成模块时为 tristate 或 bool,为 bool 时,也只能是一个为 y,当为 tristate 时,可以有多个 m。

comment 条目

comment 条目用于提供帮助信息,出现在配置界面的第一行。

comment "At least one emulation must be selected"
复制代码

source 条目

用于包含其他 Kconfig 文件

source "drivers/cpuidle/Kconfig"
复制代码

菜单形式的配置界面的操作方法

配置界面中[*]、< M >、[ ]分别表示相应的文件被编译进内核、编译成模块、没有被编译。

Load an Alertnate Configuration File Save an Alertnate Configuration File
复制代码

当执行第一条语句时,将.config 外的 config 文件加载,当执行第二条时,表示存储成处.config 外的 config 文件。


四,Linux 内核配置选项


与移植密切相关的内容是 System TypeDevice Driver

内核配置主界面内容

  1. code maturity level options:代码成熟度选项,包含一些正在开发的或者不成熟的代码和驱动程序,一般不用设置

  2. General setup:常规设置,比如增加附加的内核版本号、支持内存页交换功能、System V 进程间通信等。除非很熟悉其中的内容,否则一般使用默认配置

  3. Loadable module support:可加载模块支持:一般都会打开可加载模块支持(Enable loadable module support )、允许卸载已经加载的模块(Module unloading)、让内核通过运行 modprobe 来自动加载模块(Automatic kernel module loading)

  4. block layer:块设备层:用于设置块设备的一些总体参数,比如是否支持大于 2TB 的块设备、是否支持大于 2TB 的文件、设置 IO 调度器,使用默认值即可

  5. System Type:系统类型:选择 CPU 的架构、开发板类型等于开发板相关的配置选项

  6. Bus support:PCMCIA 、CardBus 总线的支持,对于 ARM 开发板不需要设置

  7. Kernel Feature :用于设置内核的一些参数,比如是否支持内核抢占,是否支持动态修改系统时钟

  8. Boot option:启动参数:比如设置默认的命令行参数等,一般不用理会

  9. Floating point emulation:浮点运算仿真功能:因为 Linux 内核不支持硬件浮点运算,所以要选择一个浮点仿真器,一般选择”NWFPE math emulaiton”

  10. Userspace binary formats:可执行文件格式:一般都选择 ELF、a.out 格式

  11. Power management options:电源管理选项

  12. Networking:网络协议选项:一般都选择”Networking support“以支持网络功能。通常可以在选择”Networking support“后,使用默认配置

  13. Device Driver:设备驱动程序:几乎包含了 Linux 的所有的驱动程序

  14. File systems:文件系统:选择支持的文件系统

  15. Profiling support:对系统的或顶进行分析,仅供内核开发者使用

  16. Kernel hacking:调试内核时的各种选项:Linux 设备驱动程序中有详细描述

  17. security options:安全选项:一般使用默认选项

  18. Cryptographic options:加密选项

  19. Library routines:库子程序:比如 CRC32 检验函数、zlib 压缩函数等。不包含在内核源码中的第三方内核模块可能需要这些库,可以全不全,内核中若有其他部分依赖它,会自动选项

在配置内核的时候按照顺序进行,因为前面的配置会影响后面的。

发布于: 刚刚阅读数: 3
用户头像

外在压力增加时,就应增强内在的动力。 2020.12.03 加入

擅长底层原理开发技术,分享技术和经验

评论

发布
暂无评论
一文让你彻底了解Linux内核源码组织结构【建议小白收藏】_Linux内核_Linux爱好者_InfoQ写作平台