正点原子嵌入式linux驱动开发——Linux内核顶层Makefile详解

之前的几篇学习笔记重点讲解了如何移植uboot到STM32MP157开发板上,从本章就开始学习如何移植Linux内核。 同uboot一样,在具体移植之前,先来学习一下Linux内核的顶层Makefile文件,因为顶层 Makefile控制着Linux内核的编译流程

Linux内核初次编译

先编译一下正点原子STM32MP157开发板的出厂Linux内核。

第三方库安装

编译内核之前需要先在Ubuntu上安装lzop库,否则内核编译会失败!命令如下:

sudo apt-get update //先更新在安装,防止安装的时候报错
sudo apt-get install lzop
sudo apt-get install libssl-dev

mkimage工具安装

STM32MP1编译出来的Linux内核镜像文件为uImage,这是uboot所使用的内核镜像格式,通过在zImage镜像的前面添加0X40个字节的头部来得到uImage,这个需要mkimage工具来完成此工作。所以需要在Ubuntu下安装mkimage工具,输入如下命令:

sudo apt-get install u-boot-tools

编译Linux系统

首先编译一下正点原子出厂的Linux源码,在Ubuntu下新建一个名为“alientek_linux”的目录存放正点原子出厂Linux源码,正点原子出厂Linux系统源码已经放到了开发板光盘中,就是linux-5.4.31-gb8d3ec3ac-v1.1.tar.bz2,将正点原子出厂linux系统源码拷贝到前面在Ubuntu下新建的“alientek_linux”目录下,拷贝完成以后使用如下命令解压缩:

cd alientek_linux //进入到alientek_linux目录
tar -vxjf linux-5.4.31-gb8d3ec3ac-v1.1.tar.bz2 //解压缩

解压完成后如下图所示:
正点原子的Linux源码根目录
在上图中Linux源码根目录下新建名为“stm32mp157d_atk.sh”的shell脚本,然后在这个shell脚本中输入如下所示内容:

示例代码15.1.1 stm32mp157d_atk.sh文件内容 
1 #!/bin/sh 
2 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- distclean 
3 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp1_atk_defconfig 
4 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig 
5 make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- uImage dtbs LOADADDR=0XC2000040 -j16

第2行,执行“make distclean”,清理工程,所以stm32mp157d_atk.sh每次都会清理一下工
程。如果通过图形界面配置了Linux,但是还没保存新的配置文件,那么就要慎重使用stm32mp157d_atk.sh编译脚本了,因为它会把你的配置信息都删除掉!

第3行,执行“make xxx_defconfig”,配置工程,这里使用的配置文件为stm32mp1_atk_defconfig。

第4行,执行“make menuconfig”,打开图形配置界面,对Linux进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。

第5行,编译Linux内核,后面的“uImage”表示编译uImage格式的Linux内核,“dtbs”表示编译设备树,LOADADDR表示Linux内核在DDR中的加载地址为0XC2000040。

stm32mp157d_atk.sh编译脚本每次执行都是先清理整个工程,然后在重新全编译,这个过程很费时间,如果电脑配置差的话要编译很久。除了第一次编译Linux源码,我们很少清理工程全编译的,后续驱动开发很少用“make distclean”来清理工程,都是直接“make uImage LOADADDR=0XC2000040”编译内核或者“make dtbs”编译设备树

使用chmod给予stm32mp157d_atk.sh可执行权限,然后运行此shell脚本,命令如下:

chmod 777 stm32mp157d_atk.sh //给予可执行权限
./stm32mp157d_atk.sh //执行编译脚本

编译的时候会弹出Linux图形配置界面,如下图所示:

Linux图形配置界面
Linux图形界面配置和uboot是一样的,这里不需要做任何配置,直接按两下“ESC”退出,退出后就会自动开始编译Linux,编译完成后如下图所示:
Linux内核编译成功
编译完成后就会在arch/arm/boot这个目录下生成一个uImage的文件,uImage就是要用的Linux镜像文件,如下图所示:
编译得到uImage
另外也会在arch/arm/boot/dts下生成很多.dtb文件,这些.dtb就是设备树文件,需要的是stm32mp157d-atk.dtb这个文件,如下图所示:
编译得到的.dtb文件
在编译脚本stm32mp157d_atk.sh里面,每条编译命令都指定ARCH和CROSS_COMPILE这两个变量的值,如果单独运行编译命令的话输入起来会很麻烦,可以直接在Linux内核源码的Makefile文件里面指定ARCH和CROSS_COMPILE这两个变量的值,打开Linux内核源码目录下的主Makefile,按照如下图所示设置ARCH和CROSS_COMPILE:
变量ARCH和CROSS_COMPILE
在主Makefile中添加ARCH和CROSS_COMPILE以后,stm32mp157d_atk.sh就可以简化为:

示例代码15.1.2 简化后的stm32mp157d_atk.sh文件内容 
1 #!/bin/sh 
2 make distclean 
3 make stm32mp1_atk_defconfig 
4 make menuconfig 
5 make uImage dtbs LOADADDR=0XC2000040 -j16

如果要单独编译uImage的话可以使用如下命令:

make uImage LOADADDR=0XC2000040

单独编译设备树可以使用如下命令:

make dtbs

可以看出,Linux的编译过程基本和uboot一样,都要先执行“make xxx_defconfig”来配置一下,然后在执行“make uImage”进行编译,如果需要使用图形界面配置的话就执行“make menuconfig”。

Linux工程目录分析

将正点原子提供的Linux源码进行解压,完成后的目录如下图所示:
未编译Linux源码目录
上图就是正点原子提供的未编译的Linux源码目录文件,在分析Linux之前一定要先在Ubuntu中编译一下Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析Linux不可或缺的文件。编译完成以后使用tar压缩命令对其进行压缩并使用Filezilla软件将压缩后的Linux源码拷贝到Windows下。

编译后的Linux目录如下图所示:
编译后的Linux目录
上图中重要的文件夹或文件的含义如下图所示:
Linux目录
上图中的很多文件夹和文件并不需要很在意,需要关注的重点文件夹和文件如下,会进行详细讲解。

arch目录

这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应
一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等,以arch/arm为例,其子目录如下图所示:
arch/arm子目录
上图是arch/arm的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。arch/arm/configs目录是不同平台的默认配置文件:xxx_defconfig,如下图所示:
配置文件
在arch/arm/configs中就包含有STM32MP157开发板的默认配置文件:stm32mp1_atk_defconfig,执行“make stm32mp1_atk_defconfig”即可完成配置。arch/arm/boot/dts目录里面是对应开发平台
的设备树文件

arch/arm/boot目录下会保存编译出来的Image和uImage镜像文件,而uImage就是要用的linux镜像文件。

arch/arm/mach-xxx目录分别为相应平台的驱动和初始化文件,比如mach-stm32目录里面就是STM32系列CPU的驱动和初始化文件。

block目录

block是Linux下块设备目录,像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。

crypto目录

crypto目录里存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。

Documentation目录

此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。

drivers目录

驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是学习的重点

fs目录

此目录存放文件系统,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4和f2fs等文件系统。

include目录

头文件目录。

init目录

此目录存放Linux内核启动时的初始化代码。

ipc目录

IPC为进程间通信,ipc目录是进程间通信的具体实现代码。

kernel目录

Linux内核代码。

lib目录

lib是库的意思,lib目录都是一些公用的库函数。

mm目录

此目录存放内存管理相关代码。

net目录

此目录存放网络相关代码。

samples目录

此目录存放一些示例代码文件。

scripts目录

脚本目录,Linux编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。

security目录

此目录存放安全相关的文件。

sound目录

此目录存放音频相关驱动文件,音频驱动文件并没有存放到drivers目录中,而是单独的目录。

tools目录

此目录存放一些编译的时候使用到的工具。

usr目录

此目录存放与 initramfs有关的代码。

virt目录

此目录存放虚拟机相关文件。

.config文件

跟uboot一样,.config保存着Linux最终的配置信息,编译Linux的时候会读取此文件中的配置信息。最终根据配置信息来选择编译Linux哪些模块,哪些功能。

Kbuild文件

有些Makefile会读取此文件。

Kconfig文件

图形化配置界面的配置文件。

Makefile文件

Linux顶层Makefile文件,可以好好研读一下。

README文件

此文件详细讲解了如何编译Linux源码,以及Linux源码的目录信息,可以看一下此文件。

关于Linux源码目录就分析到这里,接下来分析一下Linux的顶层Makefile。

顶层Makefile详解

创建VScode的Linux内核工程。

Linux的顶层Makefile和uboot的顶层Makefile非常相似,因为uboot参考了Linux,前602行几乎一样,所以前面部分大致看一下就行了

1、版本号

顶层Makefile一开始就是Linux内核的版本号,如下所示:
示例代码15.3.1 顶层Makefile代码段
可以看出,Linux内核版本号为5.4.31。

2、MAKEFLAGS变量

MAKEFLAGS变量设置如下所示:
示例代码15.3.2 顶层Makefile代码段

3、命令输出

Linux编译的时候也可以通过“V=1”来输出完整的命令,这个和uboot一样,相关代码如下所示:
示例代码15.3.3 顶层Makefile代码段

4、静默输出

Linux编译的时候使用“make -s”就可以实现静默编译,编译的时候就不会打印任何信息,同uboot一样,相关代码如下:
示例代码15.3.4 顶层Makefile代码段

5、设置编译结果输出目录

Linux编译的时候使用“O=xxx”即可将编译产生的过程文件输出到指定的目录中,相关代码如下:
示例代码15.3.5 顶层Makefile代码段
6、代码检查

Linux也支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层Makefile中的代码如下:
示例代码15.3.6 顶层Makefile代码段

7、模块编译

Linux允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile中的代码如下:
示例代码15.3.7 顶层Makefile代码段
外部模块编译过程和uboot基本类似,最终导出srctree、objtree和VPATH这三个变量的值,其中srctree=.,也就是当前目录objtree同样为“.”。

8、设置目标架构和交叉编译器

同uboot一样,Linux编译的时候需要设置目标板架构ARCH和交叉编译器CROSS_COMPILE,在顶层Makefile中代码如下:
示例代码15.3.8 顶层Makefile代码段
这里只定义了ARCH,CROSS_COMPILE需要编译的时候手动输入值。为了方便,一般直接在顶层Makefile中设置ARCH和CROSS_COMPILE,直接将其设置为对应的架构和编译器。比如正点原子至今的教程都是将ARCH设置为arm,CROSS_COMPILE设置为arm-none-linux-gnueabihf-,如下所示:

示例代码15.3.9 顶层Makefile代码段 
360 ARCH ?= arm 
361 CROSS_COMPILE ?= arm-none-linux-gnueabihf-

设置好后就可以使用如下命令编译Linux:

make xxx_defconfig //使用默认配置文件配置 Linux 
make menuconfig //启动图形化配置界面
make -j16 //编译 Linux

9、调用scripts/Kbuild.include文件

同uboot一样, Linux顶层Makefile也会调用文件scripts/Kbuild.include,顶层Makefile相应代码如下:
示例代码15.3.10 顶层Makefile代码段

10、交叉编译工具变量设置

顶层Makefile中其他和交叉编译器有关的变量设置如下:
示例代码15.3.11 顶层Makefile代码段
LA、LD、CC等这些都是交叉编译器使用的工具。

11、头文件路径设置

顶层Makefile定义了两个变量保存头文件路径:USERINCLUDE和LINUXINCLUDE,相关代码如下:
示例代码15.3.12 顶层Makefile代码段
第442-447行的USERINCLUDE是UAPI相关的头文件路径,第451-456行的LINUXINCLUDE是Linux内核源码的头文件路径。重点来看一下LINUXINCLUDE,其中SRACARCH=arm。因此,将USERINCLUDE和LINUXINCLUDE展开以
后为:
变量展开

12、导出变量

顶层Makefile会导出很多变量给子Makefile使用,导出的这些变量如下:
示例代码15.3.13 顶层Makefile代码段

make xxx_defconfig过程

第一次编译Linux之前都要先使用“make xxx_defconfig”配置Linux内核,在顶层Makefile中有“%config”这个目标,如下所示:

示例代码15.3.1.1 顶层Makefile代码段 
560 include arch/$(SRCARCH)/Makefile 
561 export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT 
562 
563 config: outputmakefile scripts_basic FORCE 
564     $(Q)$(MAKE) $(build)=scripts/kconfig $@
565 
566 %config: outputmakefile scripts_basic FORCE 
567     $(Q)$(MAKE) $(build)=scripts/kconfig $@

第560行引用arch/arm/Makefile这个文件,这个文件很重要,因为zImage、uImage等这些文件就是由arch/arm/Makefile来生成的。

第561行导出变量KBUILD_DEFCONFIG、KBUILD_KCONFIG、CC_VERSION_TEXT。

第563行,没有目标与之匹配,因此不执行。

第566行,“make xxx_defconfig”与目标 “%config”匹配,因此执行。“%config”依赖scripts_basic、 outputmakefile和FORCE,“%config”真正有意义的依赖就只有 scripts_basic,scripts_basic的规则如下:

示例代码15.3.1.2 顶层Makefile代码段 
499 scripts_basic: 
500     $(Q)$(MAKE) $(build)=scripts/basic 
501     $(Q)rm -f .tmp_quiet_recordmcount

build定义在文件scripts/Kbuild.include中,值为build := -f $(srctree)/scripts/Makefile.build obj,因此将示例代码15.3.1.2展开就是:

scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有 @,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有 @

接着回到示例代码15.3.1.1的目标“%config”处,内容如下:

%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

将命令展开就是:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
## Makefile.build脚本分析 从上一小节可知,“make xxx_defconfig”配置Linux的时候如下两行命令会执行脚本scripts/Makefile.build,有如下代码:
@make -f ./scripts/Makefile.build obj=scripts/basic 
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

接下来一次分析一下:

scripts_basic目标对应命令

scripts_basic目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文
件scripts/Makefile.build,有如下代码:

示例代码15.3.2.1 Makefile.build代码段 
40 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) 
41 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) 
42 include $(kbuild-file)

将kbuild-dir展开后为:

kbuild-dir=./scripts/basic

将kbuild-file展开后为:

kbuild-file= ./scripts/basic/Makefile

最后将42行展开,即:

include ./scripts/basic/Makefile

继续分析scripts/Makefile.build,代码如下:

示例代码15.3.2.2 Makefile.build代码段 
488 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ 
489     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ 
490     $(subdir-ym) $(always) 
491     @:

__build是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指
定目标,所以会使用到默认目标 __build。在顶层Makefile中,KBUILD_BUILTIN为1,KBUILD_MODULES为空,因此展开后目标__build为:

__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

可以看出目标__build有5个依赖:builtin-target、lib-target、extra-y、subdir-ym和always。这5个依赖的具体内容如下:

builtin-target =
lib-target =
extra-y =
subdir-ym =
always = scripts/basic/fixdep scripts/basic/bin2c

只有always有效,因此__build最终为:

__build: scripts/basic/fixdep scripts/basic/bin2c
@:

__build依赖于scripts/basic/fixdep和 scripts/basic/bin2c,所以要先将scripts/basic/fixdep和scripts/basic/bin2c.c这两个文件编译成fixdep和bin2c。

综上所述,scripts_basic目标的作用就是编译出scripts/basic/fixdep和scripts/basic/bin2c这两个软件。

%config目标对应的命令

%config目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig。Makefile.build会读取scripts/kconfig/Makefile中的内容,此文件有如下所示内容:

示例代码15.3.2.3 scripts/kconfig/Makefile代码段 
89 %_defconfig: $(obj)/conf 
90 $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

目标%_defconfig与xxx_defconfig匹配,所以会执行这条规则,将其展开就是:

%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig

%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。此软件就会将%_defconfig中的配置输出到.config文件中,最终生成Linux kernel根目录下的.config文件。

make过程

使用命令“make xxx_defconfig”配置好Linux内核以后就可以使用“make”或者“make all”命令进行编译。顶层Makefile有如下代码:
示例代码15.3.3.1 顶层Makefile代码段
第15行,_all是默认目标,如果使用命令“make”编译Linux的话此目标就会被匹配。

第577行,如果KBUILD_EXTMOD为空的话578行的代码成立。

第578行,默认目标_all依赖all

第631行,目标all依赖vmlinux,所以接下来的重点就是vmlinux!

顶层Makefile中有如下代码:

示例代码15.3.3.2 顶层Makefile代码段 
1037 export KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) \ 
1038             $(libs-y2)$(drivers-y) $(net-y) $(virt-y) 
1039 export KBUILD_VMLINUX_LIBS := $(libs-y1) 
1040 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds 
1041 export LDFLAGS_vmlinux 
1042 # used by scripts/Makefile.package 
1043 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux- alldirs)) LICENSES arch include scripts tools) 
1044 
1045 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) 
1046 
1047 # Recurse until adjust_autoksyms.sh is satisfied 
1048 PHONY += autoksyms_recursive 
1049 ifdef CONFIG_TRIM_UNUSED_KSYMS 
1050 autoksyms_recursive: descend modules.order 
1051     $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \ 
1052         "$(MAKE) -f $(srctree)/Makefile vmlinux" 
1053 endif 
...... 
1075 vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE 
1076     +$(call if_changed,link-vmlinux)

从第1075行可以看出目标vmlinux依赖scripts/link-vmlinux.sh、autoksyms_recursive、$(vmlinux-deps)和FORCE。第1045行定义了vmlinux-deps,值为:

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)

第1037行,KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)。

第1039行,KBUILD_VMLINUX_LIBS := $(libs-y1)。

第1040行,KBUILD_LDS:=arch/$(SRCARCH)/kernel/vmlinux.lds,其中SRCARCH=arm,因此 KBUILD_LDS= arch/arm/kernel/vmlinux.lds。

第1049行,没有定义宏CONFIG_TRIM_UNUSED_KSYMS,所以 autoksyms_recursive为空的。

综上所述,vmlinux的依赖为:scripts/link-vmlinux.sh、$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)、arch/arm/kernel/vmlinux.lds和FORCE。

第1076行的命令用于链接生成vmlinux。

重点来看一下$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)这八个变量的值。

head-y

head-y定义在文件arch/arm/Makefile中,内容如下:

示例代码15.3.3.3 arch/arm/Makefile代码段 
144 head-y := arch/arm/kernel/head$(MMUEXT).o

当不使能MMU的时候MMUEXT=-nommu,如果使能MMU的话为空,因此head-y最终的值为:

head-y = arch/arm/kernel/head.o

init-y、drivers-y和net-y

在顶层Makefile中有如下代码:

示例代码15.3.3.4 顶层Makefile代码段 
618 init-y := init/ 
619 drivers-y := drivers/ sound/ 
620 drivers-$(CONFIG_SAMPLES) += samples/ 
621 net-y := net/ 
...... 
1028 init-y := $(patsubst %/, %/built-in.a, $(init-y)) 
1030 drivers-y:= $(patsubst %/, %/built-in.a, $(drivers-y)) 
1031 net-y := $(patsubst %/, %/built-in.a, $(net-y))

从示例代码15.3.3.4可知,init-y、drivers-y和net-y最终的值为:

init-y = init/built-in.o
drivers-y = drivers/built-in.a sound/built-in.a samples/built-in.a
net-y = net/built-in.o

libs-y1和libs-y2

libs-y基本和init-y一样,在顶层Makefile中存在如下代码:

示例代码15.3.3.5 顶层Makefile代码段
622 libs-y := lib/ 
...... 
1032 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) 
1033 libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))

根据示例代码15.3.3.5可知,“libs-y1=lib/lib.a”,“libs-y2=lib/built-in.o”,这个只正确了一部分,因为在arch/arm/Makefile中会向libs-y中追加一些值,代码如下:

示例代码15.3.3.6 arch/arm/Makefile代码段 
297 libs-y := arch/arm/lib/ $(libs-y)

arch/arm/Makefile将libs-y的值改为了:arch/arm/lib $(libs-y),展开以后为:

libs-y = arch/arm/lib lib/

因此根据示例代码15.3.3.5的第1032和1033行可知,libs-y1和libs-y2最终应该为:

libs-y1 = arch/arm/lib/lib.a lib/lib.a
libs-y2 = arch/arm/lib/built-in.o lib/built-in.o

virt-y

virt-y在顶层Makefile中有如下代码:

示例代码15.3.3.7 顶层Makefile代码段 
624 virt-y := virt/ 
...... 
1034 virt-y := $(patsubst %/, %/built-in.a, $(virt-y))

从示例代码15.3.3.7可知,virt-y最终的值为:

virt-y= virt/built-in.a

core-y

core-y和init-y也一样,在顶层Makefile中有如下代码:

示例代码15.3.3.8 顶层Makefile代码段 
623 core-y := usr/ 
...... 
1015 core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/

但是在arch/arm/Makefile中会对core-y进行追加,代码如下:

示例代码15.3.3.9 arch/arm/Makefile代码段 
276 core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/ 
277 # Put arch/arm/fastfpe/ to use this. 
278 core-$(CONFIG_FPE_FASTFPE) += $(patsubst $(srctree)/%,%,$(wildcard $(srctree)/arch/arm/fastfpe/)) 
279 core-$(CONFIG_VFP) += arch/arm/vfp/ 
280 core-$(CONFIG_XEN) += arch/arm/xen/ 
281 core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/ 
282 core-$(CONFIG_VDSO) += arch/arm/vdso/ 
283 
284 # If we have a machine-specific directory, then include it in the build. 
285 core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
286 core-y += arch/arm/probes/ 
287 core-y += arch/arm/net/ 
288 core-y += arch/arm/crypto/ 
289 core-y += $(machdirs) $(platdirs)

第276-282行根据不同的配置向core-y追加不同的值,比如使能VFP的话就会在.config中有CONFIG_VFP=y这一行,那么core-y就会追加“arch/arm/vfp/”。

第285-289行就是对core-y直接追加的值。

在顶层Makefile中有如下一行:

示例代码15.3.3.10 顶层Makefile代码段 
1029 core-y := $(patsubst %/, %/built-in.a, $(core-y))

经过上述代码的转换,最终core-y的值为:

core-y = usr/built-in.a
arch/arm/vfp/built-in.a
arch/arm/vdso/built-in.a
arch/arm/kernel/built-in.a
arch/arm/mm/built-in.a
arch/arm/common/built-in.a
arch/arm/probes/built-in.a
arch/arm/net/built-in.a
arch/arm/crypto/built-in.a
arch/arm/mach-aspeed/built-in.a
arch/arm/mach-milbeaut/built-in.a
arch/arm/mach-stm32/built-in.a
kernel/built-in.a
certs/built-in.a
mm/built-in.a
fs/built-in.a
ipc/built-in.a
security/built-in.a
crypto/built-in.a
block/built-in.a

关于head-y 、init-y、core-y、libs-y2、drivers-y、net-y、virt-y和libs-y1这8个变量就讲解到这里。这些变量都是一些built-in.o或.a等文件,这个和uboot一样,都是**将相应目录中的源码文件进行编译,然后在各自目录下生成built-in.o文件,有些生成了.a库文件。最终将这些built-in.o和.a文件进行链接即可形成ELF格式的可执行文件,也就是vmlinux!**但是链接是需要链接脚本的,vmlinux的依赖arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本

示例代码15.3.3.2第1076行的命令“+$(call if_changed,link-vmlinux)”表示将$(call if_changed,link-vmlinux)的结果作为最终生成vmlinux的命令,前面的“+”表示该命令结果不可
忽略。$(call if_changed,link-vmlinux)是调用函数if_changed,link-vmlinux是函数if_changed的参数,函数if_changed定义在文件scripts/Kbuild.include中,如下所示:

示例代码15.3.3.11 scripts/Kbuild.include代码段 
217 if_changed = $(if $(any-prereq)$(cmd-check), \ 
218 $(cmd); \ 
219 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

any-prereq用于检查依赖文件是否有变化,如果依赖文件有变化那么any-prereq就不为空,否则就为空。cmd-check用于检查命令是否有变化,如果没有变化那么cmd-check就为空。当命令或者依赖有变化的话就执行命令。

第219行,打印命令执行过程。比如示例代码15.3.3.2中第1076行“(call if_changed,link-vmlinux)”,这行命令用于链接vmlinux。在这里$@就是“link-vmlinux”,因此cmd_$@表示执
行cmd_link-vmlinux的内容。cmd_link-vmlinux在顶层Makefile中有如下所示定义:

示例代码15.3.3.12 顶层Makefile代码段 
1071 cmd_link-vmlinux = \ 
1072 $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux);\
1073 $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

第1071行就是cmd_link-vmlinux的值,其中CONFIG_SHELL=sh,$<表示目标vmlinux的第一个依赖文件,根据示例代码15.3.3.2可知,这个文件为scripts/link-vmlinux.sh。LD= arm-none-linux-gnueabihf-ld -EL,KBUILD_LDFLAGS= -EL。LDFLAGS_vmlinux的值由顶层Makefile和arch/arm/Makefile这两个文件共同决定,最终LDFLAGS_vmlinux=–no-undefined -X --pic-veneer --build-id。因此cmd_link-vmlinux最终的值为:

cmd_link-vmlinux = sh scripts/link-vmlinux.sh arm-none-linux-gnueabihf-ld -EL--no-undefined -X --pic-veneer --build-id

cmd_link-vmlinux会调用scripts/link-vmlinux.sh这个脚本来链接出vmlinux!在link-vmlinux.sh中有如下所示代码:

示例代码15.3.3.13 scripts/link-vmlinux.sh代码段 
58 # Link of vmlinux 
59 # ${1} - output file 
60 # ${2}, ${3}, ... - optional extra .o files 
61 vmlinux_link() 
62 { 
63     local lds="${objtree}/${KBUILD_LDS}" 
64     local output=${1} 
65     local objects 
66 
67     info LD ${output} 
68 
69     # skip output file argument 
70     shift 
71 
72     if [ "${SRCARCH}" != "um" ]; then 
73         objects="--whole-archive \ 
74             ${KBUILD_VMLINUX_OBJS} \ 
75             --no-whole-archive \ 
76             --start-group \ 
77             ${KBUILD_VMLINUX_LIBS} \ 
78             --end-group \ 
79             ${@}" 
80 
81         ${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux} \ 
82             -o ${output} \ 
83             -T ${lds} ${objects} 
84     else 
85         objects="-Wl,--whole-archive \ 
86             ${KBUILD_VMLINUX_OBJS} \ 
87             -Wl,--no-whole-archive \ 
88             -Wl,--start-group \ 
89             ${KBUILD_VMLINUX_LIBS} \
90             -Wl,--end-group \ 
91             ${@}" 
92 
93         ${CC} ${CFLAGS_vmlinux} \ 
94             -o ${output} \ 
95             -Wl,-T,${lds} \ 
96             ${objects} \ 
97             -lutil -lrt -lpthread 
98         rm -f linux 
99     fi 
100 } 
...... 
305 vmlinux_link vmlinux "${kallsymso}" ${btf_vmlinux_bin_o}

vmliux_link就是最终链接出vmlinux的函数,第72行判断SRCARCH是否等于“um”,如果不相等的话就执 73-83行的代码。因为SRCARCH=arm,因此条件成立,执行73-83行的代码。重点看一下81-83行,这三行代码就应该很熟悉了!就是普通的链接操作,连接脚本为lds=./arch/arm/kernel/vmlinux.lds。

第305行调用 vmlinux_link函数来链接出vmlinux。

使用命令“make V=1”编译Linux,会有如下图所示的编译信息:
link-vmlinux.sh链接vmlinux过程
至此基本理清了make的过程,重点就是将各个子目录下的built-in.o、.a等文件链接在一起,最终生成vmlinux这个ELF格式的可执行文件。链接脚本为arch/arm/kernel/vmlinux.lds,链接过程是由shell脚本scripts/link-vmlinux.s来完成的。接下来的问题就是这些子目录下的built-in.o、.a等文件又是如何编译出来的呢?

built-in.o文件编译生成过程

根据示例代码15.3.3.2第1075行可知,vmliux依赖vmlinux-deps,而vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS),KBUILD_LDS是链接脚本,这里不考虑,剩下的KBUILD_VMLINUX_OBJS和KBUILD_VMLINUX_LIBS就是各个子目录下的built-in.o、.a等文件 。最终vmlinux-deps的值如下:

vmlinux-deps = arch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o
init/built-in.a
usr/built-in.a
arch/arm/vfp/built-in.a
arch/arm/vdso/built-in.a
arch/arm/kernel/built-in.a
arch/arm/mm/built-in.a
arch/arm/common/built-in.a
arch/arm/probes/built-in.a
arch/arm/net/built-in.a
arch/arm/crypto/built-in.a
arch/arm/mach-aspeed/built-in.a
arch/arm/mach-milbeaut/built-in.a
arch/arm/mach-stm32/built-in.a
kernel/built-in.a
certs/built-in.a
mm/built-in.a
fs/built-in.a
ipc/built-in.a
security/built-in.a
crypto/built-in.a
block/built-in.a
arch/arm/lib/built-in.a
lib/built-in.a
drivers/built-in.a
sound/built-in.a
samples/built-in.a
net/built-in.a
virt/built-in.a
arch/arm/lib/lib.a
lib/lib.a

除了arch/arm/kernel/vmlinux.lds以外,其他都是要编译链接生成的。在顶层Makefile中有
如下代码:

示例代码15.3.4.1 顶层Makefile代码段 
1082 $(sort $(vmlinux-deps)): descend ;

sort是排序函数,用于对vmlinux-deps的字符串列表进行排序,并且去掉重复的单词。可以看出vmlinux-deps依赖descend,descend也定义在顶层Makefile中,定义如下:

示例代码15.3.4.2 顶层Makefile代码段 
1025 build-dirs := $(vmlinux-dirs) 
...... 
1688 descend: $(build-dirs) 
1689 $(build-dirs): prepare 
1690     $(Q)$(MAKE) $(build)=$@ \ 
1691     single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \ 
1692     need-builtin=1 need-modorder=1

可以看出descend又依赖build-dirs,从1025行可以看出,build-dirs就是vmlinux-dirs。vmlinux-dirs看名字就知道和目录有关,此变量保存着生成vmlinux所需源码文件的目录,值如下:
vmlinux-dirs值
示例代码15.3.4.2中第1689行,目标build-dirs依赖prepare,这个依赖不重要。重点看一下第1690行的命令。build前面已经说了,值为“-f ./scripts/Makefile.build obj”,因此将1690行的命令展开就是:

@ make -f ./scripts/Makefile.build obj=$@

$@表示目标文件,也就是build-dirs的值,而build-dirs就是vmlinux-dirs,将vmlinux-dirs中的这些目录全部带入到命令中,结果如下:

@ make -f ./scripts/Makefile.build obj=init
@ make -f ./scripts/Makefile.build obj=usr
@ make -f ./scripts/Makefile.build obj=arch/arm/vfp
@ make -f ./scripts/Makefile.build obj=arch/arm/vdso
@ make -f ./scripts/Makefile.build obj=arch/arm/kernel
@ make -f ./scripts/Makefile.build obj=arch/arm/mm
@ make -f ./scripts/Makefile.build obj=arch/arm/common
@ make -f ./scripts/Makefile.build obj=arch/arm/probes
@ make -f ./scripts/Makefile.build obj=arch/arm/net
……
@ make -f ./scripts/Makefile.build obj=virt

这些命令运行过程其实都是一样的,就以“@ make -f ./scripts/Makefile.build obj=init”这个命令为例,讲解一下详细的运行过程。这里又要用到 Makefile.build这个脚本了,此脚本默认目标为__build,再来看一下,__build目标对应的规则如
下:

示例代码15.3.4.4 scripts/Makefile.build代码段 
488 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ 
489     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ 
490     $(subdir-ym) $(always) 
491    @:

当只编译Linux内核镜像文件,也就是使用“make uImage”编译的时候,KBUILD_BUILTIN=1,KBUILD_MODULES为空。“make”命令是会编译所有的东西,包括Linux内核镜像文件和一些模块文件。如果只编译Linux内核镜像的话,__build目标简化为:

__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

重点来看一下builtin-target这个依赖,builtin-target同样定义在文件scripts/Makefile.build中,定义如下:

示例代码15.3.4.5 scripts/Makefile.build代码段 
69 ifneq ($(strip $(real-obj-y) $(need-builtin)),) 
70 builtin-target := $(obj)/built-in.a 
71 endif

第70行就是builtin-target变量的值,为“$(obj)/built-in.o”,这就是这些built-in.o的来源了。要生成built-in.o,要求real-obj-y和need-builtin这些变量不能全部为空

make zImage过程

vmlinux、Image、zImage、uImage区别

前面几小节重点是讲vmlinux是如何编译出来的,vmlinux是ELF格式的文件,但是在实际中不会使用vmlinux,而是使用zImage或uImage这样的Linux内核镜像文件。

1、vmlinux是编译出来的最原始的内核文件,是未压缩的,比如正点原子提供的Linux源码编译出来的vmlinux差不多有260多MB,如下图所示:
vmlinux信息

2、Image是Linux内核镜像文件,但是Image仅包含可执行的二进制数据。 Image就是使用objcopy取消掉vmlinux中的一些其他信息,比如符号表。但是Image是没有压缩过的,Image保存在arch/arm/boot目录下,其大小大概在19MB左右,如下图所示:
Image镜像信息
相比vmlinux的260MB,Image缩小到了19MB。

3、zImage是经过gzip压缩后的Image,经过压缩以后其大小大概在8MB左右,如下图所示:
zImage镜像信息

4、uImage是老版本uboot专用的镜像文件,uImage是在zImage前面加了一个长度为64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息,uImage大小如下图所示:
uImage镜像大小
上图中zImage大小为8133168字节,uImage大小为8133232字节,差距就是8133232-8133168=64字节,这64字节就是头部信息。

使用“make”、“make all”、“make uImage”这些命令就可以编译出uImage镜像,在arch/arm/Makefile中有如下代码:
示例代码15.3.5.1 顶层Makefile代码段
第333行,变量BOOT_TARGETS包含zImage,Image,xipImage,bootpImage和uImage。

第341行,BOOT_TARGETS依赖vmlinux,因此如果使用“make uImage”编译Linux内核的话,首先肯定要先编译出vmlinux。

第342行,具体的命令,比如要编译uImage,那么命令展开以后如下所示:

@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/uImage @:

就是使用scripts/Makefile.build文件来完成vmlinux到uImage的转换。

关于Linux顶层Makefile就讲解到这里,基本和uboot的顶层Makefile一样,重点在于vmlinux的生成。最后将vmlinux压缩成最常用的uImage或zImage等文件。

总结

Linux内核的顶层Makefile的前半部分和uboot的就是一样的,可以去参考uboot的顶层Makefile详解,来进行学习,或者知道一个框架就可以了。

Linux内核的Makefile区别在于,关注的重点是vmlinux的生成

与uboot相似之处

首先会使用“make xxx_defconfig”配置Linux内核,最终展开就可以得到,最后执行的命令如下所示:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

而配置的时候,有两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

scripts_basic目标的作用就是编译出scripts/basic/fixdep和scripts/basic/bin2c这
两个软件。

%config目标会读取%_defconfig,这个会跟xxx_defconfig匹配,%_defconfig依赖scripts/kconfig/conf,会编译scripts/kconfig/conf.c生成conf软件,把%_defconfig中配置输出到.config,最终生成Linux kernel根目录下.config。

以上均与uboot的解读类似。

与uboot区别

“make”的默认目标是_all,_all依赖all,all依赖vmlinux。

vmlinux依赖如下:scripts/link-vmlinux.sh、$(head-y)、$(init-y)、$(core-y) 、$(libs-y2) 、$(drivers-y) 、$(net-y)、$(virt-y)、$(libs-y1)、arch/arm/kernel/vmlinux.lds和FORCE。

那8个变量展开后如下:

head-y = arch/arm/kernel/head.o
init-y = init/built-in.o
drivers-y = drivers/built-in.a sound/built-in.a samples/built-in.a
net-y = net/built-in.o
libs-y1 = arch/arm/lib/lib.a lib/lib.a
libs-y2 = arch/arm/lib/built-in.o lib/built-in.o
virt-y= virt/built-in.a
core-y = usr/built-in.a
arch/arm/vfp/built-in.a
arch/arm/vdso/built-in.a
arch/arm/kernel/built-in.a
arch/arm/mm/built-in.a
arch/arm/common/built-in.a
arch/arm/probes/built-in.a
arch/arm/net/built-in.a
arch/arm/crypto/built-in.a
arch/arm/mach-aspeed/built-in.a
arch/arm/mach-milbeaut/built-in.a
arch/arm/mach-stm32/built-in.a
kernel/built-in.a
certs/built-in.a
mm/built-in.a
fs/built-in.a
ipc/built-in.a
security/built-in.a
crypto/built-in.a
block/built-in.a

arch/arm/kernel/vmlinux.lds就是整个Linux的链接脚本,调用if_changed进行链接,最终执行cmd_link_vmlinux,而他展开后的值就是:

cmd_link-vmlinux = sh scripts/link-vmlinux.sh arm-none-linux-gnueabihf-ld -EL--no-undefined -X --pic-veneer --build-id cmd_link-vmlinux

也就是调用scripts/link-vmlinux脚本链接vmlinux,最终调用的是里面的vmlinux_link函数链接vmlinux。

最终使用

最后用的是vmlinux编译后,经过objcopy处理并gzip压缩,添加头信息的uImage镜像文件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/151182.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

提取歌曲伴奏?用对软件一键帮你搞定~

相信大家经常想获取某首歌曲的伴奏&#xff0c;但是不知从何下手&#xff0c;今天这篇教程给大家分享一个超神奇软件&#xff0c;一键提取歌曲伴奏&#xff01; 第一步&#xff1a;打开【音分轨】APP&#xff0c;进入首页点击【人声分离】 第二步&#xff1a;选择导入方式&…

C++ -- 学习系列 关联式容器 set 与 map

一 关联式容器是什么&#xff1f; c 中有两种容器类型&#xff1a;关联式容器与序列式容器&#xff08;顺序容器&#xff09; 关联式中的容器是按照关键字来存储与访问的&#xff0c;序列式容器&#xff08;顺序容器&#xff09;则是元素在容器中的相对位置来存储与访问的。…

开发过程教学——交友小程序

交友小程序 1. 我的基本信息2. 我的人脉2.1 我的关注2.2 我的粉丝 3. 我的视频4. 我的相册 特别注意&#xff1a;由于小程序分包限制2M以内&#xff0c;所以要注意图片和视频的处理。 1. 我的基本信息 数据库表&#xff1a; 我的基本信息我的登录退出记录我的登录状态&#x…

运营商sdwan优缺点及sdwan服务商优势

SD-WAN(软件定义广域网)作为一种重要的网络解决方案&#xff0c;已经受到了广泛的关注和采用。然而&#xff0c; 无论是由传统运营商提供的SD-WAN还是专门的SD-WAN服务提供商&#xff0c;都存在各自的优缺点。 运营商提供的SD-WAN的缺点&#xff1a; 1. 有限的灵活性&#xf…

Kubernetes概述架构与工作流程简述

文章目录 Kubernetes概述Kubernetes优势Kubernetes 集群组件控制平面组件Node 组件 Kubernetes工作流程下期预告 Kubernetes概述 Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 Kubernetes 拥…

如何提升爬虫IP使用效率?精打细算的方法分享

在进行爬虫数据采集时&#xff0c;爬虫IP是不可或缺的工具。然而&#xff0c;爬虫IP的费用可能是一个爬虫项目的重要开支之一。为了帮助您节省爬虫IP经费&#xff0c;本文将分享一些经济高效的方法&#xff0c;让您在使用爬虫IP时更加节约成本&#xff0c;提高经济效益。 一、优…

gitlab登录出现的Invalid login or password问题

前提 我是在一个项目里创建的gitlab账号&#xff0c;想在别的项目里登录或者官网登录发现怎么都登陆不上 原因 在GitLab中&#xff0c;有两种不同的账号类型&#xff1a;项目账号和个人账号&#xff08;官网账号&#xff09;。 项目账号&#xff1a;项目账号是在特定GitLab…

ubuntu 设置x11vnc服务

Ubuntu 18.04 设置x11vnc服务 自带的vino-server也可以用但是不好用&#xff0c;在ubuntu论坛上看见推荐的x11vnc&#xff08;ubuntu关于vnc的帮助页面&#xff09;&#xff0c;使用设置一下&#xff0c;结果发现有一些坑需要填&#xff0c;所以写下来方便下次使用 转载请说明…

云服务仿真:完全模拟 AWS 服务的本地体验 | 开源日报 No.45

localstack/localstack Stars: 48.7k License: NOASSERTION LocalStack 是一个云服务仿真器&#xff0c;可以在您的笔记本电脑或 CI 环境中以单个容器运行。它提供了一个易于使用的测试/模拟框架&#xff0c;用于开发云应用程序。主要功能包括&#xff1a; 在本地机器上完全…

Java——StringBuffer类常用操作示例

Java——StringBuffer类常用操作 package com.yushifu.javaAPI;import java.sql.SQLOutput;//StringBuffer类&#xff08;字符串缓冲区&#xff09; //StringBuffer类与String的区别————StringBuffer的内容和长度都是可以改的 //StringBuffer类似于一个字符容器&#xff0…

通过ElementUi在Vue搭建的项目中实现CRUD

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专栏…

Linux设备驱动的精髓在哪?为何感觉写驱动就像写八股文?

Linux设备驱动的精髓在哪&#xff1f;为何感觉写驱动就像写八股文&#xff1f; 话题背景&#xff1a;随着互联网尤其是移动互联网的发展&#xff0c;Android手机操作系统得到了广泛应用&#xff0c;而Android系统是基于Linux系统开发的。另外&#xff0c;大数据、云计算等技术也…

国庆出游远程实测:ToDesk 、TeamViewer、AnyDesk远程控制软件稳定性

ToDesk 、TeamViewer、AnyDesk远程控制软件稳定性 【前言】【实测软件】【测试环境】【实操体验】1. 软件安装2. 登录速度3. 文件传输4. 操作延迟5. 画面清晰度6. 安全防护 【本文小结】 【前言】 随着科技的不断发展&#xff0c;远程控制软件已成为我们生活中不可或缺的一部分…

【UE5 Cesium】15-Cesium for Unreal 加载本地影像和地形

目录 一、加载全球无高度地形 二、加载区域DEM 三、加载离线地图影像 一、加载全球无高度地形 1. 先去如下网址下载全球无高度地形&#xff1a;Using a global terrain layer without height detail - #9 by RidhwanAziz - Cesium for Unreal - Cesium Community 下载后如下…

【Zookeeper专题】Zookeeper经典应用场景实战(一)

目录 前置知识课程内容一、Zookeeper Java客户端实战1.1 Zookeeper 原生Java客户端使用1.2 Curator开源客户端使用快速开始使用示例 二、Zookeeper在分布式命名服务中的实战2.1 分布式API目录2.2 分布式节点的命名2.3 分布式的ID生成器 三、zookeeper实现分布式队列3.1 设计思路…

一些常见分布-正态分布、对数正态分布、伽马分布、卡方分布、t分布、F分布等

目录 正态分布 对数正态分布 伽马分布 伽马函数 贝塔函数 伽马分布 卡方分布 F分布 t分布 附录 参考文献 本文主要介绍一些常见的分布&#xff0c;包括正态分布、对数正态分布、伽马分布、卡方分布、F分布、t分布。给出了分布的定义&#xff0c;推导了概率密度函数&…

中国34省区市三维地形图(直接保存)

吉林 ▼ 辽宁 ▼ 北京 ▼ 河北 ▼ 山东 ▼ 山西 ▼ 天津 ▼ 江苏 ▼ 福建 ▼ 上海 ▼ 台湾 ▼ 浙江 ▼ 广东 ▼ 广西 ▼ 海南 ▼ 香港和澳门 ▼ 安徽 ▼ 河南 ▼ 湖北 ▼ 湖南 ▼ 江西 ▼ 甘肃 ▼ 内蒙古 ▼ 宁夏 ▼ 青海 ▼ 陕西 ▼ 新疆 ▼ 贵州 …

力扣 -- 1745. 分割回文串 IV

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool checkPartitioning(string s) {int ns.size();vector<vector<bool>> dp(n,vector<bool>(n));for(int in-1;i>0;i--){for(int ji;j<n;j){if(s[i]s[j]){dp[i][j]i1<j?dp[i…

One Thread One Loop主从Reactor模型⾼并发服务器

One Thread One Loop主从Reactor模型⾼并发服务器 文章目录 One Thread One Loop主从Reactor模型⾼并发服务器一些补充HTTP服务器Reactor 模型eventfd通用类Any 目标功能模块划分&#xff1a;SERVER模块Buffer模块&#xff1a;编写思路&#xff1a;接口设计&#xff1a;具体实现…

【云计算网络安全】DDoS 缓解解析:DDoS 攻击缓解策略、选择最佳提供商和关键考虑因素

文章目录 一、前言二、什么是 DDoS 缓解三、DDoS 缓解阶段四、如何选择 DDoS 缓解提供商4.1 网络容量4.2 处理能力4.3 可扩展性4.4 灵活性4.5 可靠性4.6 其他考虑因素4.6.1 定价4.6.2 所专注的方向 文末送书《数据要素安全流通》本书编撰背景本书亮点本书主要内容 一、前言 云…