[OpenWrt Wiki][开发基础]新建一个简单程序

前言

本文参阅OpenWrt WiKi的https://openwrt.org/docs/guide-developer/helloworld/start而做的简单笔记,记录如何在OpenWrt中新建一个应用程序、使用补丁等基础开发知识。

一、设置构建系统

本文的实验环境是Ubuntu 18.04.6。

安装依赖包

~$ sudo apt update
~$ sudo apt install build-essential ccache ecj fastjar file g++ gawk \
gettext git java-propose-classpath libelf-dev libncurses5-dev \
libncursesw5-dev libssl-dev python python2.7-dev python3 unzip wget \
python3-distutils python3-setuptools python3-dev rsync subversion \
swig time xsltproc zlib1g-dev

获取源码

执行以下指令,将源码克隆到本地的source目录下:
~/study/openwrt$ git clone https://git.openwrt.org/openwrt/openwrt.git source

检出一个稳定的版本,并清除构建生成文件:

~/study/openwrt$ cd ./source
~/study/openwrt/source$ git checkout v17.01.2
~/study/openwrt/source$ make distclean

建议更新并安装“feed”包以避免构建时出现问题:

~/study/openwrt/source$ ./scripts/feeds update -a
~/study/openwrt/source$ ./scripts/feeds install -a

配置、构建交叉编译工具链

调用图形配置菜单:

~/study/openwrt/source$ make menuconfig

本文使用的配置: Target SystemSubtargetTarget Profile分别对应LantiqXRX200ZyXEL P2812HNU-F1
修改完保存退出。开始构建独立于目标(target-independent)的工具和交叉编译工具链:

~/study/openwrt/source$ make toolchain/install

注意:执行上面命令编译到tools/make-ext4fs时,可能会出现报错 error: conflicting types for ‘copy_file_range’ ,参考以下链接:
host-e2fsprogs 1.43.3 Building conflict with glibc · Issue #6 · enclustra-bsp/bsp-xilinx · GitHub: https://github.com/enclustra-bsp/bsp-xilinx/issues/6
把build_dir/host/e2fsprogs-1.43.3/debugfs/misc/create_inode.c文件中的copy_file_range改为copy_file_chunk,即可解决报错。

修改PATH环境变量

上一步生成的工具在 staging_dir/host/ 和 staging_dir/toolchain/ 目录下,为了方便使用 staging_dir/host/bin 下面的工具,需要添加 PATH 变量:

~$ export PATH=/home/harlen/study/openwrt/source/staging_dir/host/bin:$PATH

二、创建“Hello, world!”应用程序

创建源代码目录和文件

基于软件开发中的一项基本原则——关注点分离(the separation of concerns),所以,在OpenWrt之外的目录创建应用程序:

~$ mkdir -p ~/study/app/helloword
~$ cd ~/study/app/helloword
~/study/app/helloword$ touch helloworld.c

编辑源代码,最终内容如下:

~/study/app/helloword$ cat helloworld.c
#include <stdio.h>

int main(void)
{
	printf("\nHello, world!\n\n");
	return 0;
}

编译、链接、测试应用程序

先确保应用程序在本机可以正常使用:

~/study/app/helloword$ gcc -c -o helloworld.o helloworld.c -Wall
~/study/app/helloword$ gcc -o helloworld helloworld.o

编译、链接成功后,执行:

~/study/app/helloword$ ./helloworld

三、从应用程序创建包

为包创建包源

OpenWrt 构建系统主要围绕包的概念展开。无论什么软件,几乎都有一个软件包,包括目标的工具、交叉编译工具链、目标固件的Linux内核、内核的模块、安装到目标根文件系统的各种应用程序,等等。
除了集成在构建系统的包,其它包的主要交付系统是包源(package feed)。包源是包的一个存储库。存储库可以是本地的目录,也可以位于网络共享上,也可以位于版本控制系统(如 GitHub)上。通过创建和维护包源,可以将与包相关的文件与示例应用程序的源代码分开,以达到关注点分离的目的。
本文中,我们将一个新的包存储库创建到本地目录中。此存储库的名称是“mypackages”,它包含一个名为“examples”的类别。在此类别中,只有一个条目,即我们的“helloworld”应用程序:

~$ mkdir -p ./study/mypackages/examples/helloworld

创建包的清单文件

OpenWrt 构建系统中的每个包都由包清单(package manifest)文件描述。 清单文件负责描述包及其作用,并且必须至少提供有关从何处获取源代码、如何构建源代码以及最终可安装包中应包含哪些文件的说明。 包清单可能还包含可选配置脚本的选项,指定包之间的依赖关系等。
为了让helloword源代码成为一个包,并成为包存储库的一部分,需要创建一个包清单文件(Makefile)把它联系起来:

~$ cd ./study/mypackages/examples/helloworld
~/study/mypackages/examples/helloworld$ touch Makefile

具体内容如下:

~/study/mypackages/examples/helloworld$ cat Makefile
include $(TOPDIR)/rules.mk

# Name, version and release number
# The name and version of your package are used to define the variable to point to the build directory of your package: $(PKG_BUILD_DIR)
PKG_NAME:=helloworld
PKG_VERSION:=1.0
PKG_RELEASE:=1

# Source settings (i.e. where to find the source codes)
# This is a custom variable, used below
SOURCE_DIR:=/home/harlen/study/app/helloword

include $(INCLUDE_DIR)/package.mk

# Package definition; instructs on how and where our package will appear in the overall configuration menu ('make menuconfig')
define Package/helloworld
  SECTION:=examples
  CATEGORY:=Examples
  TITLE:=Hello, World!
endef

# Package description; a more verbose description on what our package does
define Package/helloworld/description
  A simple "Hello, world!" -application.
endef

# Package preparation instructions; create the build directory and copy the source code. 
# The last command is necessary to ensure our preparation instructions remain compatible with the patching system.
define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	cp $(SOURCE_DIR)/* $(PKG_BUILD_DIR)
	$(Build/Patch)
endef

# Package build instructions; invoke the target-specific compiler to first compile the source file, and then to link the file into the final executable
define Build/Compile
	$(TARGET_CC) $(TARGET_CFLAGS) -o $(PKG_BUILD_DIR)/helloworld.o -c $(PKG_BUILD_DIR)/helloworld.c
	$(TARGET_CC) $(TARGET_LDFLAGS) -o $(PKG_BUILD_DIR)/$1 $(PKG_BUILD_DIR)/helloworld.o
endef

# Package install instructions; create a directory inside the package to hold our executable, and then copy the executable we built previously into the folder
define Package/helloworld/install
	$(INSTALL_DIR) $(1)/usr/bin
	$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin
endef

# This command is always the last, it uses the definitions and variables we give above in order to get the job done
$(eval $(call BuildPackage,helloworld))

定义包及其构建指令的方法有很多,这是其中一个例子。
注意:define Build/Prepare、define Build/Compile、define Package/helloworld/install这三个部分的命令需要使用Tab缩进,而不是空格。

四、将包源包含到OpenWrt构建系统中

新增包源配置文件

OpenWrt 构建系统使用一个名为 feeds.conf 的配置文件,该文件将在固件配置阶段指定可用的包源。
OpenWrt源码目录下默认不存在这个文件,所以需要创建:

~$ cd ~/study/openwrt/source
~/study/openwrt/source$ touch feeds.conf

指定本地包源,具体内容如下:

~/study/openwrt/source$ cat feeds.conf
src-link mypackages /home/harlen/study/mypackages

更新并安装源

指定了新的包源后,可以执行以下指令,使构建系统根据此包源的可用信息来更新包的索引,并将包加入到配置菜单中:

~/study/openwrt/source$ ./scripts/feeds update mypackages
~/study/openwrt/source$ ./scripts/feeds install -a -p mypackages

最后一步执行成功后,会有以下打印输出:
Installing package ‘helloworld’ from mypackages
注意,以上指令执行一次即可。后续如果有修改包的清单文件,包源系统都会自动检测到,并会在完成其他命令(例如“make menuconfig”)或构建包之前执行更新。

五、构建、部署、测试应用程序

构建包

至此,OpenWrt构建系统已经可以把helloworld包集成到我们的固件里。
1、执行以下命令打开配置菜单,选择“Examples”子菜单,在此菜单下有个“helloworld”条目,单击“Y”键将此包包含到固件配置中,保存并退出菜单。

~/study/openwrt/source$ make menuconfig

2、然后执行以下命令生成包:
~/study/openwrt/source$ make package/helloworld/compile
如果顺利,会在bin/packages//mypackages目录下看到一个名为helloworld_1.0-1_.ipk的包。

部署、测试包

生成包之后,放到目标路由器去验证。作者建议使用SCP客户端,如WinSCP,把文件从主机发送到目标路由器。
这里假设已经把软件包保存到路由器上的/tmp目录下,接着用opkg工具安装:

root@OpenWrt:/# opkg install /tmp/helloworld_1.0-1_<arch>.ipk
Installing helloworld (1.0-1) to root...
Configuring helloworld.

之后,就可以执行该应用程序了:

root@OpenWrt:/# helloworld

Hello, world!

移除包

已安装的包,也可以通过opkg来移除:

root@OpenWrt:/# opkg remove helloworld
Removing package helloworld from root...

不再需要的包,可以删除它的ipk:

root@OpenWrt:/# rm /tmp/helloworld_1.0-1_<arch>.ipk

六、在应用程序中使用GNU make

为什么使用GUN make

我们的测试应用程序很简单,只有一个源文件。但是在包清单中编译和链接指令需要指定很多选项,包括编译和链接标志、源文件和目标文件名,甚至最终的可执行文件名:

# Package build instructions; invoke the target-specific compiler to first compile the source file, and then to link the file into the final executable
define Build/Compile
        $(TARGET_CC) $(TARGET_CFLAGS) -o $(PKG_BUILD_DIR)/helloworld.o -c $(PKG_BUILD_DIR)/helloworld.c
        $(TARGET_CC) $(TARGET_LDFLAGS) -o $(PKG_BUILD_DIR)/$1 $(PKG_BUILD_DIR)/helloworld.o
endef

当执行包清单文件中的指令时,构建系统的工作目录是包清单文件本身所在的根目录。 因此,如果不使用$(PKG_BUILD_DIR) 等文件夹变量,则可能找不到源文件,或者构建工件可能会放置在不正确的目录中。
使用 GNU make 是解决其中一些问题的一种方法。

创建Makefile

在应用程序的源目录下创建一个Makefile,并填充以下内容:

~$ cd ~/study/app/helloword
~/study/app/helloword$ vi Makefile
# Global target; when 'make' is run without arguments, this is what it should do
all: helloworld

# These variables hold the name of the compilation tool, the compilation flags and the link flags
# We make use of these variables in the package manifest
CC = gcc
CFLAGS = -Wall
LDFLAGS =

# This variable identifies all header files in the directory; we use it to create a dependency chain between the object files and the source files
# This approach will re-build your application whenever any header file changes. In a more complex application, such behavior is often undesirable
DEPS = $(wildcard *.h)

# This variable holds all source files to consider for the build; we use a wildcard to pick all files
SRC = $(wildcard *.c)

# This variable holds all object file names, constructed from the source file names using pattern substitution
OBJ = $(patsubst %.c, %.o, $(SRC))

# This rule builds individual object files, and depends on the corresponding C source files and the header files
%.o: %.c $(DEPS)
	$(CC) -c -o $@ $< $(CFLAGS)

# To build 'helloworld', we depend on the object files, and link them all into a single executable using the compilation tool
# We use automatic variables to specify the final executable name 'helloworld', using '$@' and the '$^' will hold the names of all the
# dependencies of this rule
helloworld: $(OBJ)
	$(CC) -o $@ $^ $(LDFLAGS)

# To clean build artifacts, we specify a 'clean' rule, and use PHONY to indicate that this rule never matches with a potential file in the directory
.PHONY: clean

clean:
	rm -f helloworld *.o 

注意,Makefile每行命令前面需要添加一个Tab键,而不是空格。

使用本机工具测试makefile

在本机编译helloworld源码,只需要执行:

~/study/app/helloword$ make

如果输出提示 make: Nothing to be done for all,则表示可执行文件已经是最新的。为了模拟代码中的更改,用touch更新源文件,然后再次尝试 make 命令:

~/study/app/helloword$ touch helloworld.c
~/study/app/helloword$ make

修改包清单,并测试构建

把上面的makefile跟OpenWrt构建系统关联起来,需要修改包清单构建部分的内容,如下:

~/study/mypackages/examples/helloworld$ cat Makefile
...
# Package build instructions; invoke the GNU make tool to build our package
define Build/Compile
	$(MAKE) -C $(PKG_BUILD_DIR) \
		CC="$(TARGET_CC)" \
		CFLAGS="$(TARGET_CFLAGS)" \
		LDFLAGS="$(TARGET_LDFLAGS)"
endef
...

修改包清单后,从 OpenWrt 构建系统目录下再次测试包构建过程:

~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,compile}

上面命令会先后执行包清理和编译。
如果执行时报错,可能要手动更新和安装包源:

~/study/openwrt/source$ ./scripts/feeds update mypackages
~/study/openwrt/source$ ./scripts/feeds install -a -p mypackages

如果还报错,请确保源目录(/home/harlen/study/app/helloword)除了.c 和 Makefile,其它文件需清除。

七、修补应用程序:添加新文件

关于补丁

在应用程序的生命周期中,从初始设计到应用程序退役,通常需要对原始源代码或相关文件进行更改或修复才能正常运行。 在移植软件以在不同的计算机体系结构上运行时,更改应用程序源代码尤为常见。 在 OpenWrt 构建系统中,这种变更管理是通过一个名为 quilt 的工具来完成的。
下面将介绍有关如何创建 .quiltrc 文件的重要信息,已确保创建的补丁符合 OpenWrt 构建系统的既定标准。
在第一章中,OpenWrt 构建系统已将 quilt 工具安装到目标独立工具目录下(/home/harlen/study/openwrt/source/staging_dir/host/bin)。 可通过以下指令验证:

~$ quilt --version

准备源代码

在 OpenWrt 构建系统中创建补丁非常简单,但在我们开始应用补丁之前,需要使用一个特殊选项来准备源代码:

~/study/openwrt/source$ make package/helloworld/{clean,prepare} QUILT=1

注意,当使用 QUILT=1 参数调用“make”时,源代码并非用于构建最终包。 该参数会在构建目录中创建额外的目录和文件。
最后的准备步骤是切换到源代码所在的构建目录,并确保所有已有的补丁被应用:

~/study/openwrt/source$ cd build_dir/target-.../helloworld-1.0/
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt push -a

此时 quilt push 命令并没有执行任何操作,提示“No series file found”,因为我们的应用程序尚未包含任何补丁。 然而,如果是多人合作开发的项目,这个步骤是必要的,因为其它开发者可能新增了别的补丁。

创建第一个补丁文件

首先为 Quilt 创建一个补丁上下文(patch context):

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt new 100-add_module_files.patch

补丁文件的名称来自 OpenWrt 构建系统的约定。 这些名称通常以一个自由序号开头,然后是对其用途的简短描述。 序数在某些情况下具有特定含义,但通常可以简单地使用从“000”开始的编号。本文作者选择数字“100”表示此补丁为现有源代码库添加了新功能,尚未集成到原始源代码(上游)中。
该命令的输出显示此补丁文件已创建并且现在位于 Quilt 补丁堆栈(patch stack)的顶部。
在这个补丁中,我们将为helloworld程序新增两个不同的文件,一个头文件和一个C源文件。 因此需要使用“quilt”工具开始在当前补丁上下文中跟踪这些文件:

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt add functions.c
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt add functions.h

下面开始编辑文件:

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ touch functions.c
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt edit functions.c
int add(int a, int b)
{
    return a + b;
}

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ touch functions.h
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt edit functions.h
int add(int, int);

至此,我们可以通过quilt diff来查看修改记录。
修改完成后,通过quilt refresh刷新到补丁文件中。

将第一个补丁文件包含到包中

在 OpenWrt 构建系统中,补丁在源代码目录中创建和修改,然后迁移到它们所属的包中。 为了让我们将刚刚创建的补丁数据迁移到正确的包中,执行以下命令:

~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/update

可以浏览包源目录和源代码目录,来查看变化:

~$ ls -la /home/harlen/study/mypackages/examples/helloworld
~$ ls -la /home/harlen/study/app/helloword

正如我们所看到的,OpenWrt 构建系统将我们新创建的补丁文件迁移到包清单所在的目录中,而 原始源代码目录没有变化。
以下指令用于确认构建过程应用了补丁:

~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,prepare}
~/study/openwrt/source$ ls -la build_dir/target-<arch>_<subarch>_<clib>_<clibversion>/

最终看到新文件存在于构建目录中。

八、修补应用程序:已存在的文件

创建第二个补丁文件

为了调用上一章新增的函数,我们创建第二个补丁来修改 helloworld.c 文件。 首先,准备打补丁的源代码,并创建一个新的补丁上下文:

~$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/{clean,prepare} QUILT=1
~/study/openwrt/source$ cd build_dir/target-.../helloworld-1.0/
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt push -a
~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt new 101-use_module.patch

我们的任务是修改 helloworld.c 文件,因此执行以下命令将文件添加到补丁上下文中,并打开它进行编辑:

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ quilt edit helloworld.c
#include <stdio.h>
#include "functions.h"
 
int main(void)
{
    int result = add(2, 3);
 
    printf("\nHello, world!\nThe sum is '%d'", result);
    return 0;     
}

注意,由于文件已经存在,因此无需使用 quilt add 将文件添加到补丁上下文中。
保存更改,使用 quilt diff 查看改动,并使用 quilt refresh 将它们移动到补丁上下文。
最后回到 OpenWrt 构建系统的根目录并更新我们的新补丁到helloworld包:

~/study/openwrt/source/build_dir/target-.../helloworld-1.0/$ cd ~/study/openwrt/source
~/study/openwrt/source$ make package/helloworld/update

附录:参考文档

[OpenWrt Wiki] “Hello, world!” for OpenWrt: https://openwrt.org/docs/guide-developer/helloworld/start

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值