OpenWrt是一个比较完善的嵌入式Linux开发平台,在无线路由器应用上已有100多个软件包。人们可以在其基础上增加软件包,以扩大其应用范围。OpenWrt在增加软件方面使用极其方便,按照OpenWrt的约定就可以很简单完成。
加入的软件包可以是网上可下载的开源软件或自行开发的软件。為加入软件包需要在package目錄下创建一个目录,以包含软件包的各种信息和与OpenWrt建立联系的文件。然后创建一个Makefile与OpenWrt建立联系,Makefile需要遵循OpenWrt的约定。另外可以創建一個patchs目錄保存patch文件,對下載的源代碼進行適量修改。下面主要介紹Makefile的基本約定。
1、引入文件
OpenWrt使用三个makefile的子文件,分别为:
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
include $(INCLUDE_DIR)/package.mk
由這些makefile子文件確立軟件包加入OpenWrt的方式和方法。$(TOPDIR)/rules.mk一般在Makefile的開頭,$(INCLUDE_DIR)/kernel.mk文件對於軟件包為內核時不可缺少,$(INCLUDE_DIR)/package.mk一般在軟件包的基本信息完成後再引入。
2、编写软件包的基本信息,这些软件包的信息均以PKG_开头,其意思和作用如下:
PKG_NAME表示软件包名称,将在menuconfig和ipkg可以看到。
PKG_VERSION表示软件版本号。
PKG_RELEASE表示Makefile的版本号
PKG_SOURCE表示源代码的文件名。
PKG_SOURCE_URL表示源代码的下载网站位置。@SF表示在sourceforge网站,@GNU表示在GNU网站,還有@GNOME、@KERNEL。獲取方式可以為:git、svn、cvs、hg、bzr等。有關下載方法可參考$(INCLUDE_DIR)/download.mk和$(SCRIPT_DIR)/download.pl。
PKG_MD5SUM表示源代码文件的效验码。用于核对软件包是否正确下载。
PKG_CAT表示源代码文件的解压方法。包括zcat, bzcat, unzip等。
PKG_BUILD_DIR表示软件包编译目录。它的父目录为$(BUILD_DIR)。如果不指定,默认为$(BUILD_DIR)/$( PKG_NAME)$( PKG_VERSION)。
還有一些有關源代碼的定義。
PKG_SOURCE_SUBDIR
PKG_SOURCE_PROTO
PKG_SOURCE_MIRROR
PKG_MIRROR_MD5SUM
PKG_SOURCE_VERSION
3、编译包定义
用户程序和内核模块的定义不一样。用戶態軟件包使用Package,內核模塊使用KernelPackage。
3.1用户程序编译包定义
用户程序的编译包以Package/开头,然后接着软件名,在Package定义中的软件名可以与软件包名不一样,而且可以多个定义。下面使用$(PKG_NAME)只是做一个标示,并非真正用$(PKG_NAME)。
Package/$(PKG_NAME)
SECTION表示包的类型,预留。
CATEGORY表示分类,在menuconfig的菜单下将可以找到。
TITLE用于软件包的简短描述
DESCRIPTION用于软件包的详细描述,已放弃使用。如果使用DESCRIPTION將會提示“error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description”。
URL表示软件包的下载位置。
MAINTAINER表示维护者,选项。
DEPENDS表示与其他软件的依赖。即如编译或安装需要其他软件时需要说明。如果存在多個依賴,則每個依賴需用空格分開。依賴前使用+號表示默認顯示,即對象沒有選中時也會顯示,使用@則默認為不顯示,即當依賴對象選中後才顯示。
在用戶態的軟件包中沒有內核模塊的AUTOLOAD參數。如果軟件需要在boot時自動運行,則需要在/etc/init.d增加相應的腳本文件。腳本文件需要START參數,說明在boot時的優先級,如果在boot過程啟動後在關閉,則需要進一步設置STOP參數。如果STOP參數存在,其值必須大於START。腳本文件需要start()和stop()兩個函數,start()是執行程序,stop()是關閉程序。關閉程序一般需要執行killall命令。由/etc/rc.d/S10boot知道,裝載內核模塊的優先級為10,需要使用自己設計的內核模塊的程序其START的值必須大於10.
同樣由/etc/rc.d/S40network知道,使用網絡通信的程序其START的值必須大於40。
Package/$(PKG_NAME)/conffiles
本包安裝的配置文件,一行一個。如果文件結尾使用/,則表示為目錄。用於備份配置文件說明,在sysupgrade命令執行時將會用到。
Package/$(PKG_NAME)/description
软件包的详细描述,取代前面提到的DESCRIPTION详细描述。
Build/Prepare
编译准备方法,对于网上下载的软件包不需要再描述。对于非网上下载或自行开发的软件包必须说明编译准备方法。一般的准备方法为:
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
按OpenWrt的习惯,一般把自己设计的程序全部放在src目录下。
Build/Configure
在Automake中需要进行./configure,所以本配置方法主要针对需要配置的软件包而设计,一般自行开发的软件包可以不在这里说明。需要使用本定义的情况,可参考dropbear。
Build/Compile
编译方法,没有特别说明的可以不予以定义。如果不定义将使用默认的编译方法Build/Compile/Default,自行开发的软件包可以考虑使用下面的定义。
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I$(LINUX_DIR)/include"
Endef
Package/$(PKG_NAME)/install
软件包的安装方法,包括一系列拷贝编译好的文件到指定位置。調用時會帶一個參數,就是嵌入系統的鏡像文件系統目錄,因此$(1)表示嵌入系统的镜像目录。一般可以采用下面的方法:
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ $(PKG_NAME) $(1)/usr/bin/
endef
INSTALL_DIR、INSTALL_BIN在$(TOPDIR)/rules.mk文件定义,所以本Makefile必须引入$(TOPDIR)/rules.mk文件。
INSTALL_DIR :=install -d -m0755 意思創建所屬用戶可讀寫即執行,其他用戶可讀可執行的目錄。
INSTALL_BIN:=install -m0755意思編譯好的文件到鏡像文件目錄。
如果用戶態軟件在boot時要自動運行,則需要在安裝方法說明中增加自動運行的腳本文件安裝和配置文件安裝方法。
例如:
define Package/mountd/install
$(INSTALL_DIR) $(1)/sbin/ $(1)/etc/config/ $(1)/etc/init.d/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/mountd $(1)/sbin/
$(INSTALL_DATA) ./files/mountd.config $(1)/etc/config/mountd
$(INSTALL_BIN) ./files/mountd.init $(1)/etc/init.d/mountd
endef
安裝文件放在files子目錄下,不要與源代碼文件目錄src混在一起,以提高可讀性。
使用清晰的文件擴展名,更方便安裝識別文件。
Package/$(PKG_NAME)/preinst
软件包安装前处理方法,使用脚本语言,因此定义的第一行需要下面的格式
#!/bin/sh
調用時帶入的參數為嵌入式系統的鏡像目錄。
Package/$(PKG_NAME)/postinst
软件包安装后处理方法,使用脚本语言。
Package/$(PKG_NAME)/prerm
软件包删除前处理方法,使用脚本语言
Package/$(PKG_NAME)/postrm
软件包删除后处理方法,使用脚本语言
3.2内核模块包定义
Linux分为内核态和用户态。开发者开发的内核部分可以直接加入Linux的Kernel程序,也可以生成内核模块以便需要时装入内核。OpenWrt一般希望开发者生成内核模块,在Linux启动后自动装载或手工使用insmod命令装载。内核模块使用KernelPackage开头,其他与一般软件包基本相同。
在内核模块定义中增加SUBMENU表示子菜单位置,在$(INCLUDE)/kernel.mk对内核模块定义了CATEGORY为kernel modules,所以内核模块在menuconfig中的主菜单为kernel modules,然后有下一级子菜单$(SUBMENU)。在子菜单下可以看到以kmod-$( PKG_NAME)项目。
DEFAULT表示直接编入内核或产生内核模块,y表示直接编入内核,m表示产生内核模块。
AUTOLOAD表示自动装入内核,一般表示方法为:
AUTOLOAD:=$(call AutoLoad, $(PRIORITY),$(AUTOLOAD_MODS))
AutoLoad的第一个参数$(PRIORITY)为优先级,01为最优先,99为最后装载。有关自动装载可以在/etc/modules.d目录下看到,第二个参数$(AUTOLOAD_MODS)模块名,每个模块名以空格符分隔。即可同时装载多个内核模块。
在开发过程最好不要使用自动装载,經過嚴格調試後再使用,可以減輕調試的工作量。
4、使用定义
完成前面定义后,必须使用eval函数实现各种定义。其格式为:
对于一般软件包
$(eval $(call Package,$(PKG_NAME)))
或对于内核模块
$(eval $(call KernelPackage,$(PKG_NAME)))
如果一個軟件包有多個程序,例如:一個應用程序有自己的內核模塊,上面使用的PKG_NAME需要靈活變通。eval函數可能設計多個。也可以當成多個軟件包處理。
5. helloworld
前些日子又搞起了OpenWRT,之前一直只是使用shell或者Lua实现自己的想法,随着C渐渐使用的顺畅了,打算学习下OpenWRT上程序的编写以及交叉编译。
首先列下目录结构
-
lee@ubuntu:~/OpenWrt-SDK-ar71xx-for-linux-i486-gcc-4.6-linaro_uClibc-0.9.33.2/package$ tree
-
.
-
|-- hiOpenWRT
-
| |-- Makefile
-
| `-- src
-
| |-- hiOpenwrt.c
-
| `-- Makefile
-
|-- Makefile
-
`-- rules.mk
这是在OpenWRT-SDK/package下的文件结构,需要自己建的目录有hiOpenwrt/ 和hiOpenwrt/src/ ;需要自己建的文件有:
src下的hiOpenwrt.c
-
#include <stdlib.h>
-
#include <stdio.h>
-
int main(void){
-
printf("Hi,OpenWRT!\n");
-
return 0;
-
}
src下Makefile:src下Makefile:
-
# build hiOpenwrt executable when user executes "make"
-
hiOpenwrt: hiOpenwrt.o
-
$(CC) $(LDFLAGS) hiOpenwrt.o -o hiOpenwrt
-
-
hiOpenwrt.o: hiOpenwrt.c
-
$(CC) $(CFLAGS) -c hiOpenwrt.c
-
-
#remove object files and executable when user executes "make clean"
-
clean:
-
rm *.o hiOpenwrt
当然,在hiOpenwrt/Makefile也需要写,只不过这个要求比较多,格式如下:
-
#-----官方文档如下
-
This is the OpenWrt SDK. It contains a stripped-down version of the
buildroot. You can use it to test/develop packages without having to
compile your own toolchain or any of the libraries included with
OpenWrt.
-
To use it, just put your buildroot-compatible package directory in the subdir 'package/' and run 'make' from this directory.
-
-
-
#------ OPENWRT集成非官方包之Makefile规则
-
include $(TOPDIR)/rules.mk
-
-
-
PKG_NAME:=[软件包名字 和文件夹名称一样]
-
PKG_VERSION:=[软件包版本 自己写个]
-
PKG_RELEASE:=1
-
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
-
include $(INCLUDE_DIR)/package.mk
-
define Package/$(PKG_NAME)
-
SECTION:=utils
-
CATEGORY:=[软件包在menuconfig里的位置 比如Base system]
-
DEPENDS:=[依赖包 两个之间通过空格分隔 前面加+为默认显示 选中该软件包自动选中依赖包 不加+为默认不显示 选中依赖包才显示]
-
TITLE:=[标题]
-
PKGARCH:=[平台 比如ar71xx 全部写all]
-
MAINTAINER:=[作者]
-
endef
-
define Package/$(PKG_NAME)/description
-
[软件包简介]
-
endef
-
-
#非本目录下的源码文件, 拷贝到此相应目录下.
-
-
# 如../../xucommon/xucommon.c, 则将 xucommon.c 拷贝到此目录下的源码的 ../../
-
define Build/Prepare
-
mkdir -p $(PKG_BUILD_DIR)
-
$(CP) ./src/* $(PKG_BUILD_DIR)/
-
endef
-
-
define Build/Configure
-
endef
-
-
define Build/Compile
-
endef
-
-
define Package/$(PKG_NAME)/conffiles
-
[升级时保留文件/备份时备份文件 一个文件一行]
-
endef
-
-
define Package/$(PKG_NAME)/install
-
$(CP) ./files/* $(1)/
-
endef
-
-
define Package/$(PKG_NAME)/preinst
-
[安装前执行的脚本 记得加上#!/bin/sh 没有就空着]
-
#!/bin/sh
-
uci -q batch <<-EOF >/dev/null
-
delete ucitrack.@aria2[-1]
-
add ucitrack aria2
-
set ucitrack.@aria2[-1].init=aria2
-
commit ucitrack
-
EOF
-
exit 0
-
endef
-
-
define Package/$(PKG_NAME)/postinst
-
[安装后执行的脚本 记得加上#!/bin/sh 没有就空着]
-
#!/bin/sh
-
rm -f /tmp/luci-indexcache
-
exit 0
-
endef
-
-
Package/$(PKG_NAME)/prerm
-
[删除前执行的脚本 记得加上#!/bin/sh 没有就空着]
-
endef
-
Package/$(PKG_NAME)/postrm
-
[删除后执行的脚本 记得加上#!/bin/sh 没有就空着]
-
endef
-
$(eval $(call BuildPackage,$(PKG_NAME)))
自己的hi Openwrt的Makefile自己的hiOpenwrt的Makefile
-
##############################################
-
# OpenWrtMakefile for hiOpenwrt program
-
#
-
#
-
# Most ofthe variables used here are defined in
-
# theinclude directives below. We just need to
-
# specifya basic description of the package,
-
# whereto build our program, where to find
-
# thesource files, and where to install the
-
# compiled program on the router.
-
#
-
# Be verycareful of spacing in this file.
-
# Indentsshould be tabs, not spaces, and
-
# thereshould be no trailing whitespace in
-
# linesthat are not commented.
-
#
-
##############################################
-
include $(TOPDIR)/rules.mk
-
# Nameand release number of this package
-
PKG_NAME:=hiOpenWRT
-
PKG_RELEASE:=1
-
-
# Thisspecifies the directory where we're going to build the program.
-
# Theroot build directory, $(BUILD_DIR), is by default the build_mipsel
-
#directory in your OpenWrt SDK directory
-
PKG_BUILD_DIR:= $(BUILD_DIR)/$(PKG_NAME)
-
-
include $(INCLUDE_DIR)/package.mk
-
-
# Specifypackage information for this program.
-
# Thevariables defined here should be self explanatory.
-
# If youare running Kamikaze, delete the DESCRIPTION
-
#variable below and uncomment the Kamikaze define
-
# directivefor the description below
-
define Package/$(PKG_NAME)
-
SECTION:=utils
-
CATEGORY:=Utilities
-
TITLE:=hiOpenwrt-- prints a snarky message
-
endef
-
-
# Specifywhat needs to be done to prepare for building the package.
-
# In ourcase, we need to copy the source files to the build directory.
-
# This isNOT the default. The default uses thePKG_SOURCE_URL and the
-
#PKG_SOURCE which is not defined here to download the source from the web.
-
# Inorder to just build a simple program that we have just written, it is
-
# mucheasier to do it this way.
-
define Build/Prepare
-
mkdir -p $(PKG_BUILD_DIR)
-
$(CP) ./src/* $(PKG_BUILD_DIR)/
-
endef
-
-
-
# We donot need to define Build/Configure or Build/Compile directives
-
# Thedefaults are appropriate for compiling a simple program such as this one
-
-
-
# Specifywhere and how to install the program. Since we only have one file,
-
# thehelloworld executable, install it by copying it to the /bin directory on
-
# therouter. The $(1) variable represents the root directory on the router running
-
#OpenWrt. The $(INSTALL_DIR) variable contains a command to prepare the install
-
#directory if it does not already exist. Likewise $(INSTALL_BIN) contains the
-
# commandto copy the binary file from its current location (in our case the build
-
#directory) to the install directory.
-
define Package/$(PKG_NAME)/install
-
$(INSTALL_DIR) $(1)/bin
-
$(INSTALL_BIN) $(PKG_BUILD_DIR)/hiOpenwrt $(1)/bin/
-
endef
-
-
-
# Thisline executes the necessary commands to compile our program.
-
# Theabove define directives specify all the information needed, but this
-
# linecalls BuildPackage which in turn actually uses this information to
-
# build apackage.
-
$(eval $(call BuildPackage,$(PKG_NAME)))
生成ipk的命令:
-
make package/hiOpenWRT/compile V=s
加上V=s的目的是为了详细打印。
生成出来的ipk文件将在bin/ar71xx/packages/下,同时,也可以在build_dir/ build_dir/下找到可执行文件以及源码。
-
lee@ubuntu:~/OpenWrt-SDK-ar71xx-for-linux-i486-gcc-4.6-linaro_uClibc-0.9.33.2$ tree | head -n 20
-
.
-
|-- bin
-
| |-- ar71xx
-
| | `-- packages
-
| | `-- hiOpenWRT_1_ar71xx.ipk
-
| `-- packages
-
|-- build_dir
-
| `-- build_dir
-
| `-- hiOpenWRT
-
| |-- hiOpenwrt
-
| |-- hiOpenwrt.c
-
| |-- hiOpenwrt.o
-
| |-- ipkg-ar71xx
-
| | `-- hiOpenWRT
-
| | |-- bin
-
| | | `-- hiOpenwrt
-
| | `-- CONTROL
-
| | `-- control
-
| `-- Makefile
-
|-- Config.in
一点总结:
目录结构很重要,如果目录结构不正确,那么Openwrt是不可能正确编译出ipk的。
在学习编译过程中出现了很多处错误,最让我头疼的是Makefile的格式,这部分很容易错误而编译失败。
Makefile编写规则:
-
target ... : prerequisites ...
-
command
-
...
-
...
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签
(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的Shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisi
tes中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是 Makefile的规则。也就是Makefile中最核心的内容。