本节书摘来自异步社区《智能路由器开发指南》一书中的第2章,第2.2节,作者 张永智,李章明,更多章节内容可以访问云栖社区“异步社区”公众号查看。
openwrt代码有8个固定的顶层目录及6个编译时创建的临时目录,顶层的固定目录含义如表2-3所示。

目录config是编译配置文件目录,是openwrt 15.05的新增目录,是将一些编译选项配置文件分类放在这里,包含全局编译设置、开发人员编译设置、目标文件格式设置和内核编译设置等4部分。
目录include和scripts包含各种脚本和makefile。目录target是指目标嵌入式设备,针对不同的平台有不同的特性代码。针对这些平台特性,“target/linux”目录下按照平台进行目录划分,里面包括了针对各种平台标准内核的补丁及特殊配置等。目录tools和toolchain包含了一些通用命令,用来生成固件、编译器和c语言链接库。目录docs在编译时不需要,用于存放开发文档。目录package则用于存放各种必要的软件包。
编译生成结果会储存在以下3个目录下:“build_dir/host”是一个临时目录,用来储存不依赖于目标平台的工具;“build_dir/toolchain-”用来储存依赖于指定平台的编译工具链;“staging_dir/toolchain-”是编译工具链的最终安装位置。通常我们不需要改动编译链目录下的任何东西,除非要更新编译工具版本等。
在openwrt固件中,几乎所有东西都是软件包(package),可以编译为以“.ipk”结尾的安装包,这样就可以很方便地安装、升级和卸载了。注意,扩展软件包不是在主分支中维护的,但是可以使用软件包编译扩展机制(feeds)来进行扩展安装。这些包能够扩展基本系统的功能,只需要将它们链接进入主干。之后,这些软件包将会显示在编译配置菜单中。
编译工具链、目标平台的软件包等需要下载的文件都放在dl目录下。目标平台和软件包两部分都需要“build_dir/”作为编译的临时目录,并且会将目录staging_dir作为编译的临时安装目录,最终的生成文件保存在目录bin下。
目录feeds用于保存扩展软件包,可以使用软件包编译扩展机制来进行扩展安装。这些包能够扩展基本系统的功能,只需要将它们链接进入编译主目录的package目录下。之后,这些软件包将会显示在配置菜单中。编译后生成6个临时目录,其含义如表2-4所示。
目录scripts为编译工具脚本文件,例如patch-kernel.sh封装了patch命令,在编译时,首先将patches目录下的所有补丁文件打上,并且判断如果打补丁失败将退出编译过程。download.pl为下载源代码的工具脚本,封装下载工具wget的选项以及设置从哪里下载。表2-5所示为典型编译脚本功能。目录include用于保存各种makefile文件。
openwrt在构建时首先下载代码,就是使用scripts/download.pl脚本进行下载,使用方法如下:
为下载之后的保存位置,下载代码通常均保存在dl目录下。
待下载的文件名。
下载内容的md5,用于校验下载文件是否正确。
为可选的参数,是下载文件的镜像地址,可以有多个地址,优先选择第一个,如果下载失败则顺序选择后面的地址。
该程序由perl语言开发出来,代码并不复杂。代码首先进行初始条件检查,判断参数是否足够,至少需要3个参数分别为下载文件保存位置、下载文件名及下载内容md5值。 接着从命令行参数中顺序读取数据,并赋值给局部变量,最后判断md5sum或md5工具是否存在,如果不存在提示工具不存在后退出。
紧接着调用localmirrors()函数读取本地的源码镜像地址,我们可以在企业内部创建自己的代码镜像服务器,然后将镜像地址放在“scripts/localmirrors”文件中,这样我们就不用每次编译时都从互联网上去下载了。例如我这里修改如下:
紧接着遍历命令行并将代码中的镜像地址加到备选镜像中。最后使用while循环进行下载,如果下载完成就对下载文件的md5进行对比,如果md5值一致则退出循环,否则进入下一个镜像地址进行下载。下载成功后调用cleanup()函数来清理临时变量。
这个下载功能最重要的接口是我们可以通过“scripts/localmirrors”文件自定义软件包下载地址,方便开发人员进行设置。
最近有很多iphone/android编译工具爆出后门问题,就是因为使用其他第三方镜像地址文件来下载编译工具,但没有对下载的软件内容进行md5值对比,从而导致编译的应用程序感染后门。openwrt的下载检查机制从源头上解决了这类问题。在我开发openwrt时也发现了下载的一些内容被感染的问题,但检查机制丢弃了不正确的内容,从下一个的镜像网站上继续下载。
openwrt的代码包中大多均有patches目录。下载代码包完成后进行打补丁,采用的就是patck-kernel.sh脚本。脚本的第一个参数为编译代码目录,第二个为补丁目录,调用脚本形式举例如下。
执行流程如下。
(1)首先进行参数赋值,第一个参数为代码目录,第二个参数为补丁目录。
(2)第二步判定代码目录和补丁目录是否存在,如果不存在则提示错误并退出。
(3)遍历补丁文件,根据后缀判断补丁文件类型。
(4)调用patch命令应用补丁。
(5)检查补丁应用是否正确,如果存在“*.rej”文件表示出现错误,返回“1”并退出。
(6)最后检查如果存在应用补丁后的备份文件,则删除备份文件。
传统的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”文件,来检查哪些文件需要包含在编译环境中。feeds工具用法如下。
update:下载在feeds.conf或feeds.conf.default文件中的软件包列表并创建索引。-a表示更新所有的软件包。只有更新后才能进行后面的操作。
list:从创建的索引文件“feed.index”中读取列表并显示。只有进行更新之后才能查看列表。
install:安装软件包以及它所依赖的软件包,从feeds目录安装到package目录,即在“package/feeds”目录创建软件包的软链接。只有安装之后,在后面执行“make menuconfig”时,才可以对相关软件包是否编译进行选择。
例如安装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/luci- app-firewall:
用一句话来说,编译扩展安装过程就是将feeds目录下的软件包链接到packages/feeds对应目录下。可使用的feeds列表配置为feeds.conf或者feeds.conf.default。优先选择feeds.conf文件,这个文件包含了扩展安装源列表,每一行由3部分组成,包含feed方法、feed 名字和feed源。下面是一个扩展安装源配置文件的例子。
我们可以修改该文件使编译时从自己指定的位置进行下载。主要支持feed方法的类型有以下3种。
src-cpy通过从数据源路径复制数据。
src-git通过使用git从代码仓库地址下载代码数据。
src-svn通过使用svn从代码仓库地址下载代码数据。