当前位置: 首页 > news >正文

linux(3)之buildroot配置软件包

Linux(3)之buildroot配置软件包

Author:Onceday Date:2023年11月30日

漫漫长路,才刚刚开始…

参考文档:

  • Buildroot - Making Embedded Linux Easy
  • mdev.txt « docs - busybox - BusyBox: The Swiss Army Knife of Embedded Linux

文章目录

      • Linux(3)之buildroot配置软件包
        • 1. 基础软件包
          • 1.1 设备文件(/dev)管理
          • 1.2 初始化进程(init)
          • 1.3 其他基础包
        • 2. 通用软件包
          • 2.1 常用编译指令
          • 2.2 重新编译
          • 2.3 重新编译指定包
          • 2.4 指定编译输出目录
          • 2.5 设置环境变量
          • 2.6 提高文件系统镜像的存储效率
          • 2.7 查看软件包依赖关系图
          • 2.8 查看软件包编译耗时
          • 2.9 查看软件包占比图
        • 3. 高级构建参数
          • 3.1 并行构建支持
          • 3.2 使用在builroot之外生成的工具链
          • 3.3 使用ccache加快编译
          • 3.4 重定向软件包下载目录
          • 3.5 开发环境中使用
        • c4. 附注信息
          • 4.1 devtmpfs简介
          • 4.2 mdev/udev/eudev简介
          • 4.3 系统初始化程序简介
          • 4.4 getty简介
          • 4.5 稀疏文件(Sparse file)
          • 4.6 Ccache介绍

1. 基础软件包
1.1 设备文件(/dev)管理

在Linux系统中,/dev目录包含特殊文件,称为设备文件,允许用户空间应用程序访问由Linux内核管理的硬件设备。如果没有这些设备文件,那么用户空间应用程序将无法使用硬件设备,即使它们能够被Linux内核正确识别。builroot提供了四种不同的解决方案来处理/dev目录,可在System Configuration/dev management查看:

(1) 静态使用设备表。这是在Linux中处理设备文件的传统方法。使用这种方法,设备文件被持久地存储在根文件系统中(也就是说,它们在重新引导时一直存在),并且当硬件设备被添加或从系统中删除时,没有任何东西会自动创建和删除这些设备文件。因此,Buildroot使用设备表创建了一组标准的设备文件,默认的设备表存储在Buildroot源代码的system/device_table_dev.txt中。当builroot生成最终的根文件系统映像时,将处理该文件,因此设备文件在output/target目录中不可见。

BR2_ROOTFS_STATIC_DEVICE_TABLE选项允许更改Buildroot使用的默认设备表,或者添加一个额外的设备表,以便Buildroot在构建期间创建额外的设备文件。因此,如果使用此方法,并且目标系统中缺少设备文件,可以创建一个文件,如下:

board/<yourcompany>/<yourproject>/device_table_dev.txt

其中包含附加设备文件的描述,然后可以将BR2_ROOTFS_STATIC_DEVICE_TABLE设置为:

system/device_table_dev.txt board/<yourcompany>/<yourproject>/device_table_dev.txt

(2) 仅使用devtmpfsdevtmpfs是Linux内核中的一个虚拟文件系统,它是在内核2.6.32中引入。当挂载到dev中时,这个虚拟文件系统会随着硬件设备的添加和从系统中移除而自动地使设备文件出现和消失。这个文件系统在重新引导时不是持久的,它由内核动态填充。

使用devtmpfs需要启用以下内核配置选项:CONFIG_DEVTMPFSCONFIG_DEVTMPFS_MOUNT。当builroot负责为目标嵌入式设备构建Linux内核时,它会确保启用这两个选项。但是,如果在Buildroot之外构建Linux内核,但没有启动这两个选项,那么Linux系统将无法启动。

(3) 动态使用devtmpfs + mdev。此方法依赖于devtmpfs虚拟文件系统,但是在其之上添加了mdev用户空间程序。mdev是BusyBox的一个程序部分,内核将在每次添加或删除设备时调用它。根据/etc/mdev.conf配置文件,mdev可以被配置如下:

  • 设置设备文件的特定权限或所有权。
  • 每当设备出现或消失时调用脚本或应用程序。

基本上,它允许用户空间对设备添加和删除事件做出反应。例如,Mdev可用于在系统上出现设备时自动加载内核模块。如果目标设备需要固件,那么Mdev也很重要,因为它将负责将固件内容推送到内核。Mdevudev的轻量级实现(功能更少)。

有关mdev及其配置文件语法的更多详细信息,请参见:

  • mdev.txt « docs - busybox - BusyBox: The Swiss Army Knife of Embedded Linux

(4) 动态使用devtmpfs + eudev。此方法依赖于devtmpfs虚拟文件系统,但在其之上添加了eudev用户空间守护进程。Eudev是一个在后台运行的守护进程,当一个设备从系统中添加或删除时,内核会调用它。eudevudev的独立版本,udev是大多数桌面Linux发行版中使用的原始用户空间守护进程,现在是Systemd的一部分。

builroot开发人员建议从第二个方案(仅使用devtmpfs)开始使用。如果需要在添加/删除设备时通知用户空间,或者需要固件,第三个方案(动态使用devtmpfs + mdev)通常是一个很好的方式。如果systemd被选为init system, /dev管理将由systemd提供的udev程序执行

1.2 初始化进程(init)

init 是 Unix 和 Unix-like 操作系统(如 Linux)的一个重要组成部分,它是在系统启动时由内核首先启动的进程,其进程号(PID)通常是 1,其他的用户空间程序和服务将由它来负责启动。

Buildroot提供三种方法用于init程序,在System configuration/Init system中可供选择:

(1) BusyBox。在许多程序中,BusyBox有一个基本init程序的实现,这对于大多数嵌入式系统来说已经足够了,也是buildroot的默认方案。

启用BR2_INIT_BUSYBOX将确保BusyBox将构建并安装其初始化程序。BusyBox init程序将在引导时读取/etc/inittab文件,以了解该做什么。该文件的语法可参考下面文档(需要注意Busybox inittab的语法与其他类似文件不一样):

  • http://git.busybox.net/busybox/tree/examples/inittab

默认的inittab文件保存在system/skeleton/etc/inittab中。除了挂载一些重要的文件系统外,默认inittab文件的主要工作是启动/etc/init.d/rcS脚本,并启动getty程序(该程序提供登录提示)。

(2) systemV。这个方案使用旧的传统sysvinit程序,打包在Buildroot的package/sysvinit中。这是大多数桌面Linux发行版中使用的解决方案,直到它们切换到最近的替代方案,如Upstart或Systemd。sysvinit还可以使用inittab文件(其语法与BusyBox中的略有不同)。systemV默认inittab文件位于package/sysvinit/inittab中。

(3) systemdsystemd是Linux的新一代初始化系统。它比传统的init程序做得更多,如积极的并行化能力,使用套接字和D-Bus来启动服务,提供按需启动守护进程,使用Linux控制组跟踪进程,支持快照和系统恢复等功能。对于复杂的嵌入式系统来说,使用systemd比较合适,因为Systemd本身会带来非常多的依赖库。

对于资源较少的嵌入式设备系统来说,BusyBox initsystemd更为合适

1.3 其他基础包

以下的软件可以指定配置文件,并且在此基础上可以进一步修改和保存配置(没有指定将使用默认配置):

  • BusyBox,使用BR2_PACKAGE_BUSYBOX_CONFIG指定,可通过make busybox-menuconfig进一步修改。
  • uClibc,使用BR2_UCLIBC_CONFIG指定,可通过make uclibc-menuconfig进一步修改。
  • Linux kernel,使用BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG指定自定义配置,也可用BR2_LINUX_KERNEL_USE_DEFCONFIG导入预定义配置,后续可通过make linux-menuconfig进一步修改。
  • Barebox,使用BR2_TARGET_BAREBOX_USE_CUSTOM_CONFIGBR2_TARGET_BAREBOX_USE_DEFCONFIG分别指定自定义配置和预定义配置,可通过make barebox-menuconfig进行后续修改。
  • U-Boot,使用BR2_TARGET_UBOOT_USE_CUSTOM_CONFIGBR2_TARGET_UBOOT_USE_DEFCONFIG分别指定自定义配置和预定义配置,可通过make uboot-menuconfig进行后续修改。

当这些软件包没有使能时,make xxx-menuconfig通常是不可用的,因此需要确认.config文件中的配置情况

2. 通用软件包
2.1 常用编译指令
编译命令描述
make V=1 <target>执行一个make目标时,输出执行的命令信息和步骤,可用于分析编译过程。
make menuconfig进行buildroot配置,可以指定O=/path在指定目录中输出编译文件。
make list-defconfigs显示所有的默认配置文件,需要具有configs目录和对应的defconfigs
make help显示帮助文本,里面列出了buildroot提供的通用Target操作。
make clean清除所有build/host/target等编译输出,但部分配置会保留。
当编译架构和toolchain变化时,应该主动make clean,再全部重新编译。
make manual编译buildroot帮助文档(需要安装依赖库),也就是指导手册,可用make manual-clean清除。
make distclean重置整个buildroot编译输出,包括配置文件,通常用于准备编译一个新目标输出。
make printvars VARS='xxx'打印指定编译变量的值,可以指定多个变量(空格区分),也可以使用通配符%进行匹配。
以下的命令都可以搭配pkg使用,如make pkg-source只对指定的pkg软件包执行
make source下载所有源文件,这样就可以在离线环境下进行编译和配置。
make depends编译和安装所有软件包所需要的依赖目标。
make extract将软件包源文件提取到build目录中。
make patch打上软件包的补丁。
make configure运行配置命令(configure cmds)。
make build运行编译命令(build cmds)。
make install-staging(Target package)在staging目录中运行安装命令。
make install-target(Target package)在target目录中运行安装命令。
make install(Target package)运行install-staginginstall-target
(Host package)在host目录中运行安装命令。
make show-info以json格式输出使能软件包的依赖、许可证等数据信息。
make show-depends显示编译软件包的直接依赖关系。
make show-recursive-depends递归显示编译软件包的依赖关系。
make show-rdepends显示编译软件包的反向直接依赖关系。
make show-recursive-rdepends递归显示编译软件包的反向依赖关系。
make graph-depends输出图形化(PDF)的软件包依赖关系,可以直观清晰的查看不同软件包之间的依赖。
make graph-rdepends输出图形化(PDF)的软件包反向依赖关系,可以直观清晰的查看不同软件包之间的依赖。
make pkg-stats已HTML和JSON格式输出软件包的状态信息,可以查看安全漏洞和版本,需要网络支持。
make graph-build输出软件包编译时间的统计图表,按照编译顺序、耗时、软件包名称等排序和呈现。
make graph-size输出软件包占根文件系统的占比图,展示每个软件包耗费的存储空间大小。
make dirclean清除指定包的build子目录。
make reinstall重新运行安装命令。
make rebuild重新运行编译命令,当使用OVERRIDE_SRCDIR特性时或者在build目录修改文件,才起实际作用。
make reconfigure重新运行配置命令,当使用OVERRIDE_SRCDIR特性时或者在build目录修改文件,才起实际作用。

额外说明:

  • make -s printvars VARS=xxx 可以搭配以下两个参数:

    (1) QUOTED_VARS,如果设置为YES,将单引号引用值。

    ubuntu->buildroot:$ make printvars VARS=BUSYBOX_FINAL_DEPENDENCIES QUOTED_VARS=yes
    BUSYBOX_FINAL_DEPENDENCIES='host-skeleton skeleton toolchain'
    

    (2) RAW_VARS,如果设置为YES,将打印未展开的值。

    ubuntu->buildroot:$ make printvars VARS=BUSYBOX_FINAL_DEPENDENCIES QUOTED_VARS=yes RAW_VARS=yes
    BUSYBOX_FINAL_DEPENDENCIES='$(sort $(BUSYBOX_DEPENDENCIES))'
    

    QUOTED_VARS选项输出的带有引号的值,可重新注入到Shell脚本中,成为环境变量:

    ubuntu->buildroot:$ eval $(make printvars VARS=BUSYBOX_FINAL_DEPENDENCIES QUOTED_VARS=yes)
    ubuntu->buildroot:$ echo $BUSYBOX_FINAL_DEPENDENCIES
    host-skeleton skeleton toolchain
    
2.2 重新编译

当通过make menuconfigmake nconfig或其他配置工具更改系统配置时,builroot不会尝试检测应该重新构建系统的哪些部分。很难找到一种完全可靠的方式去检查什么时候重建整个系统,什么时候重建特定的包,因此作为用户需要根据实际情况把握,下面是一些可供参考的场景:

  • 当目标体系结构配置发生更改时,需要完全重新构建。更改架构种类,例如二进制格式或浮点策略会对整个系统产生影响。
  • 当工具链配置更改时,通常需要完全重新构建。更改工具链配置通常涉及更改编译器版本、C库类型或其配置,或其他一些基本配置项,这些更改会对整个系统产生影响。
  • 当额外的包被添加到配置中时,不需要完全重新构建。builroot将检测到这个包从未被构建过,并将构建它。但是,如果这个包是一个库,可以被已经构建的包使用,那么Buildroot将不会自动重新构建这些包。或者您知道应该重新构建哪些包,并且可以手动重新构建它们,或者您应该进行完整的重新构建。例如,假设您使用ctorrent包构建了一个系统,但没有使用openss1。您的系统工作正常,但是您意识到您希望在ctorrent中支持SSL,因此您在builroot配置中启用openssl包并重新启动构建。builroot将检测到应该构建openssl,并将构建它,但它不会检测到应该重新构建ctorrent以受益于openssl以添加openssl支持。您要么必须进行完整的重新构建,要么重新构建ctorrent本身。
  • 当一个包从配置中删除时,builroot不会做任何特别的事情。它不会从目标根文件系统或工具链sysroot中删除这个包安装的文件。需要完全重新构建以摆脱此包。然而,通常您并不一定需要立即删除这个包: 您可以等到下一个空闲时间重新开始构建。
  • 当包的子选项被更改时,不会自动重新构建包。在做出这样的更改之后,通常只重新构建这个包就足够了,除非启用包子选项向包中添加了一些对已经构建的另一个包有用的特性。同样,Buildroot不跟踪何时应该重新构建包:一旦构建了包,除非显式地告诉它这样做,否则它永远不会重新构建。
  • 当对根文件系统框架进行更改时,需要进行完整的重新构建。但是,当对根文件系统进行更改时,不需要进行完整的重新构建:简单的make调用将考虑这些更改。
  • 当在FOO_DEPENDENCIES中列出的包被重新构建或删除时,包foo不会自动重新构建。例如,如果使用FOO_DEPENDENCIES = barpackage bar列在FOO_DEPENDENCIES中,并且更改了bar包的配置,则配置更改不会自动导致package foo的重新构建。在这种情况下,您可能需要重新构建构建中的任何在其依赖项中引用bar的包,或者执行完整的重新构建以确保任何bar依赖包都是最新的。

一般来说,当面临构建错误并且不确定所做的配置更改的潜在后果时,请执行完整的重新构建。如果得到相同的构建错误,那么可以确定该错误与包的部分重新构建无关,如果此错误发生在来自官方Buildroot的包中,请毫不犹豫地报告问题!随着使用Buildroot的经验的增加,你将逐渐了解何时真正需要进行完全重新构建,并且节省越来越多的时间。

执行下面的命令可以完全清除再重建:

make clean all
2.3 重新编译指定包

Buildroot用户最常见的问题之一是如何重新构建给定的包,或者如何在不重新构建所有内容的情况下删除一个包。如果不从头重新构建,则builroot不支持删除包。这是因为Buildroot不跟踪哪个包安装了output/stagingoutput/target目录中的哪些文件,或者哪个包将根据另一个包的可用性进行不同的编译。

从头开始重新构建单个包的最简单方法是删除output/build中的构建目录。然后,builroot将从头开始重新提取、重新配置、重新编译和重新安装这个包。您可以使用make <package>-dirclean命令要求builtroot执行此操作。

另一方面,如果您只想从编译步骤重新启动包的构建过程,则可以运行make <package>-rebuild。它将重新启动包的编译和安装,但不是从头开始: 它基本上是在包内重新执行makemake install,因此它只会重新构建更改过的文件。

如果希望从配置步骤重新启动包的构建过程,可以运行make <package>-reconfigure。它将重新启动包的配置、编译和安装。而<package>-rebuild意味着<package>-install<package>-reconfigure意味着<package>-rebuild这些目标,以及<package>只作用于所述包,而不触发重新创建根文件系统映像。如果需要重新创建根文件系统,还应该运行makemake all

在内部,builroot创建所谓的stamp文件来跟踪每个包完成了哪些构建步骤。它们存储在包构建目录中:

output/build/<package>-<version>/.stamp_<step-name>

上面详细介绍的命令只是操作这些stamp文件,以强制builroot重新启动包构建过程的一组特定步骤。

2.4 指定编译输出目录

默认情况下,Buildroot构建的所有内容都存储在Buildroot树的目录输出中。builroot还支持用类似于Linux内核的语法从树中构建。要使用它,在make命令行中添加O=<directory>:

make O=/tmp/build menuconfig

所有的输出文件都位于/tmp/build下。如果O路径不存在,则builroot将创建它。注意: O路径既可以是绝对路径,也可以是相对路径,但如果它作为相对路径传递,请注意它是相对于主Buildroot源目录解释的,而不是当前工作目录

当指定输出目录构建时,.config和临时文件也存储在输出目录中。这意味着只要使用唯一的输出目录,就可以安全地使用相同的源代码树并行运行多个构建。

为了方便使用,builroot在输出目录中生成一个Makefile包装器——因此在第一次运行之后,您不再需要传递O = <…>,只需运行(在输出目录中):

make <target>
2.5 设置环境变量

Buildroot中有一些预期的环境变量,它们可以被传递进Make里,并且作为环境变量,用户可以单独指定它们的值。

变量名描述
HOSTCXX编译环境本地C++编译器
HOSTCC编译环境本地C编译器
UCLIBC_CONFIG_FILE==<path/to/.config>uClibc配置文件,推荐通过.config文件配置(make menuconfig)
BUSYBOX_CONFIG_FILE=<path/to/.config>BusyBox配置文件,推荐通过.config文件配置(make menuconfig)
BR2_CCACHE_DIR指定ccache缓存文件的目录
BR2_DL_DIR指定builroot存储/检索下载文件的目录,推荐通过.config文件配置(make menuconfig)
BR2_GRAPH_ALT如果设置且非空,则在build-time图表中使用备用配色方案
BR2_GRAPH_OUT设置生成图形的文件类型,pdf(默认)或png
BR2_GRAPH_DEPS_OPTS传递额外的选项到软件包依赖图生成程序中
BR2_GRAPH_DOT_OPTS作为选项逐字传递给dot实用程序,以绘制依赖关系图
BR2_GRAPH_SIZE_OPTS传递额外的选项到build-size图表中

下面是一个使用示例:

make HOSTCXX=g++-4.3-HEAD HOSTCC=gcc-4.3-HEAD
2.6 提高文件系统镜像的存储效率

文件系统映像可能会变得非常大,这取决于选择的文件系统、软件包的数量、是否提供了可用空间等等。然而,文件系统映像中的某些位置可能只是空的(例如,一长串零),这样的文件称为稀疏文件sparse file。大多数工具都可以有效地处理稀疏文件,并且只存储或写入稀疏文件中非空的部分。例如:

  • tar接受-S选项来告诉它只存储稀疏文件的非零块:

    tar cf archive.tar -s [files…]	#将有效地将稀疏文件存储在tarball中
    tar xf archive.tar -s			#将有效存储从tarball中提取的稀疏文件
    
  • cp接受--sparse=WHEN选项,WHEN可选值包括auto/never/always这三个值之一:

    cp --sparse=always source.file dest.file # 如果source.file中有很多零,那么dest.file将是一个稀疏文件
    
2.7 查看软件包依赖关系图

Buildroot的工作之一是了解包之间的依赖关系,并确保它们按照正确的顺序构建。这些依赖关系有时可能相当复杂,对于给定的系统,通常不容易理解为什么这样或那样的包被Buildroot引入构建中。

为了帮助理解依赖关系,从而更好地理解嵌入式Linux系统中不同组件的角色,builroot能够生成依赖关系图。要生成编译的整个系统的依赖关系图,只需运行:

make graph-depends

可以在output/graphs/graph-depends.pdf中找到生成的PDF图。如果系统非常大,那么依赖关系图可能过于复杂,难以阅读。因此,可以为给定的包生成依赖关系图:

make <pkg>-graph-depends

注意,依赖关系图是使用Graphviz项目中的dot工具生成的,必须在系统上安装该工具才能使用此特性。在大多数发行版中,它作为graphviz包提供。

默认情况下,依赖关系图以PDF格式生成。但是,通过传递BR2_GRAPH_OUT环境变量,可以切换到其他输出格式,例如PNGPostScriptSVGBR2_GRAPH_DEPS_OPTS可以控制关系图输出的格式,如下:

  • --depth N/-d N,将依赖深度限制为N个级别。默认值0表示没有限制。
  • --stop-on PKG/-s PKG,停止包PKG上的图形,PKG可以是实际包名、通配符、关键字virtual(停止在虚拟包)或关键字host(停止在本地包)。PKG包仍然出现在图上,但是它的依赖项不在了。
  • --exclude PKG/-x PKG,像--stop-on一样,但也从图中省略PKG
  • --transitive/--no-transitive,绘制(或不绘制)传递依赖关系。默认情况下不绘制传递依赖项。
  • --colors R, T, H,以逗号分隔的颜色列表,用于绘制根包®,目标包(T)和本地包(H)。默认值:浅蓝色,灰色,亮灰色。

实例如下:

ubuntu->buildroot:$ BR2_GRAPH_DEPS_OPTS='--colors=red,green,cyan' make graph-depends
Getting dependency tree...
dot  -Tpdf \-o /home/ubuntu/build-root-all/buildroot/output/graphs/graph-depends.pdf \/home/ubuntu/build-root-all/buildroot/output/graphs/graph-depends.dot

下面是输出的PDF文件(buildroot编译一套toolchain):

在这里插入图片描述

2.8 查看软件包编译耗时

当系统的构建需要很长时间时,了解哪些包的构建时间最长,看看是否可以做些什么来加快构建速度,有时是有用的。为了帮助这样的构建时间分析,Buildroot收集每个包的每个步骤的构建时间,并允许从这些数据生成图形,使用如下命令即可:

make graph-build

会生成五个文件,如下:

  • build.hist-build.pdf,每个包的构建时间的直方图,按构建顺序排序。
  • build.hist-duration.pdf,每个包的构建时间的直方图,按持续时间排序(最长的优先)构建。
  • build.hist-name.pdf,每个包的构建时间的直方图,按包名排序。
  • build.pie-packages.pdf,每个包构建时间的饼状图。
  • build.pie-steps.pdf,一个饼状图,显示了包构建过程中每个步骤所花费的全局时间。
  • build.timeline.pdf,一个柱状图,按照构建顺序排布组件编译耗时,可以直观看出耗时较长的编译过程。

下面是build.hist-duration.pdf的输出,可以直观看出哪些包编译耗时较长:

在这里插入图片描述

下面是build.timeline.pdf的输出,非常直观看出编译顺序和耗时:

在这里插入图片描述

2.9 查看软件包占比图

当目标系统增长时,了解每个Buildroot包对整个根文件系统大小的贡献有时是有用的。为了帮助进行这样的分析,Buildroot收集每个包安装的文件的数据,并使用这些数据生成一个图表和CSV文件,详细说明不同包的大小贡献。使用命令如下:

make graph-size

输出如下文件:

  • output/graphs/graph-size.pdf,每个包对整个根文件系统大小的贡献的饼状图
  • output/graphs/package-size-stats.csv,一个CSV文件,给出每个包对整个根文件系统大小的大小贡献
  • output/graphs/file-size-stats. CSV,这是一个CSV文件,给出了每个已安装文件对其所属包的大小贡献,以及对整个文件系统大小的贡献。

此外,还可以设置环境变量BR2_GRAPH_SIZE_OPTS来进一步控制生成的图。可接受的选项有:

  • --size-limit X/-1 X,将把所有个人贡献低于X %的包分组到图中标记为Others的单个条目中。默认情况下,x=0.01,这意味着每个贡献小于1%的包被分组在Others下。可接受的取值范围为[0.0 ~ 1.0]。
  • --iec/--binary/--si/--decimal,使用IEC(二进制,1024的幂)或SI(十进制,1000的幂;默认)前缀。
  • --biggest-first,将包按大小递减顺序排序,而不是按大小递增顺序排序。

注意:收集的文件系统大小数据只有在完全干净地重建之后才有意义。在使用make graph-size之前,请确保运行makc clean all。要比较两个不同Buildroot编译的根文件系统大小,例如在调整配置之后或切换到另一个Buildroot版本时,可以使用size-stats-compare脚本。

它需要两个文件大小统计,csv文件(由make graph-size生成)作为输入。

ubuntu->buildroot:$ utils/size-stats-compare -h
usage: size-stats-compare [-h] [-d] [-t THRESHOLD]old-file-size-stats.csv new-file-size-stats.csv

下面是graph-size.pdf的输出:

在这里插入图片描述

3. 高级构建参数
3.1 并行构建支持

注意:本节处理的是一个非常实验性的特性,它在一些非异常情况下可以达到收支平衡。使用风险自负。

Buildroot总是能够在每个包的基础上使用并行构建:每个包都是由Buildroot使用make -jN(或非基于make的构建系统的等效调用)构建的。默认情况下,并行级别是CPUs+1,但可以使用BR2_JLEVEL配置选项进行调整。

在2020.02之前,Buildroot是以串行方式构建包的: 每个包一个接一个地构建,而包之间的构建没有并行化。截至2020.02年,Buildroot已经对顶级并行构建提供了实验性支持,通过并行构建没有依赖关系的包,可以显著节省构建时间。然而,这个功能被标记为实验性的,并且已知在某些情况下不起作用。

为了使用顶级并行构建,需要满足以下两个条件:

  • 在builroot配置中启用BR2_PER_PACKAGE_DIRECTORIES
  • 在启动Buildroot构建时使用make -jN

在内部,BR2_PER_PACKAGE_DIRECTORIES将启用一种称为**每包目录(perf-package directories)**的机制,它将具有以下效果:

  • 与所有包通用的全局目标(target)目录和全局主机(host)目录不同,将使用每个包的目标目录和主机目录,分别在$(O)/per-package/<pkg>/target/$(O)/per-package/<pkg>/host/中。这些文件夹将在<pkg>构建开始时从包依赖项的相应文件夹中填充。因此,编译器和所有其他工具只能看到和访问由<pkg>显式列出的依赖项安装的文件。
  • 在构建结束时,将填充全局目标(target)和主机(host)目录,分别位于$(O)/target$(O)/host中。这意味着在构建过程中,这些文件夹将是空的,只有在构建的最后才会填充它们。
3.2 使用在builroot之外生成的工具链

如果需要为目标设备编译自定义的程序或未打包在builroot中的其他软件,可以使用由builroot生成的工具链。默认情况下,builroot生成的工具链位于output/host/中。使用它的最简单方法是将output/host/添加到PATH环境变量中,然后使用ARCH-linux-gccARCH-linux-objdumpARCH-linux-ld等。

另外,通过运行make SDK命令,builroot还可以将所有选定包的工具链和开发文件导出为SDK。这将生成主机目录output/host/内容的tarball文件,名称如下:

output/images/<TARGET-TUPLE>_sdk-buildroot.tar.gz # 可以通过设置环境变量BR2_SDK_PREFIX来更改名字

当应用程序开发人员希望开发尚未打包为Buildroot包的应用程序时,可以将该tarball分发给应用程序开发人员。

在提取SDK tarball后,用户必须运行脚本relocate-sdk.sh(位于SDK的顶目录),以确保所有路径都使用新位置更新

或者,如果你只是想准备SDK而不生成tarball(例如,因为你将只是移动主机目录,或者将自己生成tarball),builroot也允许你只使用make prepare-sdk准备SDK而不实际生成tarball。

为了方便,通过选择BR2_PACKAGE_HOST_ENVIRONMENT_SETUP选项,可以在output/host/中安装一个environment-setup脚本。这个脚本可以从/sdk/path/environment中导出一些环境变量,这些环境变量将有助于使用Buildroot sdk交叉编译自定义项目:

  • PATH路径中将包含sdk二进制文件。
  • 标准autotools变量将用适当的值定义。
  • CONFIGURE_FLAGS将包含基本的./configure选项。

它还提供了一些有用的命令。但是请注意,一旦这个脚本被执行,环境就只会被设置为交叉编译,而不再是本机编译。

3.3 使用ccache加快编译

Ccache是一个编译器缓存。它存储每个编译过程产生的目标文件,并且能够通过使用预先存在的目标文件跳过对相同源文件(使用相同的编译器和相同的参数)的编译。当从零开始多次进行几乎相同的构建时,它可以很好地加快构建过程。

在builroot中集成了Ccache支持。你只需要在Buildoptions中启用enable compiler cache。这将自动构建ccache,并在每个主机(host)和目标(target)编译中使用它。缓存位于BR2_CCACHE_DIR配置选项定义的目录中,默认为$HOME/.buildroot-cache,此默认位置位于Buildroot输出目录之外,因此可以由单独的Buildroot构建共享。如果要删除缓存,只需删除此目录。

可以通过运行make cache-stats获取缓存的统计信息(大小、命中次数、未命中次数等)。可以按照如下方式对ccache进行更多的访问和操作:

# set cache limit size
make CCACHE_OPTIONS="--max-size=5G" ccache-options
# zero statistics counters
make CCACHE_OPTIONS="--zero-stats" ccache-options

Ccache对源文件和编译器选项进行散列处理。如果编译器选项不同,则不会使用缓存的对象文件。但是,许多编译器选项包含到暂存目录的绝对路径。因此,在不同的输出目录中构建会导致很多缓存丢失。

为了避免这个问题,builroot有使用相对路径选项(BR2_CCACHE_USE_BASEDIR)。这将把指向输出目录内的所有绝对路径重写为相对路径。因此,更改输出目录不再导致缓存丢失。相对路径的一个缺点是它们最终也是目标文件中的相对路径。因此,例如,调试器将不再找到该文件,除非您先cd到输出目录。

当使用BR2_CCACHE=y选项在builroot中启用ccache时:

  • ccache在Buildroot构建过程中使用。
  • 在Buildroot之外构建时不使用ccache,例如直接调用交叉编译器或使用SDK时。

可以使用BR2_USE_CCACHE环境变量覆盖此行为:

  • 当设置为1时,启用ccache的使用(在builroot构建期间的默认值)。
  • 当取消设置或设置为与1不同的值时,禁用ccache的使用。
3.4 重定向软件包下载目录

由builroot下载的各种tarball都存储在BR2_DL_DIR中,默认情况下是dl目录。如果想保留一个完整版本的Buildroot,它已知正在使用相关的tarball,可以复制这个目录。

这将允许使用完全相同的版本重新生成工具链和目标文件系统。如果维护多个Buildroot目录,那么共享一个下载位置可能会更好。这可以通过将BR2_DL_DIR环境变量指向一个目录来实现。如果设置了这个值,那么将覆盖builroot配置中的BR2_DL_DIR值。下面这行应该添加到<~/.bashrc>中:

export BR2_DL_DIR=<shared download location>

下载位置也可以在.config文件中设置,使用BR2_DL_DIR选项。与.config文件中的大多数选项不同,这个值被BR2_DL_DIR环境变量覆盖。

3.5 开发环境中使用

builroot的正常操作是下载一个tarball,提取它,配置,编译和安装在这个tarball中找到的软件组件。源代码在output/build/<package>-<version>中提取,这是一个临时目录: 每当使用make clean时,该目录将被完全删除,并在下一次make调用时重新创建。

即使使用Git或Subversion存储库作为包源代码的输入,builroot也会从中创建一个tarball,然后像通常使用tarball一样运行。当Buildroot主要用作集成工具来构建和集成嵌入式Linux系统的所有组件时,这种行为非常适合。

但是,如果在系统的某些组件的开发过程中使用了buildroot,那么这种行为就不是很方便了:相反,人们希望对一个包的源代码做一个小的更改,并能够使用buildrot快速重建系统。直接在output/build/<package>-<version>中进行更改不是一个合适的解决方案,因为该目录在make clean时会被删除。

因此,builroot为这个用例提供了一个特定的机制:<pkg>_OVERRIDE_SRCDIR机制。Buildroot读取一个覆盖文件,该文件允许用户告诉Buildroot某些包的源代码位置。覆盖文件的默认位置是$(CONFIG_DIR)/local.mk,由BR2_PACKAGE_OVERRIDE_FILE配置选项定义。$(CONFIG_DIR)是buildroot配置文件.config的位置,所以local.mk默认与.config文件共存,这意味着

  • 在内部构建的顶级Buildroot源目录中(即,当不使用O=时)。
  • 在外部构建的自定义目录中(即,当使用O=时)。

如果需要不同于这些默认值的位置,可以通过BR2_PACKAGE_OVERRIDE_FILE配置选项指定。在这个覆盖文件中,builroot希望找到这样的行:

<pkg1>_OVERRIDE_SRCDIR = /path/to/pkg1/sources
<pkg2>_OVERRIDE_SRCDIR = /path/to/pkg2/sources

当builroot发现对于给定的包,已经定义了<pkg>_OVERRIDE_SRCDIR时,它将不再尝试下载、解压缩和修补该包。相反,它将直接使用指定目录中可用的源代码,并且make clean不会触及该目录。这允许将builroot指向您自己的目录,这些目录可以由Git、Subversion或任何其他版本控制系统管理。

为了实现这一点,builroot将使用rsync将组件的源代码从指定的<pkg>_OVERRIDE_SRCDIR复制到output/build/<package>-custom/。此机制最好与make <pkg>-rebuildmake <pkg>-reconfigure目标结合使用。make <pkg>-rebuild all序列会将源代码从<pkg> _OVERRIDE_SRCDIR同步到output/build/<package>-custom/(多亏了rsync,只有修改过的文件被复制),并重新启动这个包的构建过程。

例如对于Linux内核软件包,开发人员可以在/home/bob/linux中更改源代码,然后运行:

make linux-rebuild all

output/images中的根文件系统映像包含更新后的Linux内核镜像。大型项目的源代码树通常包含数百或数千个文件,这些文件不是构建所需的,但会减慢使用rsync复制源代码的过程。

这可以通过定义<pkg>_OVERRIDE_SRCDIR_RSYNC_EXCLUSIONS来跳过对源树中的某些文件的同步。例如,当使用webkitgtk包时,以下命令将从本地WebKit源代码树中排除测试和树内构建:

WEBKITGTK_OVERRIDE_SRCDIR = /home/bob/WebKit
WEBKITGTK_OVERRIDE_SRCDIR_RSYNC_EXCLUSIONS = \
--exclude JSTests --exclude ManualTests --exclude PerformanceTests \
--exclude WebDriverTests --exclude WebKitBuild --exclude WebKitLibraries \
--exclude WebKit.xcworkspace --exclude Websites --exclude Examples

默认情况下,builroot会跳过VCS工件(例如.git和.svn目录)的同步。有些包更喜欢在构建期间使用这些VCS目录,例如用于自动确定版本信息的精确提交引用。要以较慢的速度为代价撤销此内置过滤,请重新添加这些目录:l

LINUX_OVERRIDE_SRCDIR_RSYNC_EXCLUSIONS = --include .git
c4. 附注信息
4.1 devtmpfs简介

devtmpfs是一种在Linux内核中实现的特殊类型的临时文件系统(tmpfs)。它被设计用于存储设备文件,这些设备文件代表系统中的设备,包括硬件设备(如硬盘驱动器和网络接口)以及一些软件或虚拟设备(如伪终端)。

devtmpfs的主要功能和作用包括:

  1. 设备管理devtmpfs为系统中的每个设备创建一个设备文件。这些设备文件位于/dev目录下,它们是设备驱动程序和用户空间应用程序之间的接口。应用程序可以通过读写这些设备文件来控制设备。

  2. 快速启动:在早期的Linux系统中,设备文件是由用户空间的udev系统在启动时创建的。这在某些情况下可能导致系统启动速度较慢,因为必须等待udev在/dev目录下创建所有设备文件。通过在内核中实现devtmpfs,设备文件可以在系统启动时立即可用,从而加快了系统的启动速度。

  3. 简化系统配置:在一些嵌入式系统或者容器环境中,可能没有 udev 或其他动态设备管理系统。在这种情况下,devtmpfs可以提供一个简单的方法来创建和管理设备文件。

  4. 动态设备管理:当新设备被插入或移除时,devtmpfs可以动态地创建或删除对应的设备文件。这使得系统可以在运行时适应硬件配置的变化。

需要注意的是,虽然devtmpfs在内核中创建设备文件,但是它并不负责设备的热插拔或其他更复杂的设备管理任务。这些任务通常由udev或其他用户空间的设备管理系统来完成。

4.2 mdev/udev/eudev简介

mdev, udev, 和 eudev 都是用于设备管理的工具,它们在 Linux 系统中动态地创建和管理设备节点:

  1. mdev:mdev 是 BusyBox 提供的一个轻量级的工具,主要用于在 Linux 系统中动态地创建和管理设备节点。它特别适合资源有限的系统,如嵌入式系统。
  2. udev:udev 是 Linux 系统的一部分,用于管理 /dev 目录中的设备节点。它可以动态地创建和删除设备节点,当新设备被添加到系统,或从系统中移除时,udev 会创建或删除相应的设备节点。
  3. eudev:eudev 是 udev 的一个分支,由 Gentoo 团队创建。它的目标是提供一个与 udev 兼容但对系统依赖性较小的设备管理程序,因此更易于在不同类型的 Linux 系统,包括嵌入式系统上使用。

以下是三者的一些主要功能和作用:

  1. 动态设备管理:当新设备被添加到系统(例如,USB 设备被插入)或从系统中移除时,mdev 可以动态地创建或删除相应的设备节点。

  2. 设备节点创建mdev 可以根据 /sys 文件系统中的设备信息来创建设备节点。

  3. 设备属性设置mdev 可以根据配置文件或环境变量来设置设备节点的所有者、组和权限。

  4. 事件处理:可以在设备添加或删除时执行特定的脚本或命令。

4.3 系统初始化程序简介

init 是 Unix 和 Unix-like 操作系统(如 Linux)的一个重要组成部分,它是在系统启动时由内核首先启动的进程,其进程号(PID)通常是 1。因为它是所有其他用户空间进程的父进程,所以它在系统操作中扮演着关键的角色。

以下是 init 进程的一些主要功能和作用:

  1. 系统初始化init 进程负责初始化系统,它会挂载必要的文件系统,启动必要的宁静和服务,以及执行其他初始化任务。

  2. 服务管理init 进程通常负责启动和管理系统服务。这些服务可能包括日志服务、网络服务、调度服务等。

  3. 系统运行级别管理init 进程管理系统的运行级别。例如,在 SysV init 系统中,不同的运行级别对应着不同的系统状态,如单用户模式、多用户模式、重启、关机等。

  4. 僵尸进程回收:当一个子进程终止,而其父进程没有获取其终止状态,那么它就会变成僵尸进程。init 进程负责定期收集这些僵尸进程,以防止它们占用系统资源。

  5. 系统关机和重启:当需要关闭或重启系统时,init 进程负责按照正确的顺序停止服务和卸载文件系统。

在许多现代 Linux 系统中,init 的功能由如 systemd、Upstart 这样的更现代的初始化系统来实现。这些系统提供了更强大和灵活的服务管理功能,如依赖性处理、并行启动、动态服务管理等:

  1. System V init:这是 Unix 和早期 Linux 系统中的传统初始化系统。System V init 使用一系列脚本,按照特定顺序启动各种系统服务。这些脚本通常位于 /etc/init.d/ 目录,每个运行级别有一个相关的目录(例如,/etc/rc.d//etc/rc*.d/),其中包含指向 init.d/ 目录中脚本的符号链接。虽然 System V init 的设计简单易懂,但它没有处理服务依赖关系,所有服务都必须按照固定的顺序启动,这可能限制了启动速度。
  2. Upstart:Upstart 是由 Ubuntu 开发的一个现代化的初始化系统,用来替代 System V init。Upstart 的主要优点是它可以处理服务的依赖关系,并且可以在系统运行时动态地启动和停止服务。此外,Upstart 还支持事件驱动的编程模型,这使得它可以更灵活地响应系统事件。然而,Upstart 在 2015 年被 Ubuntu 弃用,转而使用 systemd。
  3. systemd:systemd 是许多现代 Linux 发行版(如 Fedora、Debian、Ubuntu 和 CentOS)的默认初始化系统。systemd 提供了一种声明式的语法来定义服务及其依赖关系,并且可以并行地启动服务以加快启动速度。此外,systemd 还提供了许多其他的高级功能,如系统日志管理、用户登录会话管理、设备热插拔处理、网络配置等。

此外,systemd 自从被引入 Linux 系统以来,就在社区中引发了一些争议,如下:

  1. 集成度高systemd 不仅仅是一个初始化系统,它还包括了许多其他的功能,如日志管理、设备管理、网络配置、用户会话管理等。这与 Unix 的传统哲学"每个程序只做好一件事"相违背。一些用户和开发者认为,这种高度的集成导致 systemd 变得复杂和难以理解,也增加了出错的可能性。
  2. 不易替换:由于 systemd 的高度集成,很多其他的系统组件都依赖于 systemd 的特定功能。这使得在 systemd 的系统中替换 systemd 变得困难。一些用户和开发者认为,这限制了用户的选择,违反了 Linux 的开放和多样性的原则。
  3. 配置复杂:虽然 systemd 提供了一种声明式的语法来配置服务,但这种语法相较于传统的 shell 脚本更为复杂和难以理解。一些用户和开发者认为,这增加了学习和使用 systemd 的难度。
  4. 开发和决策过程:一些用户和开发者对 systemd 的开发和决策过程表示不满。他们认为 systemd 的开发团队不够接纳社区的反馈,而且对 systemd 的决策过于集权。

虽然有这些争议,但也应该认识到 systemd 也有其优点,如启动速度快、并行启动服务、处理服务依赖等,并且它已经被许多主流的 Linux 发行版采用。

4.4 getty简介

getty 是 Unix 和 Unix-like 系统(如 Linux)中的一个程序,它的主要功能是管理物理或虚拟的终端行。getty 的名字来源于 “get teletype”,它通常在系统启动时由 init 进程启动,并在特定的终端行上等待用户登录。

以下是 getty 的一些主要功能和作用:

  1. 终端行设置getty 负责设置终端行的参数,如波特率、字符大小、奇偶校验等。这在使用物理串行终端或调制解调器时特别重要。

  2. 用户登录getty 在终端上显示登录提示符,等待用户输入用户名。一旦用户输入用户名,getty 就会启动登录程序(如 login),然后让用户输入密码进行身份验证。

  3. 会话管理:一旦用户成功登录,getty 就会启动一个 shell 以开始用户的会话。当会话结束时(例如,用户登出或会话被断开),getty 会重新设置终端并等待下一个用户登录。

4.5 稀疏文件(Sparse file)

稀疏文件(Sparse file)是一种文件存储方式,用于在文件系统中有效地保存数据。它的主要特点是只保存实际有用的数据,而对于文件中的空白部分(即全零的数据块),则不进行实际的磁盘空间分配。

稀疏文件的作用:

  1. 空间效率:稀疏文件最大的优点就是它可以非常高效地使用存储空间。如果一个文件的大部分内容都是空的(也就是说,大部分的字节都是零),那么使用稀疏文件可以显著减少实际使用的磁盘空间。

  2. 时间效率:由于稀疏文件只写入实际存在的数据,因此在处理大文件时,可以显著提高读写速度。

  3. 灵活性:稀疏文件可以创建实际大小超过物理存储空间的文件,这在某些场景下非常有用,例如某些数据库操作,或者虚拟磁盘映像等。

稀疏文件的介绍:

当创建一个稀疏文件时,文件系统并不会立即分配所有的磁盘空间。相反,它只会在数据实际写入时才分配空间。如果数据块全是0,文件系统会记录这个信息,但并不会在磁盘上分配空间。因此,一个稀疏文件的大小(逻辑大小)通常会比它实际占用的磁盘空间(物理大小)大得多。

例如,如果你创建一个10GB的文件,其中9.5GB都是空白,那么这个文件的逻辑大小是10GB,但它的物理大小可能只有0.5GB。这就是稀疏文件能高效利用存储空间的原因。

在Unix-like系统(如Linux)中,可以使用dd命令创建稀疏文件,也可以使用cp --sparse=always命令将普通文件转化为稀疏文件。并且,许多文件系统,如ext3,ext4,XFS,Btrfs等都支持稀疏文件。

但是,需要注意的是,不是所有的文件系统都支持稀疏文件。在不支持稀疏文件的文件系统上,所有的零都会被实际写入,这就失去了使用稀疏文件的意义。因此,使用稀疏文件时,要确保你的文件系统支持这一特性。

4.6 Ccache介绍

ccache 是一个开源的编译缓存工具。它能大幅度地加速C/C++代码的重新编译速度。ccache 通过缓存之前编译过的源代码的对象文件,然后在源代码没有发生改变的情况下,直接使用这些缓存的对象文件,从而避免了重复的编译过程。

ccache的功能:

  1. 缓存编译结果ccache 将编译结果(对象文件)存储在缓存中。当源文件没有改变时,ccache 会直接从缓存中取出已编译的结果,而不是重新编译。这大大减少了编译时间。

  2. 自动处理ccache 可以自动处理C/C++预处理器的输出,这意味着你可以在不改变构建系统的情况下使用它。

  3. 灵活配置:你可以设置缓存的大小,当缓存满时,ccache 会自动清理最少使用的缓存项。

  4. 统计信息ccache 提供了详细的统计信息,允许你查看缓存的使用情况和效果。

ccache的作用:

  1. 提高编译速度:对于大型的C/C++项目,编译通常需要很长的时间。如果源文件没有改变,使用ccache 可以避免重复编译,大大提高编译速度。

  2. 节省资源:通过避免不必要的编译,ccache 可以节省CPU的计算资源,以及用于存储编译结果的磁盘空间。

  3. 提高开发效率ccache 的使用可以让开发者更快地进行编译-测试-修改的开发流程,从而提高开发效率。

在实际使用中,ccache 通常被设置为编译器的一个包装器(wrapper),当执行编译命令(如gccg++)时,实际上执行的是ccacheccache 会判断是否可以使用缓存的结果,如果可以,就直接使用缓存;如果不可以,就执行实际的编译命令,然后将编译结果保存到缓存中。

需要注意的是,ccache 最适合用于频繁编译的场景,如持续集成(Continuous Integration)或者频繁的源代码修改。对于只编译一次的项目,使用 ccache 的效果可能并不明显。
使用它。

相关文章:

  • JavaScript中的时间日期函数new Date()(JS中5种获取时间戳的函数)
  • SELinux refpolicy详解(5)
  • 无人机助力电力设备螺母缺销智能检测识别,python基于YOLOv5开发构建电力设备螺母缺销小目标检测识别系统
  • [github全教程]github版本控制最全教学------- 大厂找工作面试必备!
  • uniapp uni-popup组件在微信小程序中滚动穿透问题
  • python获取网络时间,0延时
  • 使用Xshell启动远程服务器上的tensorboard:本地浏览器打开
  • uniapp在H5端实现PDF和视频的上传、预览、下载
  • 国家开放大学 平时作业 测试题 训练
  • visual Studio MFC 平台实现图像增强中Gray-level slicing,Bit-plane slicing,对比度拉伸三种方法
  • ECShop 4.x collection_listSQL注入
  • 软考2016年上半年第六题(适配器模式)与手术训练系统项目适配器模式的应用
  • Google Analytics(谷歌分析)是什么以及如何使用
  • 血的教训------入侵redis之利用python来破解redis密码
  • 使用 NRF24L01 无线收发模块进行远程控制
  • 【个人向】《HTTP图解》阅后小结
  • 345-反转字符串中的元音字母
  • 4个实用的微服务测试策略
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • java2019面试题北京
  • JavaScript-Array类型
  • JavaWeb(学习笔记二)
  • JS题目及答案整理
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • 从0到1:PostCSS 插件开发最佳实践
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 使用 Docker 部署 Spring Boot项目
  • 通过git安装npm私有模块
  • 转载:[译] 内容加速黑科技趣谈
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • # centos7下FFmpeg环境部署记录
  • #、%和$符号在OGNL表达式中经常出现
  • #stm32驱动外设模块总结w5500模块
  • #Z0458. 树的中心2
  • (1)bark-ml
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (一)kafka实战——kafka源码编译启动
  • (一)SpringBoot3---尚硅谷总结
  • .apk 成为历史!
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .Net Core与存储过程(一)
  • .net mvc 获取url中controller和action
  • .NET 事件模型教程(二)
  • @ModelAttribute 注解
  • [ C++ ] STL---仿函数与priority_queue
  • [AX]AX2012 SSRS报表Drill through action
  • [bzoj 3534][Sdoi2014] 重建