在软件开发过程中,尤其是在处理源码目录时,我们必须遵守一系列细致且至关重要的规则。虽然这些规则的学习可能显得有些繁琐,但它们的重要性不容忽视。
创建files目录的意义
在源码目录中建立一个名为“files”的文件夹至关重要,它相当于根目录的一个镜像。以开发物联网设备软件为例,在2023年,我们按照根目录的结构来存放文件,这样做有助于对即将集成到固件中的文件进行清晰的组织,便于管理。许多刚开始接触这一领域开发的人往往忽略了这一点,这可能会在后续的文件打包过程中造成混乱。这一点虽然容易被忽视,但实则非常有价值。此外,在每个新增的软件包下面创建相应的目录,这个目录里包含了软件包的各种信息和相关文件,有利于项目内部不同模块的管理和调用。
$()/rules.mk的位置与特性
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
通常,rules.mk文件位于开头部分。在开发基于Linux系统核心的监控软件时,开发者需要特别注意,当软件包是内核时,rules.mk文件是必不可少的。而且,rules.mk文件通常在软件包基本信息确定后才会被引入。这种顺序是通过大量实践得出的最佳方案,一旦打乱,可能会引起编译错误或软件运行时的问题。对于不熟悉这些规则的普通开发者来说,错误调整顺序可能会耗费大量时间去排查故障。
用户空间应用软件包特点
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR)
$(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I $(LINUX_DIR)/include"
endef
用户空间的应用程序软件包具有其独到之处。以开发一款办公软件为例,这类软件包中并不包含内核驱动模块的参数设置。这实际上是与内核相关的软件包在根本上的不同。当应用软件需要在开机过程中自动启动时,操作过程就会变得相对复杂。以我们常见的开机自启的杀毒软件为例,它就需要在/etc/init.d目录下添加相应的脚本文件。
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_DIR) $(PKG_BUILD_DIR)/ $(PKG_NAME) $(1)/usr/bin/
endef
脚本文件参数设置
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
脚本文件在软件启动时若需自动执行,需加入/etc/init.d目录,并配置START参数以标明其在启动过程中的优先级。这就像数据库启动程序,只有设定恰当的优先级,才能确保系统运行稳定。启动后若需关闭,还需配置STOP参数。据/etc/rc.d/目录所示,内核驱动模块的加载优先级为10,若程序采用自定义内核驱动模块,其START值需高于10。例如,开发打印驱动程序时,这一规则必须遵循。此外,根据/etc/rc.d/信息,使用网络通信的程序,其START值应超过40,比如浏览器程序等。
程序存放位置及可自动运行软件安装
通常情况下,开发者会把自编的程序统一存放在src目录中。若要让用户空间的应用软件在启动时自动执行,那么在安装指南中添加自动运行脚本的安装和配置文件的安装步骤尤为关键。这些安装文件应置于files子目录中,而非与源代码文件所在的src目录混淆。这样的布局有助于提升项目文件的易读性,并减少后续维护的难度。以2022年我们开发的一款图像编辑软件为例,采用这种方式后,后续的代码维护工作就变得轻松多了。
内核部分开发相关
在进行内核部分的开发过程中,开发者面临多种选择。他们可以直接将Linux程序纳入其中,亦或是创建内核模块,以便在需要时进行装载。一般情况下,我们更倾向于开发者创建内核模块,因为这样Linux启动后,模块可以自动加载,或者通过手动命令进行加载。内核模块采用特定的起始模式,其它的结构与一般的应用软件包大致相同。以2023年一个加密算法在内核中的应用开发为例,若一个软件包如同大型企业办公软件那样,包含多个程序,且拥有独立的内核驱动模块,那么在应用上述规则时,就需要根据实际情况灵活变通。
# build helloworld executable when user executes "make"
LDFLAGS += -L$(TOPDIR)/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib/mysql
LDFLAGS += -L$(TOPDIR)/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib/libevent
LDFLAGS += -L$(TOPDIR)/staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/lib/ -lstdc++ -Wno-explicit
LDFLAGS += -L$(TOPDIR)/build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/libjsoncpp/
LIBS += -lmysqlclient -lpthread -lrt -lcurl -ljsoncpp -levent
CXXFLAGS += -I$(TOPDIR)/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include/mysql
CXXFLAGS += -I$(TOPDIR)/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include/libevent
CXXFLAGS += -I$(TOPDIR)/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include
#CXXFLAGS += -I$(TOPDIR)/build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/json-c-0.12
CXXFLAGS += -I$(TOPDIR)/build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/libjsoncpp/include/
CXXFLAGS += -std=c++11
OBJ=cpp.o common.o Serial.o protocol.o CHttpClient.o
CSocket.o CUDPSocket.o DALReceive.o SocketSend.o DALSend.o Central.o CMsg.o
DALBase.o CustomMessage.o HCIModule.o HCIMatch.o
SQLBase.o HCIServiceObject.o SQLServiceObject.o HCIEvent.o SQLEvent.o
HCIGateway.o SQLGateway.o Gateway.o
HCIIfttt.o SQLIfttt.o HCIAction.o SQLAction.o HCIDebug.o ServiceObject.o CustomCfg.o
Load.o Event.o HandleThread.o HCIIftttLog.o SQLIftttLog.o Action.o
Ifttt.o IftttLog.o Timer.o JsonVector.o HttpServer.o WebServer.o TimerThread.o Buffer.o
cpp: $(OBJ)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(LIBS) $(OBJ) -o $@
%.o: %.cpp
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(LIBS) -c $< -w
# remove object files and executable when user executes "make clean"
clean:
rm *.o cpp
在软件开发过程中,编译应用程序和可执行文件是不可或缺的环节。首先,需要将应用程序的完整文件复制到系统源码的“/”目录中。接着,切换到系统源码的顶级目录,输入make命令,即可开始编译应用程序。至于编译可执行文件,操作类似,只是需要在make命令后加上“/cpp/”和“V=99”参数。每一个步骤都需准确无误。
大家不妨想想,在开发过程中,是否常常因为疏忽了这些小细节而招致了不少麻烦?觉得这篇文章对您有帮助的话,不妨点个赞、转发一下。同时,欢迎在评论区和大家分享您的开发经历,一起交流讨论。
##############################################
# OpenWrt Makefile for cpp program
#
#
# Most of the variables used here are defined in
# the include directives below. We just need to
# specify a basic description of the package,
# where to build our program, where to find
# the source files, and where to install the
# compiled program on the router.
#
# Be very careful of spacing in this file.
# Indents should be tabs, not spaces, and
# there should be no trailing whitespace in
# lines that are not commented.
#
##############################################
include $(TOPDIR)/rules.mk // 必须引入$(TOPDIR)/rules.mk文件
# Name and release number of this package
PKG_NAME:=cpp // 软件包名称
PKG_RELEASE:=1 // Makefile的版本号
# This specifies the directory where we're going to build the program.
# The root 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 // 引入$(INCLUDE_DIR)/package.mk文件
# Specify package information for this program.
# The variables defined here should be self explanatory.
# If you are running Kamikaze, delete the DESCRIPTION
# variable below and uncomment the Kamikaze define
# directive for the description below
define Package/cpp
SECTION:=utils // 包的类型
CATEGORY:=Utilities // 包的分类,在make menuconfig的菜单下将可以找到。
// 与其他软件的依赖
DEPENDS:=+libmysqlclient +libpthread +libreadline +libstdcpp +librt +libcurl +libjsoncpp +libevent2
TITLE:=alm-tn01 // 软件包的简短描述
endef
# Uncomment portion below for Kamikaze and delete DESCRIPTION variable above
define Package/cpp/description // 软件包的详细描述
If you can't figure out what this program does, you're probably
brain-dead and need immediate medical attention.
endef
# Specify what needs to be done to prepare for building the package.
# In our case, we need to copy the source files to the build directory.
# This is NOT the default. The default uses the PKG_SOURCE_URL and the
# PKG_SOURCE which is not defined here to download the source from the web.
# In order to just build a simple program that we have just written, it is
# much easier to do it this way.
define Build/Prepare // 自行开发软件包的编译准备方法
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/ * $(PKG_BUILD_DIR)/
endef
# We do not need to define Build/Configure or Build/Compile directives
# The defaults are appropriate for compiling a simple program such as this one
# Specify where and how to install the program. Since we only have one file,
# the cpp executable, install it by copying it to the /bin directory on
# the router. 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
# command to copy the binary file from its current location (in our case the build
# directory) to the install directory.
define Package/cpp/install // 软件包的安装方法
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/cpp $(1)/bin/
$(INSTALL_DIR) $(1)/etc/init.d // 自动运行的脚本文件安装和配置文件安装方法
$(INSTALL_BIN) ./files/cpp.init $(1)/etc/init.d/cpp
$(INSTALL_DIR) $(1)/etc/app
endef
# This line executes the necessary commands to compile our program.
# The above define directives specify all the information needed, but this
# line calls BuildPackage which in turn actually uses this information to
# build a package.
$(eval $(call BuildPackage,cpp)) // 使用定义