顶层目录
目 录 | 含 义 |
---|---|
config | 编译选项配置文件,包含全局编译设置、开发人员编译设置、目标文件格式设置和内核 编译设置等 4 部分 |
include | 包含准备环境脚本、下载补丁脚本、编译 Makefile 以及编译指令等 |
package | 各种功能的软件包,软件包仅包含 Makefile 和修改补丁及配置文件。其中 Makefile 包含 源代码真正的地址及 MD5 值。OpenWrt 社区的修改代码以补丁包形式管理,package 只 保存一些常用的软件包 |
scripts | 包含准备环境脚本、下载补丁脚本、编译 Makefile 以及编译指令等 |
target | 指的是嵌入式平台,包括特定嵌入式平台的内容 |
toolchain | 编译器和C库等(交叉编译工具),例如包含编译工具gcc和glibc库 |
tools | 通用命令/工具,用来生成固件的辅助工具,如打补丁工具patch、编译工具make及squashfs等 |
config目录
- 目录下存放的是编译配置文件,是 OpenWrt 15.05 的新增目录,是将一些编译选项配置文件放在此处,包含全局编译设置、开发人员编译设置、目标文件格式设置和内核编译设置等4部分。
- 编译源码时,输入 make defconfig 命令,这个目录下的配置文件会被集中读取并生成一个 .config 配置文件,该文件在下面会介绍。
包含文件
- Config-build.in:最基本的配置文件(全局编译)。
- Config-devel.in:用于开发的编译配置文件。
- Config-images.in:目标文件格式设置(生成某种镜像的配置文件)。
- Config-kernel.in:关于内核编译的配置文件。
in配置文件的内容与格式
- 下面我们以“Config-build.in”格式为例。
- **menu:**我们在目录下输入“make menuconfig”之后显示的主菜单项,例如上面我们的menu为“Global build settings”,在主菜单项中可以看到一个选项为“Global build settings”的内容。(备注:menu以endmenu结尾)。
- **config:**config下的bool的内容为我们回车进入某一个菜单项之中显示的子项。但是如果一个config的上一级没有menu项,那么config下的bool内容直接显示在主菜单项中(例如下面的"Advanced configuration options (for developers)“项上一级不是enu,那么其直接显示在主菜单中)。
- **default:**代表默认值,如果为y代表是默认选择的,如果为n代表默认不选择。
- **help:**这个选项的帮助解释说明。
- 例如下面我们有一个"Compile with support for patented functionality"子选项,其default为y,所以前面有“*”号为默认选择。当我们在下面选择“help”然后回车,可以看到此子选项的注释。
增加自定义模块
- 根据.in文件的格式,我们也可以自己设置菜单项和内容,然后在编译时使用。
- 例如:下面我们在target/Config-buidl.in配置文件中加上下面内容(一个“TEST”主选项,一个“This is my test”子选项,子选项默认值为“y”,帮助内容为“dongshao CSDN url: https://me.csdn.net/qq_41453285”)。
- 接着来到来到顶级目录下,输入“make menuconfig”进入配置工具选项菜单,可以看到我们修改的内容。
- 进入“TEST”选项。
- 选择下方的Help查看帮助信息。
include目录
- 保存各种makefile文件:
.mk脚本在编译时的作用
- 在顶层目录下有一个“Makefile”脚本,当我们在编译源码时,输入make命令,“Makefile”脚本被执行,紧接着.mk脚本会被顶级目录下的Makefile调用,然后这些.mk脚本会解析“.config”文件(.config文件下面有介绍),并且根据“.config”文件的内容来编译相关内容(例如下载、编译安装packages目录下的第三方软件等)。
package目录
- 存放各种必要的软件包,可以供二次开放使用。
- 当我们在顶级目录下输入“./script/feeds update -a、./script/feeds install -a”命令时更新和下载的就是这个目录下的软件。
- **特性:**我们可以根据自己的需求,在这个目录下新建目录(根据软件类型存放在对应的目录下),然后编写程序和Makefile脚本,这样就可以编译生成自己想要的软件了。
目录结构
- 目录结构分为:功能目录–>软件目录。例如下面以“devel”这个功能模块为例,其目录下有“gdb、perf、strace”这些软件。
Makefile脚本
- 在每一个软件的目录下,都有一个Makefile文件。这个Makefile脚本会在编译源码时被执行(何时执行?include目录下的.mk脚本执行时,.mk脚本解析.config脚本,.config脚本中记录着软件的名称以及是否要安装“y/n”,如果某个软件选项为“y”,那么软件的Makefile脚本会被执行,于是这个软件就会被下载安装;如果为“n”,那么就不会被安装)。
- 小面我们以gdb这个软件为例,其目录下有一个Makefile文件,Makefile内有软件的名称、软件版本、要下载的包文件名、下载的软件的名称、软件包的HASH算法,软件的名称下载地址。
scripts目录
- 目录scripts为编译工具脚本文件。
一些比较重要的脚本及功能
- 例如patch-kernel.sh封装了patch命令,在编译时,首先将 patches 目录下的所有补丁文件打上,并且判断如果打补丁失败将退出编译过程。
- download.pl 为下载源代码的工具脚本,封装下载工具 wget 的选项以及设置从哪里下载。
- 简述如下:
脚 本 文 件 含 义 scripts/download.pl 下载编译软件包源代码 scripts/patch-kernel.sh 打补丁脚本,并且判断如果打补丁失败将退出编译过程 scripts/feeds 收集扩展软件包的工具,用于下载和安装编译扩展软件包工具 scripts/diffconfig.sh 收集和默认配置不同之处的工具 scripts/kconfig.pl 处理内核配置 scripts/deptest.sh 自动 OpenWrt 的软件包依赖项检查 scripts/metadata.pl 检查 metadata scritps/rstrp.sh 丢弃目标文件中的符号,这样就将执行文件和动态库变小 scripts/timestamp.pl 生成文件的时间戳 scripts/ipkg-make-index.sh 生成软件包的 ipkg 索引,在使用 opkg 安装软件时使用 scripts/ext-toolchain.sh 工具链 scripts/strip-kmod.sh 删除内核模块的符号信息,使文件变小
target目录
- 存放用于编译各类平台使用的二进制文件,定义了各类平台编译固件和内核的具体过程。
- 是指目标嵌入式设备,针对不同的平台有不同的特性代码。
- 例如下面的“target/linux”目录是对Linux系统的划分,目录下包括Linux系统针对于各种平台标准内核的不订及特殊配置(如ar7、arm系统)。
tools、toolchain目录
tools目录
- 是一些通用命令/工具,用来生成固件的辅助工具,如打补丁工具patch、编译工具make及squashfs等。
- 与package目录结构类似,每个工具占一个目录,工具目录下都有该工具下载所对应的Makefile。
toolchain目录
- 与tools差不多,也是一些工具,编译器和C库等(交叉编译工具),例如包含编译工具gcc和glibc库,在嵌入式交叉编译时有用。
- 与package目录结构类似,每个工具占一个目录,工具目录下都有该工具下载所对应的Makefile。
顶级目录Makefile
- 进行编译的总体工作。
- 自己开发时,这个Makefile不需要改动,因此这个Makefile对我们也不重要,只需要大致了解一下即可。
Makefile的world目标
- 执行make时,如果不指定任何目标,因为world目标处于第一位,所以默认执行world。
- 在make时不指定OPENWRT_BUILD参数时,进入ifneq语句,如果编译时make OPENWRT_BUILD=1则进入else。
- 进入ifneq语句:
- 重写OPENWRT_BUILD变量并export导出OPENWRT_BUILD变量。
- 包含仅顶级目录下include目录下的debug.mk、depends.mk、toplevel.mk。
- debug.mk:在编译过程中各类信息的输出 (V=s参数用到)。
- depends.m:检查当前系统在编译内核阶段所有需要依赖的包是否安装。
- toplevel.mk:解析编译world目标的规则。
- **toplevel.mk:**主要为了生成下面两个目标(prereq和world),在toplevel.mk180行开始。
prereq:: prepare-tmpinfo .config
@make -r -s tmp/.prereq-build
@make V=ss -r -s
prereq %:
@make V=ss -r -s
@make -w -r world
Makefile其他目标
- “make clean”:删除编译目录。
- “make dirclean”:除了删除编译目录之外还删除编译工具目录。
- “make printdb”:输出所有的编译变量定义。
feeds.conf.default、feeds.conf文件
- 这两个是我们自己添加进去的文件。
- script/feeds脚本文件会调用这两个配置文件,配置文件中定义了大量第三方软件包的下载地址(使用了国内的镜像源,下载速度更快),下图是script/feeds脚本文件,可以看到其调用了这两个配置文件。
配置文件支持的语法:
- src-git:通过git的方式从后面的链接进行下载。
- src-cpy:通过path进行拷贝 (通过U盘更新等)。
- src-bzr:通过bzr的方式从后面的链接进行下载 。
- src-link:创建一个数据源path的symlink。
- src-svn:通过svn的方式从后面的链接进行下载。
演示案例
src-git telephony https://git.lede-project.org/feed/telephony.git^ac6415e61f147a6892fd2785337aec93ddc68fa9
编译时创建的临时目录
- 编译工具链、目标平台的软件包等需要下载的文件都放在dl目录下。目标平台和软件包两部分都需要“build_dir/”作为编译的临时目录,并且会将目录 staging_dir作为编译的临时安装目录,最终的生成文件保存在目录bin下。
目 录 | 含 义 |
---|---|
dl | 下载软件代码包临时目录。编译前,将原始的软件代码包下载到该目录 |
feeds | 扩展软件包目录。将一些不常用的软件包放在其他代码库中,通过feed机制可以自定义下载及配置 |
bin | 编译完成后的最终成果目录。例如安装映像文件及 ipk 安装包 |
build_dir | 编译中间文件目录。例如生成的.o 文件 |
staging_dir | 编译安装目录。文件安装到这里,并由这里的文件生成最终的编译成果 |
log | 如果打开了针对开发人员 log 选项,则将编译 log 保存在这个目录下,否则该目录并不会创建 |
tmp | 编译过程的大量临时文件都会在此 |
feeds目录
- 在OpenWrt固件中,几乎所有东西都是软件包(package),可以编译为以“.ipk”结尾的安装包,这样就可以很方便地安装、升级和卸载了。注意,扩展软件包不是在主分支中维护的,但是可以使用软件包编译扩展机制(feeds)来进行扩展安装。这些包能够扩展基本系统的功能,只需要将它们链接进入主干。之后,这些软件包将会显示在编译配置菜单中。
- 目录feeds用于保存扩展软件包,可以使用软件包编译扩展机制来进行扩展安装。这些包能够扩展基本系统的功能,只需要将它们链接进入编译主目录的package目录下。 之后,这些软件包将会显示在配置菜单中。
- ./sripts/feeds install -a时,feeds目录就产生了,安装的软件就存放在这个目录下了。
dl目录
- 编译工具链、目标平台的软件包等需要下载的文件都放在dl目录下。
- 在编译过程中,各类需要下载的包都保存在这个目录下 (编译过程中用的工具)。
- 当编译的过程中,如果出错,出错的原因是某个软件包下载错误或丢失,可以手动下载对应的软件包(压缩文件形式),并放在这个目录下,之后重新编译。
dl目录与feeds目录的区别
- dl中存放的是编译过程中需要用到的工具,而feeds中存放的是系统编译好之后在系统中需要用的软件。
build_dir目录
- 交叉编译工具的编译中间文件目录。例如生成的.o文件
“build_dir/host”目录
- “build_dir/host”是一个临时目录,用来储存不依赖于目标平台的工具。
- tools目录中各类工具编译的结果存放在host中,因此可以看到这些目录的名称与tools下的工具名称相同。例如进入一个软件目录,看到其编译结果如下:
build_dir/toolchain-目录
- 用来储存依赖于指定平台的编译工具链。
- tools-chain目录交叉编译工具最终编译的结果文件。
“build_dir/”目录
- 目标平台和软件包两部分都需要“build_dir/”作为编译的临时目录。
staging_dir目录
- staging_dir作为编译的临时安装目录
staging_dir/toolchain-*目录
- 是编译工具链的最终安装位置。
- 通常我们不需要改动编译链目录下的任何东西,除非要更新编译工具版本等。
tools、toolchain、build_dir、staging_dir四者的关系
- tools、toolchain目录中的编译中间文件存放在buidl_dir目录下。例如生成的.o 文件等。
- buidl_dir目录存放的软件编译文件,最终安装在staging_dir目录下,因此staging_dir目录为编译安装目录,文件安装到staging_dir目录,并由staging_dir目录的文件生成最终的编译成果。
- **所以:**流程是:tools、toolchain==>编译到build_dir中==>安装到staging_dir中。
bin目录
- 编译完成后的最终成果目录。例如安装映像文件及ipk安装包。
packages目录
- 这个目录下主要保存我们编译生成的系统软件包(.ipk)。
- 我们进入packages目录。
- 在进入i386_pentium4目录,可以看到有软件包的分类目录。
- 进入base软件包目录(一些系统基础的软件),进入之后可以看到很多我们在编译时选择的很多软件生成的软件包。
- 进入luci软件包目录,我们在编译时选择了生成luci软件,可以看到目录下一些的luci软件包。
- 进入packages目录,其中也有一些文件。
targets目录
- 这个目录保存我们的系统镜像文件(按照系统的类别分类)。
- 我们在make menuconfig时选择了x86平台,就进入targets目录之后就可以看到一个属于x86平台的目录。
- 进入x86平台的目录,其中有一个generic目录,再进入generic目录,可以看到其中有一系列我们可以使用的openwrt系统的镜像文件(备注:不同的镜像文件特点是什么见文章:https://blog.csdn.net/qq_41453285/article/details/101223847)。
log目录
- 如果打开了针对开发人员 log 选项,则将编译log保存在这个目录下,否则该目录并不会创建。
tmp目录
- 编译过程的大量临时文件都会在此。
附加知识
.config配置文件
- 编译源码时,输入“make defconfig”之后会生成一个“.config”文件,内容大致如下:
- **文件的内容是什么,从何处来?**这个.config会读取config目录下的4个配置文件(Config-build.in、Config-devel.in、Config-images.in、Config-kernel.in),根据.in文件中的设置生成对应的“选项”与选项对应的“内容/值”。
- 例如上面图中我们.config文件读取的“Config-Images.in”文件的内容中,“CONFIG_TARGET_ROOTFS_EXT4FS”设置为“y”,“CONFIG_TARGET_EXT4_RESERVED_PCT”被设置为“0”,都是读取“Config-Images.in”文件获取的(下图是Config-Images.in的内)。
- **该文件何时被用到?**当我们在编译源码时,输入“make menuconfig”进入配置工具选项菜单,此时就会读取这个“.config”文件,并根据“.config”文件的内容设置选项的默认值。例如上面的“.config”文件中“ext4”选项默认为“y”,配置选项菜单时“ext4”前面默有个“*”号就代表被选中;“ext4”选项的子选项"Percentage of reserved blocks in root filesystem"默认值为“0”,可以看到其默认值就为“0”。
- **.config内容的修改:**上面说过配置工具选项菜单会读取.config的内容,因此在配置工具选项菜单中修改的内容保存也是在这个配置文件中。例如上面将上面的那个“ext4”选项取消,可以看到在右侧的“.config”文件中“Root filesystem images”下方的那3个选消失了。
scripts/download.pl下载工具
- OpenWrt在构建时首先下载代码,就是使用 scripts/download.pl 脚本进行下载。
- 这个下载功能最重要的接口是我们可以通过“scripts/localmirrors”文件自定义软件包 下载地址,方便开发人员进行设置。
- 最近有很多 iPhone/Android 编译工具爆出后门问题,就是因为使用其他第三方镜像地 址文件来下载编译工具,但没有对下载的软件内容进行 MD5 值对比,从而导致编译的应 用程序感染后门。OpenWrt 的下载检查机制从源头上解决了这类问题。在我开发 OpenWrt 时也发现了下载的一些内容被感染的问题,但检查机制丢弃了不正确的内容,从下一个的 镜像网站上继续下载。
使用方法如下:
./download.pl <target dir> <filename> <md5sum> [<mirror> ...]
:为下载之后的保存位置,下载代码通常均保存在 dl 目录下。 :待下载的文件名。 :下载内容的 MD5,用于校验下载文件是否正确。 :为可选的参数,是下载文件的镜像地址,可以有多个地址,优先选择第一个, 如果下载失败则顺序选择后面的地址。
代码解析:
- 该程序由 Perl 语言开发出来,代码并不复杂。代码首先进行初始条件检查,判断参数 是否足够,至少需要 3 个参数分别为下载文件保存位置、下载文件名及下载内容 MD5 值。
- 接着从命令行参数中顺序读取数据,并赋值给局部变量,最后判断 md5sum 或 md5 工具是否存在,如果不存在提示工具不存在后退出。
- 紧接着调用 localmirrors()函数读取本地的源码镜像地址,我们可以在企业内部创建自 己的代码镜像服务器,然后将镜像地址放在“scripts/localmirrors”文件中,这样我们就不 用每次编译时都从互联网上去下载了。例如我这里修改如下:
cat localmirrors
http://192.168.1.106:8080/openwrt/
http://mirror.bjtu.edu.cn/gnu/
- 紧接着遍历命令行并将代码中的镜像地址加到备选镜像中。
- 最后使用 while 循环进行下载,如果下载完成就对下载文件的 MD5 进行对比,如果 MD5 值一致则退出循环,否则 进入下一个镜像地址进行下载。下载成功后调用 cleanup()函数来清理临时变量。
patch-kernel.sh脚本
- OpenWrt 的代码包中大多均有 patches 目录。下载代码包完成后进行打补丁,采用的 就是 patck-kernel.sh 脚本。
- 脚本的第一个参数为编译代码目录,第二个为补丁目录。
调用脚本形式举例如下:
../scripts/patch-kernel.sh iproute2-3.3.0 ../package/iproute2/patches/
- 上述命令执行流程如下:
- ①首先进行参数赋值,第一个参数为代码目录,第二个参数为补丁目录
- ②第二步判定代码目录和补丁目录是否存在,如果不存在则提示错误并退出。
- ③遍历补丁文件,根据后缀判断补丁文件类型。
- ④调用 patch 命令应用补丁
- ⑤检查补丁应用是否正确,如果存在“*.rej”文件表示出现错误,返回“1”并退出。
- ⑥最后检查如果存在应用补丁后的备份文件,则删除备份文件
编译扩展机制feeds
- 传统的 Linux 操作系统在编译某一个软件的时候,会检查其依赖软件及头文件是否存在,如果没有安装,则会报缺少头文件或缺少链接库等错误,编译将退出。这种机制使得 开发者在编译一个软件之前,需要查找该软件所需的依赖库及头文件,并手动去安装这些 软件。有时碰到比较娇贵的软件时,嵌套式的安装依赖文件,会使得开发者头昏脑胀。 OpenWrt 通过引入 feeds 机制,很好地解决了这个问题。
- feeds 是OpenWrt开发所需要的软件包套件的工具及更新地址集合,这些软件包通过 一个统一的接口地址进行访问。这样用户可以不用关心扩展包的存储位置,可以减少扩展 软件包和核心代码部分的耦合。它由两部分组成,即扩展包位置配置文件 feeds.conf.default 脚本工具 feeds。
目前在配置文件中保存最重要的扩展软件包集合有以下4个:
- ‘LuCI’OpenWrt 默认的 Web 浏览器图形用户接口。
- ‘routing’一些额外的基础路由器特性软件,包含动态路由 Quagga 等。
- ‘telephony’IP 电话相关的软件包,例如 freeswitch 和 Asterisk 等。
- ‘management’TR069 等各种管理软件包。
在前面编译OpenWrt源码之前,我们做了如下的操作:
- 上述操作,就是利用 feeds 提供的接口将 OpenWrt 所需的全部扩展软件包进行下载并 安装。在更新时,需要能够访问互联网。在下载之前可以通过查看“feeds.conf.default”文 件,来检查哪些文件需要包含在编译环境中。
./scripts/feeds update –a
./scripts/feeds install -a
feeds工具用法如下:
- **update:**下载在 feeds.conf 或 feeds.conf.default 文件中的软件包列表并创建索引。-a 表 示更新所有的软件包。只有更新后才能进行后面的操作
- **list:**从创建的索引文件“feed.index”中读取列表并显示。只有进行更新之后才能查 看列表
- **install:**安装软件包以及它所依赖的软件包,从 feeds 目录安装到 package 目录,即在 “package/feeds”目录创建软件包的软链接。只有安装之后,在后面执行“make menuconfig” 时,才可以对相关软件包是否编译进行选择
例如安装 luci-app-firewall:
./scripts/feeds install luci-app-firewall
- **search:**按照给定的字符串来查找软件包,需要传入一个字符串参数。
- **uninstall:**卸载软件包,但它没有处理依赖关系,仅仅删除本软件包的软链接。
- **clean:**删除 update 命令下载和生成的索引文件,但不会删除 install 创建的链接。
feeds代码处理流程:
- 这个命令首先读取并解析feeds.conf 配置文件,然 后执行相应的命令,例如 install 时,将安装应用程序包和它所有直接或间接依赖的所有软件包。安装时将创建一个符号链接,从 packages/feeds/$feed_name/$package_name指feeds/$feed_name/$package_name, 这样在“make menuconfig”时,feeds 的软件包就可 以被处理到,就可以选择编译了。
- 例如“luci-app-firewall”指向“feeds/luci/applications/luciapp-firewall”:
ls package/feeds/luci/luci-app-firewall -alht
相关配置文件:
- 用一句话来说,编译扩展安装过程就是将feeds目录下的软件包链接到packages/feeds对应目录下。可使用的feeds列表配置为feeds.conf或者 feeds.conf.default。优先选择feeds.conf 文件,这个文件包含了扩展安装源列表,每一行由3部分组成,包含feed方、 feed名字和feed源。
- 下面是一个扩展安装源配置文件的例子:
src-git luci https://github.com/openwrt/luci.git;for-15.05
src-git routing https://github.com/openwrt-routing/packages.git;for15.05
src-git telephony https://github.com/openwrt/telephony.git;for-15.05
src-gitmanagement https://github.com/openwrt-management/packages.git;for-15.05
- 我们可以修改该文件使编译时从自己指定的位置进行下载。主要支持feed方法的类型有以下3种:
- src-cpy:通过从数据源路径复制数据。
- src-git:通过使用 Git 从代码仓库地址下载代码数据。
- src-svn:通过使用 SVN 从代码仓库地址下载代码数据。