背景
????????之前在做路由器工作時,搞過一段時間openwrt,最近看到之前寫的筆記。整理一下,希望能幫助一些朋友入坑。
熟悉openwrt
????????openwrt之前并沒有接觸過,其目錄結構和linux也有所不同。先大致了解一下openwrt文件系統中各個目錄的作用;
原始目錄
????????存放了一些腳本,使用了bash,Python,perf等多種腳本語言,編譯過程中,用于第三方軟件包管理的feeds文件也是在這個目錄中當中,在編譯過程中,使用的腳本也統一放在這個目錄中。
????????編譯是,主機需要使用一些工具軟件,tools里面包含了獲取和編譯這些工具的命令,軟件包里面有Makefile文件,有的還包含了patch,每個Makefile當中都有一句$(eval $(call HostBuild)),這表明編譯這個工具是為了在主機上使用的
????????存放著整個系統的配置文件,其中包括Config-build.in? Config-devel.in? Config-images.in? Config-kernel.in
????????包含了整個宿主機的文件源碼的介紹, 里面還有Makefile為目標系統生成docs.使用make -C docs/可以為目標系統生成文檔
????????包含了 kernel 頭文件,C庫,binutils, debugger等。
????????openwrt的源碼可以編譯出各個平臺適用的二進制文件,個平臺在這個目錄里面定義了firmware和Kernel的編譯過程。
????????存放了openwrt系統中適用的軟件包,包含針對各個軟件包的Makefile。openwrt定義了一套Makefile模板.各軟件參照這個模板定義了自己的信息,如軟件包的版本、下載地址、編譯方式、安裝地址等。在二次開發過程中,這個文件夾我們會經常打交道.
事實上,通過
./scripts/feed update -a和
./scripts/feed install -a的軟件包也會存放在這個目錄之中.
????????openwrt的Makefile都存放在這里。文件名為 *.mk 。這里的文件上是在Makefile里被include的,類似于庫文件.這些文件定義了編譯過程.
????????主要目錄就是前面提及的8個,剩下的是單個文件。
Makefile在頂層目錄執行make命令的入口文件
rules.mk定義了Makefile中使用的一些通用變量和函數
Config.in在include/toplevel.mk中我們可以看到,這是和make menuconfig相關聯的文件.
feeds.conf.default是下載第三方一些軟件包時所使用的地址
LICENSE & README即軟件許可證和軟件基本說明.其中README描述了編譯軟件的基本過程和依賴文件.
????????以上大致就是openwrt的原始目錄,其中需要我們關注的就是package目錄,我們添加應用時,就需要在里面修改。
生成目錄
????????openwrt的附加軟件包管理器的擴展包索引目錄.有點繞,簡單來說就是下載管理軟件包的.默認的feeds下載有packages、management、luci、routing、telephony。如要下載其他的軟件包,需打開源碼根目錄下面的feeds.conf.default文件,去掉相應軟件包前面的#號,然后更新源:
./scripts/feeds update -a
安裝下載好的包:
./scripts/feeds install -a
????????在前面的原始目錄中,我們提到了host工具,toolchain工具還有目標文件.openwrt將在這個目錄中展開各個軟件包,進行編譯.所以這個文件夾中包含3個子文件夾:
????????在該文件夾中編譯主機使用的工具軟件
????????在該文件夾中編譯交叉工具鏈
????????在此編譯目標平臺的目標文件,包括各個軟件包和內核文件.
????????保存編譯完成后的二進制文件,包括:完整的bin文件,所有的ipk文件.
????????在編譯過程中使用的很多軟件,剛開始下載源碼并沒有包含,而是在編譯過程中從其他服務器下載的,這里是統一的保存目錄
????????用于保存在build_dir目錄中編譯完成的軟件.所以這里也和build_dir有同樣的子目錄結構。比如,在target-XXX文件夾中保存了目標平臺編譯好的頭文件,庫文件.在我們開發自己的ipk文件時,編譯過程中,預處理頭文件,鏈接動態庫,靜態庫都是到這個子文件夾中。
????????從名字來看,是臨時文件夾.在編譯過程中,有大量中間臨時文件需要保存,都是在這里.
????????這個文件夾,有時可以看到,有時沒有.這是因為這個文件夾保存的是,編譯過程中出錯的信息,只有當編譯出錯了才會出現.我們可以從這里獲取信息,從而分析我們的軟件編譯為什么沒有完成.
OpenWrt添加模塊(package)
????????OpenWrt是一個比較完善的嵌入式Linux開發平臺,在無線路由器應用上已有100多個軟件包。人們可以在其基礎上增加軟件包,以擴大其應用范圍。
????????OpenWrt在增加軟件方面極其方便,按照OpenWrt的約定就可以很簡單完成。加入的軟件包可以是網上下載的開源軟件或自行開發的軟件。加入軟件包需要在package目錄下創建一個目錄。然后創建一個Makefile與OpenWrt建立聯系,Makefile需要遵循OpenWrt的約定。另外可以創建一個patchs目錄保存patch文件,對下載的源代碼進行修改。
- 創建Makefile
??????? 由于本文所建立的模塊是基于luci的,所以在OpenWrt的“package/feeds/luci”目錄下建立“szloogson”目錄,并創建Makefile文件。該文件的如下
# # Copyright (C) 2010-2014 Davied Huang Wich <apple_guet@126.com> # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. # include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-szloogson PKG_VERSION=1.0 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) include $(INCLUDE_DIR)/package.mk define Package/luci-app-szloogson ??? SECTION:=luci ??? CATEGORY:=LuCI ??? SUBMENU:=3. Applications ??? TITLE:=shenzhou loogson for LuCI ??? PKGARCH:=all endef define Package/luci-app-njitclient/description ??? This package contains LuCI configuration pages for shenzhou loogson. endef define Build/Prepare ??? mkdir -p $(PKG_BUILD_DIR) ??? $(CP) ./src/* $(PKG_BUILD_DIR)/ endef define Build/Configure endef define Build/Compile ??? $(MAKE) -C $(PKG_BUILD_DIR) \ ??????? CC="$(TARGET_CC)" \ ??????? CROSS_COMPILE="$(TARGET_CROSS)" \ ??????? ARCH="$(ARCH)" endef define Package/luci-app-szloogson/install ??? #install shell ??? $(INSTALL_DIR) $(1)/etc/init.d ??? $(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson ??? #install config ??? $(INSTALL_DIR) $(1)/etc/config ??? $(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson ??? #install execute bin ??? $(INSTALL_DIR) $(1)/usr/sbin ??? $(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/gsc3280_led??? ??? #install luci ??? mkdir -p $(1)/usr/lib/lua/luci/controller/admin ??? $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller ??? $(INSTALL_DATA) ./src/luci/controller/admin/loogson.lua $(1)/usr/lib/lua/luci/controller/admin/loogson.lua ??? ??? mkdir -p $(1)/usr/lib/lua/luci/model/cbi/admin_loogson ??? $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi ??? $(INSTALL_DATA) ./src/luci/model/cbi/admin_loogson/* $(1)/usr/lib/lua/luci/model/cbi/admin_loogson/ endef $(eval $(call BuildPackage,luci-app-szloogson) |
說明:
第8行“$(TOPDIR)/rules.mk”一般在Makefile的開頭,定義一些包的基本信息。
軟件包的信息均以“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 2.2.2、第17行“include $(INCLUDE_DIR)/package.mk” “include $(INCLUDE_DIR)/package.mk”一般在軟件包的基本信息完成后再引入,他定義了用戶態軟件包的規則。 編譯包分為用戶態和內核模塊,用戶態軟件包使用Package,內核模塊使用KernelPackage。“$(INCLUDE_DIR)/kernel.mk”文件對于軟件包為內核時不可缺少,“$(INCLUDE_DIR)/package.mk”應用在用戶態。接下來講述用戶態軟件包。用戶程序的編譯包以“Package/”開頭,然后接著軟件名,在Package定義中的軟件名可以與軟件包名不一樣,而且可以多個定義。 |
第19行”define Package/luci-app-szloogson,包的名稱為”luci-app-szloogson“。
? ? ??接下來定義的包括: ??????? SECTION:包的類型,預留。 ??????? CATEGORY:分類,在menuconfig的菜單下將可以找到。 ???? SUBMENU:包在make menuconfig的位置,此處即在”LuCi/3. Applications“下。 ??????? TITLE:用于軟件包的簡短描述,將顯示在”make menuconfig“中。 ??????? DESCRIPTION:用于軟件包的詳細描述,已放棄使用。如果使用DESCRIPTION將會提示“error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description”。 ??????? URL:軟件包的下載位置。 ??????? MAINTAINER:維護者選項。 ??????? DEPENDS:與其他軟件的依賴。即如編譯或安裝需要其他軟件時需要說明。如果存在多個依賴,則每個依賴需用空格分開。依賴前使用+號表示默認顯示,即對象沒有選中時也會顯示,使用@則默認為不顯示,即當依賴對象選中后才顯示。 |
第27行”define Package/luci-app-szloogson/description“
? ? ? ??軟件包的詳細描述,取代前面提到的DESCRIPTION詳細描述。此處定義的信息將顯示在”make menuconfig“中。 |
第31行”define Build/Prepare“
編譯準備方法,對于網上下載的軟件包不需要再描述。對于非網上下載或自行開發的軟件包必須說明編譯準備方法。本文所用的準備方法就是首先創建軟件包目錄,然后將源碼拷貝到剛剛創建的目錄中。按OpenWrt的習慣,一般把自己設計的程序全部放在src目錄下。 |
第36行"define Build/Configure”
????????Build/Configure:在Automake中需要進行“./configure”,所以本配置方法主要針對需要配置的軟件包而設計,一般自行開發的軟件包可以不在這里說明。本文設計的package由自己寫makefile,所以此處沒有定義。 |
第39行”define Build/Compile“
? ? ? ??編譯方法,沒有特別說明的可以不予以定義。如果不定義將使用默認的編譯方法Build/Compile/Default。 ??????? 自行開發的軟件包可以考慮使用下面的定義: 點擊(此處)折疊或打開
define Build/Compile ?? $(MAKE) -C $(PKG_BUILD_DIR) \ ???? $(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I$(LINUX_DIR)/include" endef ??????? 本文此處指定了交叉編譯器和體系結構。 |
第46行”define Package/luci-app-szloogson/install“
軟件包的安裝方法,包括一系列拷貝編譯好的文件到指定位置。調用時會帶一個參數,就是嵌入系統的鏡像文件系統目錄,因此$(1)表示嵌入系統的鏡像目錄。一般可以采用下面的方法: 點擊(此處)折疊或打開
define Package/luci-app-szloogson/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/ endef INSTALL_DIR、INSTALL_BIN在”$(TOPDIR)/rules.mk“文件中定義,所以本Makefile必須引入$(TOPDIR)/rules.mk文件。 INSTALL_DIR :=install -d -m0755:創建所屬用戶可讀寫、執行,其他用戶可讀可執行的目錄。 INSTALL_BIN:=install -m0755:編譯好的文件到鏡像文件目錄。 安裝文件放在files子目錄下,不要與源代碼文件目錄“src”混在一起,以提高可讀性。 如果用戶態軟件在boot時要自動運行,則需要在安裝方法說明中增加自動運行的腳本文件安裝和配置文件安裝方法。 例如: 點擊(此處)折疊或打開
define Package/luci-app-szloogson/install #install shell $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson #install config $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson endef 使用清晰的文件擴展名,更方便安裝識別文件。 Package/$(PKG_NAME)/preinst 軟件包安裝前處理方法,使用腳本語言,因此定義的第一行需要下面的格式 #!/bin/sh:調用時帶入的參數為嵌入式系統的鏡像目錄。 Package/$(PKG_NAME)/postinst:軟件包安裝后處理方法,使用腳本語言。 Package/$(PKG_NAME)/prerm:軟件包刪除前處理方法,使用腳本語言 Package/$(PKG_NAME)/postrm:軟件包刪除后處理方法,使用腳本語言 程序接下來安裝luci文件。 |
第66行”$(eval $(call BuildPackage,luci-app-szloogson))“
完成前面定義后,必須使用eval函數實現各種定義。其格式為: 對于一般軟件包:$(eval $(call Package,$(PKG_NAME))) 或對于內核模塊:$(eval $(call KernelPackage,$(PKG_NAME))) 如果一個軟件包有多個程序,例如:一個應用程序有自己的內核模塊,上面使用的“PKG_NAME”需要靈活變通。eval函數可能設計多個。也可以當成多個軟件包處理。 |
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)模塊名,每個模塊名以空格符分隔。即可同時裝載多個內核模塊。
???? 在開發過程最好不要使用自動裝載,需經過嚴格調試后再使用,可以減輕調試的工作量。
???? 用戶態的軟件包中沒有內核模塊的“AUTOLOAD”參數。如果軟件需要在boot時自動運行,則需要在“/etc/init.d”增加相應的腳本文件。 腳本文件需要START參數,說明在boot時的優先級,如果在boot啟動后再啟動,則需要STOP參數。如果STOP參數存在,其值必須大于START。由“/etc/rc.d/S10boot”知道,裝載內核模塊的優先級為10,需要使用自己設計的內核模塊的程序其START的值必須大于10。同樣由“/etc/rc.d/S40network”知道,使用網絡通信的程序其START的值必須大于40。
3. 腳本文件
???? 腳本文件需要start()和stop()兩個函數,start()是執行程序,stop()是關閉程序。關閉程序一般需要執行killall命令。
???? 在(一)中我們討論了點擊“應用”后執行的腳本文件在“/etc/init.d/loogson”目錄下,程序如下:
點擊(此處)折疊或打開
#!/bin/sh /etc/rc.common # (C) 2014 openwrt.org # add by Davied Huang <apple_guet@126.com> START=50 LED_BIN="/usr/sbin/gsc3280_led" control_board() { ??? local ledstatus, lednum; ??? ??? config_get_bool ledstatus $1 ledstatus ??? ??? config_get lednum $1 lednum ??? ??? echo "${lednum} ${ledstatus}" ??? ${LED_BIN} ${lednum} ${ledstatus} } start() { ??? config_load loogson ??? config_foreach control_board controlboard } stop() { ??? config_load loogson ??? #config_foreach stop_instance controlboard } |
說明:
- 在“start”函數中,首先使用“config_load”函數加載“/etc/config/”目錄下的loogson配置文件。
- “config_foreach”遍歷"/etc/init.d/loogson"配置文件中的Section,并且執行"control_board"函數。
- 在"control_board"函數中,使用“config_get_bool”獲得操作LED的開關狀態,config_get獲得操作第幾個LED。
- "config_load"、“config_foreach”、“config_get”和“config_get_bool”等函數由其他腳本提供,可以直接使用。
- 最后執行可執行文件,后面加上可執行文件需要的參數。該可執行文件的源碼為:
#include<stdio.h> #include<unistd.h> #include<fcntl.h> #include<asm/ioctl.h> int main(int argc,char *argv[]) { ??? int fd; ??? if (argc != 3) { ??????? printf("wrong cmd!\n"); ??? } ??? fd = open("/dev/led", O_RDWR); ??? if(fd == -1){ ??????? printf("open led failed!\n"); ??? } ??? ioctl(fd, argv[1], argv[2]);??? ??????? ??? close(fd); ??? return 0; } |