轉載自:http://www.liwangmeng.com/openwrt%E5%9F%BA%E6%9C%AC%E7%9F%A5%E8%AF%86%E5%BD%92%E7%BA%B3/
目錄:
6.2.進入example目錄,創建Makefile文件和代碼路徑… 20
6.5.在OpenWrt系統裏面就可以用opkg下載使用了。… 22
處理vmlinux: Image/BuildKernel 28
7.5.製作squashfs,生成.bin: $(call Image/mkfs/squashfs). 29
7.6.Openwrt_SDK中Makefile相關總結… 31
include $(TOPDIR)/rules.mk. 36
include $(INCLUDE_DIR)/package.mk. 38
1.OpenWRT虛擬系統準備
環境:
編譯服務器:Ubuntu-14.04-desktop
虛擬機軟件:VMWare-10.0
編譯環境準備:apt-get install subversion build-essential libncurses5-dev zlib1g-dev gawk git ccache gettext libssl-dev xsltproc flex gcc-multilib
1.1.編譯openwrt虛擬機系統
1.1.1 準備源碼
#cd /home/nickli/
#mkdir openwrt_SDK
#cd openwrt_SDK
#svn checkout
svn://svn.openwrt.org/openwrt/branches/barrier_breaker
1.1.2 準備源碼包
#cd barrier_breaker
#mkdir dl
#cp -r /home/public/openwrt_dl/* ./dl/
之前已經將幾乎所有的編譯時需要下載的源碼包下載到了/home/public/openwrt_dl 目錄下,所以爲減少編譯時間直接將該目錄鏈接到新的SDK中。
1.1.3 編譯配置
#make defconfig
#make prereq
#make menuconfig //配置系統編譯選單時需注意以下幾點:
- 系統目標平臺選擇爲x86
- 目標文件爲虛擬機文件***.vmdk
- 選擇c庫(uClibc/eglibc/glibc……)
- 配置系統所需的基本配置比如adduser/addgroup/su等命令(basesystem/busybox)
退出配置單並保存。
1.1.4 編譯
#make V=99
完成以後鏡像文件“openwrt-x86-generic-combined-squashfs.vmdk”存在於bin/x86/目錄下,將該文件拷貝到windows下如F:/template/下。
1.2.安裝虛擬機
1.2.1.新建虛擬機
下一步
選擇稍後安裝操作系統
客戶及操作系統選擇Linux,版本選擇“其他Linux 2.6.x內核”
爲虛擬機命名,並選擇之前鏡像存放的路徑作爲虛擬機存放路徑
點擊繼續
Cpu/memory等信息保持默認,點擊下一步即可:
內存選擇512足夠了:
選擇橋接:
保持默認,點擊下一步
選擇硬盤類型爲IDE:
創建新磁盤:
設置磁盤大小10G:
然後一路默認,點擊“下一步”直到結束。
1.2.2.修改虛擬機配置文件:
進入以上放置虛擬機的目錄,並找後綴爲vmx的文件,使用記事本打開,將以下途中表示的虛擬機名字修改成第一步中編譯出來,保存在本地的那個系統鏡像名稱:
修改完成後保存退出。
在vmware中啓動剛剛新建的虛擬機“smartRouter_4x4”
2.配置使用
2.1.設置root密碼
#passwd
兩次輸入密碼即可
重啓系統即可
2.2.網絡配置
#vi /etc/config/network
保存退出,後重啓網絡:
#/etc/init.d/network restart
#ifconfig br-lan
以上紅色標註處顯示修改成功。
2.3.關於ssh
OpenWrt 中默認自帶的 SSH 服務端和客戶端是 Dropbear
使用ssh代理方法,實現自動登錄ssh,類似於convirt節點管理方法:
http://talk.withme.me/?p=210#codesyntax_1
3.軟件安裝方法
3.1.本地安裝ipk軟件包
使用命令:opkg install softwall.ipk
例如:安裝sftp
首先下載軟件:
#cd /root
# opkg install openssh-sftp-server_5.8p2-2_x86.ipk
Installing openssh-sftp-server (5.8p2-2) to root…
Configuring openssh-sftp-server.
3.2.在線安裝軟件及設置
3.2.1.設置軟件源
#vim /etc/opkg.conf
內容如下:
dest root /
dest ram /tmp
lists_dir ext /var/opkg-lists
option overlay_root /overlay
src/gz barrier_breaker_basehttp://downloads.openwrt.org/barrier_breaker/14.07/x86/generic/packages/base
最後一行url爲實際可以下載到ipk的地址,配置好後才能使用在線安裝功能。
3.2.2.安裝軟件
更新可用ipk軟件包列表:
#opkg update
查看所有ipk包列表:
#opkg list
#opkg install software.ipkg
安裝軟件:
#opkg install software.ipk
3.2.3.直接安裝
在3.1節中,描述的安裝本地軟件,實際上是先wget下載一個軟件到本地,然後再執行本地安裝,實際上可以直接使用opkg install URL的方式在線安裝。如下:
#opkg install http://downloads.openwrt.org/backfire/10.03.1/x86_generic/packages/openssh-sftp-server_5.8p2-2_x86.ipk
3.3.使用本地服務器做軟件源以安裝自定義軟件
安裝本地軟件有兩種方法:一種是將本地軟件scp到openwrt系統中,通過opkg安裝即可;第二種就是在本地服務器上搭建一個ftp服務器,然後在openwrt系統軟件源中添加該服務器的url,然後通過在線安裝的方式安裝本地服務器上製作的Ipk包。現就第二種方式做介紹。
3.3.1.在編譯服務器上配置vsftp服務器
2.11安裝ftp服務器
#sudo apt-get install vsftpd
# sudo gedit /etc/vsftpd.conf
原文件中不少指令被註釋,只要啓用部分即可,一下是啓用的命令(配置文件中對每一條都有具體說明)
listen=YES # 服務器監聽
anonymous_enable=YES # 匿名訪問允許
local_enable=YES # 本地主機訪問允許
write_enable=YES # 寫允許
anon_upload_enable=YES
# 匿名上傳允許,默認是NO,嫌麻煩的可以開起來。出了問題我不負責~
anon_mkdir_write_enable=YES # 匿名創建文件夾允許
dirmessage_enable=YES # 進入文件夾允許
xferlog_enable=YES # ftp 日誌記錄允許
connect_from_port_20=YES # 允許使用20號端口作爲數據傳送的端口
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/private/vsftpd.pem
保存。
創建匿名訪問目錄以供openwrt訪問
#mkdir /srv/ftp
修改ftp目錄權限:
#chmod 755 /srv/ftp
創建upload download目錄
#mkdir –p –m 777 /srv/ftp/upload
#mkdir –p –m 755 /srv/ftp/download
重啓vsftpd
#service vsftpd restart
然後將/home/nickli/openwrt_SDK/barrier_breaker/bin/x86/packages 目錄下所有的內容都拷貝到/srv/ftp/download目錄下:
#scp -r /home/nickli/openwrt_SDK/barrier_breaker/bin/x86/packages /srv/ftp/download/
默認情況下,每個用戶的家目錄會自動做爲vsftp服務的根目錄。也就是說使用用戶A的權限來訪問該ftp服務器時,其登錄到的ftp根目錄就是該用戶A在該ftp服務器寄存的這臺主機上自己的home目錄。
本文檔製作時服務器IP爲10.8.3.50的ubuntu-14.04系統,OpenWRT的SDK在/home/nickli/openwrt_SDK/barrier_breaker目錄下。編譯出來的所有成果在該目錄的bin目錄下。我們將使用編譯出的packages作爲最終的軟件源。
3.3.2.修改openwrt在線安裝軟件源
openwrt端修改opkg.conf,添加ftp服務器ipk包存在地址,註釋掉系統默認的url地址如下紅色標註,並添加綠色標註的文字:
src/gz barrier_breaker_base ftp://10.8.3.50/download/packages/base
(或者src/gz packages ftp://10.8.3.50/download/packages/base)
dest root /
dest ram /tmp
lists_dir ext /var/opkg-lists
option overlay_root /overlay
#src/gz barrier_breaker_base http://downloads.openwrt.org/barrier_breaker/14.07/x86/generic/packages/base
3.3.3更新opkg
#opkg update
若無錯誤提示,則表示可以正常使用opkg從ftp下載ipk包了。
3.3.3.通過命令安裝ipk軟件
#opkg install software.ipk
4.openwrt SDK源碼目錄結構
4.1.源碼下載
OpenWrt的源代碼管理默認用的是SVN下載:
svn co svn://svn.openwrt.org/openwrt/trunk/ .
還可以用Git下載:
git clone git://git.openwrt.org/openwrt.git
git clone git://git.openwrt.org/packages.git
參考方法:https://dev.openwrt.org/wiki/GetSource
4.2.OpenWRT的feeds
包括:
packages – 提供衆多庫, 工具等基本功能. 也是其他feed所依賴的軟件源, 因此在安裝其他feed前一定要先安裝packages!
luci – OpenWrt默認的GUI(WEB管理界面).
xwrt – 另一種可替換LuCI的GUI
qpe – DreamBox維護的基於Qt的圖形界面, 包含Qt2, Qt4, Qtopia, OPIE, SMPlayer等衆多圖形界面.
device – DreamBox維護與硬件密切相關的軟件, 如uboot, qemu等.
dreambox_packages – DreamBox維護的國內常用網絡工具, 如oh3c, njit8021xclient等.
desktop – OpenWrt用於桌面的一些軟件包.
xfce – 基於Xorg的著名輕量級桌面環境. Xfce建基在GTK+2.x之上, 它使用Xfwm作爲窗口管理器.
efl – 針對enlightenment.
phone -針對fso, paroli.
trunk中默認的feeds下載有packages、xwrt、luci、routing、telephony。如要下載其他的軟件包,需打開源碼根目錄下面的feeds.conf.default文件,去掉相應軟件包前面的#號,然後更新源:
./scripts/feeds update -a
安裝下載好的包:
./scripts/feeds install -a
4.3.OpenWrt源碼目錄結構
tools和toolchain目錄:包含了一些通用命令, 用來生成固件, 編譯器, 和C庫.
build dir/host目錄:是一個臨時目錄, 用來儲存不依賴於目標平臺的工具.
build dir/toolchain-目錄:用來儲存依賴於指定平臺的編譯鏈. 只是編譯文件存放目錄無需修改.
build dir/target-目錄:用來儲存依賴於指定平臺的軟件包的編譯文件, 其中包括linux內核, u-boot, packages, 只是編譯文件存放目錄無需修改.
staging_dir目錄:是編譯目標的最終安裝位置, 其中包括rootfs, package, toolchain.
package目錄:軟件包的下載編譯規則, 在OpenWrt固件中, 幾乎所有東西都是.ipk, 這樣就可以很方便的安裝和卸載.
target目錄:目標系統指嵌入式設備, 針對不同的平臺有不同的特性, 針對這些特性, “target/linux”目錄下按照平臺進行目錄劃分, 裏面包括了針對標準內核的補丁, 特殊配置等.
bin目錄:編譯完OpenWrt的二進制文件生成目錄, 其中包括sdk, uImage, u-boot, dts, rootfs構建一個嵌入式系統完整的二進制文件.
config目錄:存放着整個系統的的配置文件.
docs目錄:裏面不斷包含了整個宿主機的文件源碼的介紹, 裏面還有Makefile爲目標系統生成docs.
include目錄:裏面包括了整個系統的編譯需要的頭文件, 但是是以Make進行連接的.
feeds目錄:擴展軟件包索引目錄.
scripts目錄:組織編譯整個OpenWrt的規則.
tmp目錄:編譯文件夾, 一般情況爲空.
dl目錄:所有軟件的下載目錄, 包括u-boot, kernel.
logs目錄:如果編譯出錯, 可以在這裏找到編譯出錯的log.
4.4.基本命令介紹
#./scripts/feeds update -a
#./scripts/feeds install -a
#make prereq //該命令可以檢查SDK中target內是否有存在錯誤的Makefile。
#make defconfig
#make menuconfig
#make kernel_config
#make V=99
#make package/pacakge_name/{clean,prepare,compile,install} V=99
5.用戶層模塊開發方法
5.1.SDK準備
請參考:4.1小節內容。
5.2.添加模塊代碼
例如添加模塊名字加hello,其目錄樹如下:
hello
├── files ———————–文件夾:存放文件如:服務配置文件/啓動腳本等
├── Makefile ————————文件:打包相關的Makefile
└── src ———————–文件夾:存放模塊源碼文件
├── hello.c ———————文件:源代碼
└── Makefile ——————文件:源碼編譯用的Makefile
5.3.模塊程序源代碼
#include<stdio.h>
int main ()
{
printf(“hello world!\n”);
return 0;
}
5.4.源碼編譯Makefile
hello:hello.o
$(CC) -o $@ $^
hello.o:hello.c
$(CC) -c $<
clean:
rm -rf *.o hello
5.5.ipK包製作規則Makefile
#
# Copyright (C) 2006-2010 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=hello
PKG_VERSION:=1.0
PKG_RELEASE:=1.0
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/hello
SECTION:=libs
CATEGORY:=Libraries
TITLE:=hello
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/hello/description
the hello is the base utils of the skysoft’s smartrouter
endef
define Package/hello/install
$(INSTALL_DIR) $(1)/usr/lib/
$(CP) $(PKG_BUILD_DIR)/hello.so* $(1)/usr/lib/
endef
$(eval $(call BuildPackage,hello))
注意以上紅色標註部分文字須保持一致。
5.6.編譯模塊
$ make package/libs/hello/compile V=99
$ make package/libs/hello/clean
$ make package/skysoft_web_admin/{clean,prepare,compile} V=99
5.7.ipk包製作
加入install後會將編譯結果打包安裝到SDK目錄下bin目錄下相應位置。
$ make package/skysoft_web_admin/{clean,prepare,compile,install} V=99
5.8.安裝測試
編譯完成後在SDK目錄下的/bin/x86/package/base文件夾下回有hello*.ipk文件,將bin/x86/package整個目錄拷貝到ftp下載目錄/srv/ftp/download/下,採用在openwrt在線安裝的方式,或者直接遠程拷貝到目標openwrt系統中安裝。
6.內核模塊開發方法
OpenWrt開發內核驅動有多種方式,前面講到的製作內核補丁也是一種開發方法。這裏介紹直接在OpenWrt系統上開發內核驅動,把內核驅動做成ipk軟件包的形式。
6.1.建立工作目錄
$cd openwrt/trunk/package
$mkdir example
6.2.進入example目錄,創建Makefile文件和代碼路徑
$cd example
$mkdir src
$vim Makefile
文件內容如下:
# Kernel module example
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=example
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define KernelPackage/example
SUBMENU:=Other modules
DEPENDS:=@TARGET_octeon
TITLE:=Support Module for example
AUTOLOAD:=$(call AutoLoad,81,example)
FILES:=$(PKG_BUILD_DIR)/example/example.$(LINUX_KMOD_SUFFIX)
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) -R ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C “$(LINUX_DIR)” \
CROSS_COMPILE=”$(TARGET_CROSS)” \
ARCH=”$(LINUX_KARCH)” \
SUBDIRS=”$(PKG_BUILD_DIR)/example” \
EXTRA_CFLAGS=”-g $(BUILDFLAGS)” \
modules
endef
$(eval $(call KernelPackage,example))
註釋:AUTOLOAD:定義了內核模塊開機自動掛載的動作,$(call AutoLoad,81,example),表示當系統啓動時名叫“example”的內核模塊會在順序爲第81的位置加載到系統中,不必每次啓動系統後手動的去insmod加載模塊。如果正常,本模塊ipk包製作好並安裝到目標系統中後,會在目標系統的/etc/modules.d/目錄下創建一份名叫81-example的加載序列文件,其內容爲example。而本ipk安裝後會在/lib/moudules/$(KERNEL-VERSION)/目錄下放置example.ko文件。而如果本軟件包包含了多個內核模塊文件,比如多個“.ko”文件,那麼在這個位置就要加入多個模塊名,模塊名之間以“空格”隔開,如下例:
define KernelPackage/exfat
SUBMENU:=Other modules
TITLE:=exfat driver
DEPENDS:=+kmod-nls-base @BUILD_PATENTED
FILES:=$(PKG_BUILD_DIR)/*.$(LINUX_KMOD_SUFFIX)
AUTOLOAD:=$(call AutoLoad,82,exfat_core exfat_fs)
KCONFIG:=
endef
定義的模塊名稱爲exfat,其內部有兩個內核文件需要添加,分別爲exfat_core.ko,exfat_fs.ko。
6.3.進入src目錄,創建代碼路徑和相關源文件
$cd src
$mkdir example
$cd example
$vim example.c
編輯內容如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/* hello_init —- 初始化函數,當模塊裝載時被調用,如果成功裝載返回0 否則返回非0值 */
static int __init hello_init(void)
{
printk(“I bear a charmed life.\n”);
return 0;
}
/ * hello_exit —- 退出函數,當模塊卸載時被調用 */
static void __exit hello_exit(void)
{
printk(“Out, out, brief candle\n”);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Pillar_zuo”);
vim Kconfig
config EXAMPLE
tristate “Just a example”
default n
help
This is a example, for debugging kernel model.
If unsure, say N.
保存並退出。
增加源碼編譯Makefile:
$vim Makefile
內容如下:
obj-m := example.o
6.4.回到OpenWrt源碼根目錄下
$make menuconfig
Kernel modules —>
Other modules —>
kmod-example
選項設置爲M,保存退出
然後編譯該模塊:
$make package/example/compile
$make package/index
6.5.在OpenWrt系統裏面就可以用opkg下載使用了。
方法參考第三節內容。
7.openwrt Makefile框架分析
本節原文地址:http://www.cnblogs.com/sammei/p/3968916.html
本篇的主要目的是想通過分析Makefile,瞭解openwrt編譯過程。着重關注以下幾點:
- openwrt目錄結構
- 主Makefile的解析過程,各子目錄的目標生成
- kernel編譯過程
- firmware的生成過程
- 軟件包的編譯過程
7.1.openwrt目錄結構
官方源下載速度太慢,我從github上clone了openwrt的代碼倉庫。
git clone https://github.com/openwrt-mirror/openwrt.git
openwrt目錄結構
上圖是openwrt目錄結構,其中第一行是原始目錄,第二行是編譯過程中生成的目錄。各目錄的作用是:
tools – 編譯時需要一些工具, tools裏包含了獲取和編譯這些工具的命令。裏面是一些Makefile,有的可能還有patch。每個Makefile裏都有一句 $(eval $(call HostBuild)),表示編譯這個工具是爲了在主機上使用的。
toolchain – 包含一些命令去獲取kernel headers, C library, bin-utils, compiler, debugger
target – 各平臺在這個目錄裏定義了firmware和kernel的編譯過程。
package – 包含針對各個軟件包的Makefile。openwrt定義了一套Makefile模板,各軟件參照這個模板定義了自己的信息,如軟件包的版本、下載地址、編譯方式、安裝地址等。
include – openwrt的Makefile都存放在這裏。
scripts – 一些perl腳本,用於軟件包管理。
dl – 軟件包下載後都放到這個目錄裏
build_dir – 軟件包都解壓到build_dir/裏,然後在此編譯
staging_dir – 最終安裝目錄。tools, toolchain被安裝到這裏,rootfs也會放到這裏。例如puma_SDK中文件系統目錄爲:
/home/nickli/2015-Router_N01-PUMA_4x4/trunk/02.DevelopementRepository/04.Coding/qualcomm_sdk/qsdk/staging_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/root-ipq806x
feeds – 擴展軟件包索引目錄。
bin – 編譯完成之後,firmware和各ipk會放到此目錄下。
推薦閱讀官方資料:OpenWrt Development Guide
7.2.main Makefile
openwrt根目錄下的Makefile是執行make命令時的入口。從這裏開始分析。
world:
ifndef ($(OPENWRT_BUILD),1)
# 第一個邏輯
…
else
# 第二個邏輯
…
endif
上面這段是主Makefile的結構,可以得知:
執行make時,若無任何目標指定,則默認目標是world
執行make時,無參數指定,則會進入第一個邏輯。如果執行命令make OPENWRT_BUILD=1,則直接進入第二個邏輯。
編譯時一般直接使用make V=s -j5這樣的命令,不會指定OPENWRT_BUILD變量
第一個邏輯
override OPENWRT_BUILD=1
export OPENWRT_BUILD
更改了OPENWRT_BUILD變量的值。這裏起到的作用是下次執行make時,會進入到第二邏輯中。
toplevel.mk中的 %:: 解釋world目標的規則。
prereq:: prepare-tmpinfo .config
@+$(MAKE) -r -s tmp/.prereq-build $(PREP_MK)
@+$(NO_TRACE_MAKE) -r -s $@
%::
@+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq
@( \
cp .config tmp/.config; \
./scripts/config/conf –defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \
if ./scripts/kconfig.pl ‘>’ .config tmp/.config | grep -q CONFIG; then \
printf “$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n” >&2; \
fi \
)
@+$(ULIMIT_FIX) $(SUBMAKE) -r $@
執行 make V=s 時,上面這段規則簡化爲:
prereq:: prepare-tmpinfo .config
@make -r -s tmp/.prereq-build
@make V=ss -r -s prereq
%::
@make V=s -r -s prereq
@make -w -r world
可見其中最終又執行了prereq和world目標,這兩個目標都會進入到第二邏輯中。
第二邏輯
首先就引入了target, package, tools, toolchain這四個關鍵目錄裏的Makefile文件
include target/Makefile
include package/Makefile
include tools/Makefile
include toolchain/Makefile
這些子目錄裏的Makefile使用include/subdir.mk裏定義的兩個函數來動態生成規則,這兩個函數是subdir和stampfile
stampfile
拿target/Makefile舉例:
(eval(call stampfile,$(curdir),target,prereq,.config))
會生成規則:
target/stamp-prereq:=$(STAGING_DIR)/stamp/.target_prereq
$$(target/stamp-prereq): $(TMP_DIR)/.build .config
@+$(SCRIPT_DIR)/timestamp.pl -n $$(target/stamp-prereq) target .config || \
make $$(target/flags-prereq) target/prereq
@mkdir -p $$$$(dirname $$(target/stamp-prereq))
@touch $$(target/stamp-prereq)
$$(if $(call debug,target,v),,.SILENT: $$(target/stamp-prereq))
.PRECIOUS: $$(target/stamp-prereq) # work around a make bug
target//clean:=target/stamp-prereq/clean
target/stamp-prereq/clean: FORCE
@rm -f $$(target/stamp-prereq)
所以可以簡單的看作: (eval(call stampfile,(curdir),target,prereq,.config))生成了目標(target/stamp-prereq)
對於target分別生成了:(target/stamp−preq),(target/stamp-copile), $(target/stamp-install)
toolchain : $(toolchain/stamp-install)
package : (package/stamp−preq),(package/stamp-cleanup), (package/stamp−compile),(package/stamp-install)
tools : $(tools/stamp-install)
OpenWrt的主Makefile工作過程
subdir
subdir這個函數寫了一大堆東西,看起來很複雜 。
$(call subdir, target) 會遍歷下的子目錄,執行 make -C 操作。這樣就切入子目錄中去了。
目錄變量
幾個重要的目錄路徑:
KERNEL_BUILD_DIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/linux-3.14.18
LINUX_DIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/linux-3.14.18
KDIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a
BIN_DIR
bin/ramips
Makefile中包含了rules.mk, target.mk等.mk文件,這些文件中定義了許多變量,有些是路徑相關的,有些是軟件相關的。這些變量在整個Makefile工程中經常被用到,
TARGET_ROOTFS_DIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2
BUILD_DIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2
STAGING_DIR_HOST
staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2
TARGET_DIR
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/root-ramips
7.3.kernel 編譯:
target/linux/ramips/Makefile: $(eval $(call BuildTarget))
target/linux/Makefile : export TARGET_BUILD=1
include/target.mk:
ifeq ($(TARGET_BUILD),1)
include $(INCLUDE_DIR)/kernel-build.mk
BuildTarget?=$(BuildKernel)
endif
BuildKernel是include/kernel-build.mk定義的一個多行變量,其中描述瞭如何編譯內核, 主要關注其中install規則的依賴鏈:
$(KERNEL_BUILD_DIR)/symtab.h: FORCE
rm -f $(KERNEL_BUILD_DIR)/symtab.h
touch $(KERNEL_BUILD_DIR)/symtab.h
+$(MAKE) $(KERNEL_MAKEOPTS) vmlinux
…
$(LINUX_DIR)/.image: $(STAMP_CONFIGURED) $(if $(CONFIG_STRIP_KERNEL_EXPORTS),$(KERNEL_BUILD_DIR)/symtab.h) FORCE
$(Kernel/CompileImage)
$(Kernel/CollectDebug)
touch $$@
install: $(LINUX_DIR)/.image
+$(MAKE) -C image compile install TARGET_BUILD=
- 觸發make vmlinux命令生成vmlinux: install –> $(LINUX_DIR)/.image –> $(KERNEL_BUILD_DIR)/symtab.h –> `$(MAKE) $(KERNEL_MAKEOPTS) vmlinux`
- 對vmlinux做objcopy, strip操作: $(LINUX_DIR)/.image –> $(Kernel/CompileImage) –> $(call Kernel/CompileImage/Default) –> $(call Kernel/CompileImage/Default)
$(KERNEL_CROSS)objcopy -O binary $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(LINUX_KERNEL)$(1)
–> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux
$(KERNEL_CROSS)objcopy $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux$(1).elf
–> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux.elf
$(CP) $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux.debug
–> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux.debug
7.4.生成firmware
firmware由kernel和rootfs兩個部分組成,要對兩個部分先分別處理,然後再合併成一個.bin文件。先看一下這個流程。
“target/linux/ramips/image/Makefile” 文件中的最後一句:$(eval $(call BuildImage)),將BuildImage展開在這裏。BuildImage定義在 include/image.mk 文件中,其中定義了數個目標的規則。
define BuildImage
compile: compile-targets FORCE
**$(call Build/Compile)**
install: compile install-targets FORCE
…
$(call Image/BuildKernel) ## 處理vmlinux
…
$(call Image/mkfs/squashfs) ## 生成squashfs,並與vmlinux合併成一個.bin文件
…
endef
處理vmlinux: Image/BuildKernel
target/linux/ramips/image/Makefile:
define Image/BuildKernel
cp $(KDIR)/vmlinux.elf $(BIN_DIR)/$(VMLINUX).elf
cp $(KDIR)/vmlinux $(BIN_DIR)/$(VMLINUX).bin
$(call CompressLzma,$(KDIR)/vmlinux,$(KDIR)/vmlinux.bin.lzma)
$(call MkImage,lzma,$(KDIR)/vmlinux.bin.lzma,$(KDIR)/uImage.lzma)
cp $(KDIR)/uImage.lzma $(BIN_DIR)/$(UIMAGE).bin
ifneq ($(CONFIG_TARGET_ROOTFS_INITRAMFS),)
cp $(KDIR)/vmlinux-initramfs.elf $(BIN_DIR)/$(VMLINUX)-initramfs.elf
cp $(KDIR)/vmlinux-initramfs $(BIN_DIR)/$(VMLINUX)-initramfs.bin
$(call CompressLzma,$(KDIR)/vmlinux-initramfs,$(KDIR)/vmlinux-initramfs.bin.lzma)
$(call MkImage,lzma,$(KDIR)/vmlinux-initramfs.bin.lzma,$(KDIR)/uImage-initramfs.lzma)
cp $(KDIR)/uImage-initramfs.lzma $(BIN_DIR)/$(UIMAGE)-initramfs.bin
endif
$(call Image/Build/Initramfs)
endef
lzma壓縮內核
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/ 目錄中:
lzma e vmlinux -lc1 -lp2 -pb2 vmlinux.bin.lzma
MkImage
build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/ 目錄中:
mkimage -A mips -O linux -T kernel -C lzma -a 0x80000000 -e 0x80000000 -n “MIPS OpenWrt Linux-3.14.18” -d vmlinux.bin.lzma uImage.lzma
copy
VMLINUX:=$(IMG_PREFIX)-vmlinux –> openwrt-ramips-mt7620a-vmlinux
UIMAGE:=$(IMG_PREFIX)-uImage –> openwrt-ramips-mt7620a-uImage
cp $(KDIR)/uImage.lzma $(BIN_DIR)/$(UIMAGE).bin
把uImage.lzma複製到bin/ramips/目錄下:
cp $(KDIR)/uImage.lzma bin/ramips/openwrt-ramips-mt7620a-uImage
7.5.製作squashfs,生成.bin: $(call Image/mkfs/squashfs)
define Image/mkfs/squashfs
@mkdir -p $(TARGET_DIR)/overlay
$(STAGING_DIR_HOST)/bin/mksquashfs4 $(TARGET_DIR) $(KDIR)/root.squashfs -nopad -noappend -root-owned -comp $(SQUASHFSCOMP) $(SQUASHFSOPT) -processors $(if $(CONFIG_PKG_BUILD_JOBS),$(CONFIG_PKG_BUILD_JOBS),1)
$(call Image/Build,squashfs)
endif
mkdir -p $(TARGET_DIR)/overlay
mkdir -p build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/root-ramips/overlay
mksquashfs4
$(STAGING_DIR_HOST)/bin/mksquashfs4 $(TARGET_DIR) $(KDIR)/root.squashfs -nopad -noappend -root-owned -comp $(SQUASHFSCOMP) $(SQUASHFSOPT) -processors $(if $(CONFIG_PKG_BUILD_JOBS),$(CONFIG_PKG_BUILD_JOBS),1)
製作squashfs文件系統,生成root.squashfs:
mksquashfs4 root-ramips root.squashfs -nopad -noappend -root-owned -comp gzip -b 256k -p ‘/dev d 755 0 0’ -p ‘/dev/console c 600 0 0 5 1’ -processors 1
$(call Image/Build,squashfs)
在 target/linux/ramips/image/Makefile 中:
define Image/Build
$(call Image/Build/$(1))
dd if=$(KDIR)/root.$(1) of=$(BIN_DIR)/$(IMG_PREFIX)-root.$(1) bs=128k conv=sync
$(call Image/Build/Profile/$(PROFILE),$(1))
endef
dd if=(KDIR)/root.squashfsof=(BIN_DIR)/$(IMG_PREFIX)-root.squashfs bs=128k conv=sync
dd if=build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/root.squashfs of=bin/ramips/openwrt-ramips-mt7620-root.squashfs bs=128k conv=sync
(callImage/Build/Profile/(PROFILE),squashfs)
target/linux/ramips/mt7620a/profiles/00-default.mk, 中調用 Profile 函數:$(eval $(call Profile,Default))
include/target.mk 中定義了 Profile 函數, 其中令 PROFILE=Default
define Image/Build/Profile/Default
$(call Image/Build/Profile/MT7620a,$(1))
…
endef
規則依賴序列如下:
$(call Image/Build/Profile/$(PROFILE),squashfs)
–> $(call BuildFirmware/Default8M/squashfs,squashfs,mt7620a,MT7620a)
–> $(call BuildFirmware/OF,squashfs,mt7620a,MT7620a,8060928)
–> $(call MkImageLzmaDtb,mt7620a,MT7620a)
–> $(call PatchKernelLzmaDtb,mt7620a,MT7620a)
–> $(call MkImage,lzma,$(KDIR)/vmlinux-mt7620a.bin.lzma,$(KDIR)/vmlinux-mt7620a.uImage)
–> $(call MkImageSysupgrade/squashfs,squashfs,mt7620a,8060928)
其中的主要步驟:
複製: cp (KDIR)/vmlinux(KDIR)/vmlinux-mt7620a
生成dtb文件: (LINUXDIR)/scripts/dtc/dtc−Odtb−o(KDIR)/MT7620a.dtb ../dts/MT7620a.dts
將內核與dtb文件合併:(STAGINGDIRHOST)/bin/patch−dtb(KDIR)/vmlinux-mt7620a $(KDIR)/MT7620a.dtb
使用lzma壓縮:(callCompressLzma,(KDIR)/vmlinux-mt7620a,$(KDIR)/vmlinux-mt7620a.bin.lzma)
將lzma壓縮後的文件經過mkimage工具處理,即在頭部添加uboot可識別的信息。
接下來就是合併生成firmware固件了:
MkImageSysupgrade/squashfs, squashfs, mt7620a,8060928
cat vmlinux-mt7620a.uImage root.squashfs > openwrt-ramips-mt7620-mt7620a-squashfs-sysupgrade.bin
–> 製作squashfs bin文檔, 並確認它的大小 < 8060928 纔是有效的,否則報錯。
總結: 整個流程下來,其實最煩索的還是對內核生成文件vmlinux的操作,經過了objcopy, patch-dtb, lzma, mkimage 等過程生成一個uImage,再與mksquashfs工具製作的文件系統rootfs.squashfs合併。
7.6.Openwrt_SDK中Makefile相關總結
7.6.1CURDIR變量
在makefile中表示當前目錄,效果等同於shell命令pwd.
7.6.2空格的表示方法:
empty:=
space:=$(empty) $(empty)
在openwrtSDK中該變量用以檢查SDK目錄是否含有空格,要求所有路徑中不得有含空格的文件夾
7.6.3調用makefile中的函數
if判斷函數,用法:
$(if <condition>,<then-part>)
或者:
$(if <condition>,<then-part>,<else-part>)
findstring查找字符串函數,用法:
$(findstring <find>,<in>)在字串<in>中查找<find>字串
error控制makefile運行的函數,用法:
7.6.4.其他相關內容
2.1make 的參數
-i 或者”–ignore-errors”:忽略Makefile中所有的命令錯誤;
-k或者“–keep-going“:終止出錯命令,但繼續執行其他命令;
-w或者”–print-directory“:嵌套執行make時,會輸出當前工作目錄信息,該選項的反作用項是”-s“;
2.2嵌套執行make
我們有一個子目錄叫 subdir,這個目錄下有個 Makefile 文件,來指明瞭這個目錄下文件的編譯規則。那麼我們總控的 Makefile 可以這樣書寫:
subsystem:
cd subdir && $(MAKE)
其等價於:
subsystem:
$(MAKE) -C subdir
定義$(MAKE)宏變量的意思是,也許我們的 make 需要一些參數,所以定義成一個變量比較利於維護。這兩個例子的意思都是先進入“subdir”目錄,然後執行 make 命令。
我們把這個 Makefile 叫做“總控 Makefile”,總控 Makefile 的變量可以傳遞到下級的 Makefile 中(如果你顯示的聲明),但是不會覆蓋下層的 Makefile 中所定義的變量,除非指定了“-e”參數。
如果你要傳遞變量到下級 Makefile 中,那麼你可以使用這樣的聲明:
export <variable …>
如果你不想讓某些變量傳遞到下級 Makefile 中,那麼你可以這樣聲明:
unexport <variable …>
2.在package目錄中的Makefile:
2.1.call函數
call 函數是唯一一個可以用來創建新的參數化的函數。你可以寫一個非常複雜的表達式,這個表達式中,你可以定義許多參數,然後你可以用 call 函數來向這個表達式傳遞參數。其語法是:
$(call <expression>,<parm1>,<parm2>,<parm3>…)
當 make 執行這個函數時,<expression>參數中的變量,如$(1),$(2),$(3)等,會被參數<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是 call 函數的返回值。
2.2.eval函數
函數“eval”是一個比較特殊的函數。使用它可以在Makefile中構造一個可變的規則結構關係(依賴關係鏈),其中可以使用其它變量和函數。
函數“eval”對它的參數進行展開,展開的結果作爲Makefile的一部分,make可以對展開內容進行語法解析。展開的結果可以包含一個新變量、目標、隱含規則或者是明確規則等。也就是說此函數的功能主要是:根據其參數的關係、結構,對它們進行替換展開。
Ø 返回值:函數“eval”的返回值是空,也可以說沒有返回值。
Ø 函數說明:“eval”函數執行時會對它的參數進行兩次展開。第一次展開過程發是由函數本身完成的,第二次是函數展開後的結果被作爲Makefile內容時由make解析時展開的。明確這一過程對於使用“eval”函數非常重要。理解了函數“eval”二次展開的過程後。實際使用時,如果在函數的展開結果中存在引用(格式爲:$(x)),那麼在函數的參數中應該使用“$$”來代替“$”。因爲這一點,所以通常它的參數中會使用函數“value”來取一個變量的文本值。
我們看一個例子。例子看起來似乎非常複雜,因爲它綜合了其它的一些概念和函數。不過我們可以考慮兩點:其一,通常實際一個模板的定義可能比例子中的更爲複雜;其二,我們可以實現一個複雜通用的模板,在所有Makefile中包含它,亦可作到一勞永逸。相信這一點可能是大多數程序員所推崇的。
示例:
# sample Makefile
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Everything after this is generic
.PHONY: all
all: $(PROGRAMS)
define PROGRAM_template
$(1): $$($(1)_OBJ) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $@
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
來看一下這個例子:它實現的功能是完成“PROGRAMS”的編譯鏈接。例子中“$(LINK.o)”爲“$(CC) $(LDFLAGS)”,意思是對所有的.o文件和指定的庫文件進行鏈接。
“$(foreach prog,$(PROGRAM),$(eval $(call PROGRAM_template,$(prog))))”展開爲:
server : $(server_OBJS) –l$(server_LIBS)
client : $(client_OBJS) –l$(client_LIBS)
8.軟件包Makefile解析
上面一篇博文中,博主嘗試創建一個非常簡單的helloworld包,過程詳見博文:http://my.oschina.net/hevakelcj/blog/410633
本文將帶大家一起深入地學習一下OpenWrt包的 Makefile。我們不僅要知其然,還要知其所以然。在上篇博文裏,包裏的 Makefile 內容如下:
include $(TOPDIR)/rules.mk
PKG_NAME:=helloworld PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/helloworld SECTION:=utils CATEGORY:=Utilities TITLE:=Helloworld — prints a snarky message endef
define Package/helloworld/description It’s my first package demo. endef
define Build/Prepare echo “Here is Package/Prepare” mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef
define Package/helloworld/install echo “Here is Package/install” $(INSTALL_DIR) $(1)/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/bin/ endef
$(eval $(call BuildPackage,helloworld)) |
大概我們可以將簡代爲如下的結構:
include $(TOPDIR)/rules.mk
# 這裏定義一系列的 PKG_XX
include $(INCLUDE_DIR)/package.mk
# 定義各種 Package, Build 宏
$(eval $(call BuildPackage,包名)) |
下面,我們來一一拆解。
8.1.include $(TOPDIR)/rules.mk
首先,include $(TOPDIR)/rules.mk,也就是將 SDK/rules.mk 文件中的內容導入進來。
TOPDIR就是SDK的路徑。
在 SDK/rules.mk 文件中,定義了許多變量。
我們可以看出,在Makefile中,賦值是用 := ,而不是等號。
比如上面的 BUILD_DIR, INCLUDE_DIR 等,都在這裏定義。還有:
還有關於 TARGET_CC, TARGET_CXX 等非常有用的變量定義。
還有 TAR, FIND, INSTALL_BIN, INSTALL_DIR, INSTALL_DATA等等非常重要的變量定義。
8.2.自定義 PKG_XXXX 變量
官網上指定有如下變量需要設置:
PKG_NAME – The name of the package, as seen via menuconfig and ipkg
PKG_VERSION – The upstream version number that we’re downloading
PKG_RELEASE – The version of this package Makefile
PKG_LICENSE – The license(s) the package is available under, SPDX form.
PKG_LICENSE_FILE- file containing the license text
PKG_BUILD_DIR – Where to compile the package
PKG_SOURCE – The filename of the original sources
PKG_SOURCE_URL- Where to download the sources from (directory)
PKG_MD5SUM – A checksum to validate the download
PKG_CAT – How to decompress the sources (zcat, bzcat, unzip)
PKG_BUILD_DEPENDS – Packages that need to be built before this package, but are not required at runtime. Uses the same syntax as DEPENDS below.
PKG_INSTALL – Setting it to “1” will call the package’s original “make install” with prefix set to PKG_INSTALL_DIR
PKG_INSTALL_DIR – Where “make install” copies the compiled files
PKG_FIXUP – ???
PKG_SOURCE_PROTO – the protocol to use for fetching the sources (git, svn)
PKG_REV – the svn revision to use, must be specified if proto is “svn”
PKG_SOURCE_SUBDIR – must be specified if proto is “svn” or “git”, e.g. “PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)”
PKG_SOURCE_VERSION – must be specified if proto is “git”, the commit hash to check out
PKG_CONFIG_DEPENDS – specifies which config options depend on this package being selected
8.3.include $(INCLUDE_DIR)/package.mk
跟上面的 include $(TOPDIR)/rules.mk 是一樣的。就是把這個文件包含進來。
INCLUDE_DIR這個變量在 rules.mk 裏已經定義了:
那就是 SDK/include/package.mk 文件了,打開看看。
主要有以下幾個功能:
(1)它會配置默認的變量
如果某個變量我們沒有在上一部分裏定義,那裏在這個文件裏,它就會被指定爲默認值,比如:
上面的用 ?= 來表示給未定義的變量賦默認值。比如,如果沒有指定 PKG_MD5SUM,那麼就默認爲 unknow。
(2)推導其它變量
根據上部分用戶自定義的 PKG_XXXX 變量推導出更多相關的變量。
比如:
雖然我沒有看過相關的手冊,根據多年的從業經驗也能看出上面的意思來。
#如果定義了宏,就…
ifdef 宏名 … endif
#如果宏相等 ifeq (宏1,宏2) … endif
strip $宏名 #將宏對應的值去除前後的空白字符
VAR += xxxx #在變量 VAR 後面追加 xxxx |
我猜大概就是這樣,如果不對請指正。
再比如如下:
就這樣,它爲我們提供了大量有價值的變量。
(3)包含其它mk文件
(4)定義默認宏
在 Makefile 中,宏的定義格式是:
define XXX/xxxx
<宏的實體…> endef |
package.mk會把大部分需要的宏都定義好。理想情況下,用戶只需要定義好了 PKG_XXX 之後,不需要再自定義宏,默認的宏就可以滿足需求。
比如Build/Prepare/Default的定義:
Build/Prepare宏是在編譯前進行的,是準備工作。
可以看出,它分了兩種情況:
A,定義了 USE_GIT_TREE,則按git的方式定義。
B,定義了 USB_SOURCE_DIR,則按源碼在本地的方案定義。
(5)BuildPackage宏
最重要的一個宏是 BuildPackage。它會在 Makefile 的最後一行被引用。它的實現也就是在 package.mk 文件裏。如下爲其源碼:
define BuildPackage
$(Build/IncludeOverlay) $(eval $(Package/Default)) #定義在package-defaults.mk文件裏 $(eval $(Package/$(1))) #調用用戶自定義的 Package/<包名> 宏
ifdef DESCRIPTION $$(error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description) endif
#檢查有沒有定義 Package/<包名>/description宏,如果沒有定義,則以TITLE默認定義一個 ifndef Package/$(1)/description define Package/$(1)/description $(TITLE) endef endif
BUILD_PACKAGES += $(1) $(STAMP_PREPARED): $$(if $(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))
#檢查 TITLE, CATEGORY, SECTION, VERSION 是否定義,如果沒有定義則報錯 $(foreach FIELD, TITLE CATEGORY SECTION VERSION, ifeq ($($(FIELD)),) $$(error Package/$(1) is missing the $(FIELD) field) endif )
#如果有定義DUMP,那就引入Dumpinfo/Package宏的內部。 #如果沒有,那麼就引用 Packaget/<包名>/targets裏面的每一個target,如果沒有定義Packaget/<包名>/targets宏,那麼將PKG_TARGETS裏的每個target取出來, #如果也沒有定義PKG_TARGETS,那就默認ipkg作爲target。將每一個target,引用 BuildTarget/$(target)。 $(if $(DUMP), \ $(Dumpinfo/Package), \ $(foreach target, \ $(if $(Package/$(1)/targets),$(Package/$(1)/targets), \ $(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \ ), $(BuildTarget/$(target)) \ ) \ ) $(if $(PKG_HOST_ONLY)$(DUMP),,$(call Build/DefaultTargets,$(1))) endef |
總結一下語法:
$() 表示要執行的一條語句
$(if 條件, 成立執行, 失敗執行) if條件分支
$(foreach 變量, 成員列表, 執行體) 成員遍歷語句
可以看出,語句是可以嵌套使用的。
$(N) 表示第N個參數
8.4.自定義宏
必須定義的宏
我定要爲我們的package定義特定的宏:
Package/<包名> #包的參數
在 Package/<包名> 宏中定義與包相關的信息。
如Package/helloworld宏:
1
2 3 4 5 |
define Package/helloworld
SECTION:=utils CATEGORY:=Utilities TITLE:=Helloworld — prints a snarky message endef |
除了上面所列的 SECTION,CATEGORY,TITLE變量外,還可以定義:
SECTION – 包的種類
CATEGORY – 顯示在menuconfig的哪個目錄下
TITLE – 簡單的介紹
DESCRIPTION – (deprecated) 對包詳細的介紹
URL – 源碼所在的網絡路徑
MAINTAINER – (required for new packages) 維護者是誰(出錯了聯繫誰)
DEPENDS – (optional) 需要依事的包,See below for the syntax.
USERID – (optional) a username:groupname pair to create at package installation time.
【PKG_VERSION宏缺失時報錯】:
OpenWrt Developers Team <[email protected]>
@@
Makefile:53: *** Package/exfat is missing the VERSION field. Stop.
可選定義的宏
其它的宏可以選擇性地定義,通常沒必要自己重寫。但有些情況,package.mk中默認的宏不能滿足我們的需求。這時,我們就需要自己重定義宏。
比如,我們在爲helloworld寫Makefile時,我們要求在編譯之前,將 SDK/package/helloworld/src/ 路徑下的文件複製到 PKG_BUILD_DIR 所指定的目錄下。
於是我們重新定義Build/Prepare宏:
1
2 3 4 |
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef |
如此以來,在我們 make V=s 時,make工具會在編譯之前執行 Build/Prepare 宏裏的命令。
再比如,我們要指定包的安裝方法:
1
2 3 4 |
define Package/helloworld/install
$(INSTALL_DIR) $(1)/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/bin/ endef |
上面的這個宏就是指定了包的安裝過程。其中 INSTALL_DIR 定義在 rules.mk 文件裏。
INSTALL_DIR = install -d -m0755
INSTALL_BIN = install -m0755
$(1)爲第一個參數是./configure時的–prefix參數,通常爲””
展開之後就是:
define Package/helloworld/install
install -d -m0755 /bin install -m0755 $(PKG_BUILD_DIR)/helloworld /bin/ endef |
它的意思就一目瞭然了。
除了上面所列舉的這兩個宏外,在官網上也說明了其它可選的宏:
Package/conffiles (optional)
由該包安裝的配置文件的列表,一行一個文件。
Package/description
對包描述的純文本
Build/Prepare (optional)
A set of commands to unpack and patch the sources. You may safely leave this undefined.
Build/Configure (optional)
You can leave this undefined if the source doesn’t use configure or has a normal config script, otherwise you can put your own commands here or use “$(call Build/Configure/Default,)” as above to pass in additional arguments for a standard configure script.
Build/Compile (optional)
How to compile the source; in most cases you should leave this undefined, because then the default is used, which calls make. If you want to pass special arguments to make, use e.g. “$(call Build/Compile/Default,FOO=bar)
Build/Install (optional)
How to install the compiled source. The default is to call make install. Again, to pass special arguments or targets, use $(call Build/Install/Default,install install-foo) Note that you need put all the needed make arguments here. If you just need to add something to the “install” argument, don’t forget the ‘install’ itself.
Build/InstallDev (optional)
For things needed to compile packages against it (static libs, header files), but that are of no use on the target device.
Package/install
A set of commands to copy files into the ipkg which is represented by the $(1) directory. As source you can use relative paths which will install from the unpacked and compiled source, or $(PKG_INSTALL_DIR) which is where the files in the Build/Install step above end up.
Package/preinst
The actual text of the script which is to be executed before installation. Dont forget to include the #!/bin/sh. If you need to abort installation have the script return false.
Package/postinst
The actual text of the script which is to be executed after installation. Dont forget to include the #!/bin/sh.
Package/prerm
The actual text of the script which is to be executed before removal. Dont forget to include the #!/bin/sh. If you need to abort removal have the script return false.
Package/postrm
The actual text of the script which is to be executed after removal. Dont forget to include the #!/bin/sh.
之所以有些宏是以”Package/”開頭,有的又以”Build/”,是因爲在一個Makefile裏生成多個包。OpenWrt默認認爲一個Makefile裏定義一個包,但我們也可以根據需要將其拆分成多個。所以說,如果我們只希望編譯一次,那麼只要有一系列的”Build/”的宏定義就可以了。但是,我們也可以通過添加多個”Package/”宏定義,並調用 BuildPackage,來創建多個包。
8.5.使之生效
在Makefile的最後一行是:
$(eval $(call BuildPackage,helloworld)) |
最重要的 BuildPackage定義在 package.mk 文件裏。見上面 BuildPackage 宏定義。
8.6.添加新編譯選項
比如dibbler軟件包的編譯,需要編譯C++代碼,需要使用-Istdc++選項,可以在dibbler編譯Makefile中添加TAGET_CXXFLAGS +=-Istdc++ 即可。
9.openwrt主Makafile解析
其他相關參考:http://blog.csdn.net/suiyuan19840208/article/details/25737323/
原文地址:http://www.right.com.cn/forum/thread-73443-1-1.html
本文是本人對OpenWrt的Makefile的理解,並非轉載。
OpenWrt是一個典型的嵌入式Linux工程,瞭解OpenWrt的Makefile的工作過程對提高嵌入式Linux工程的開發能力有極其重要意義。
OpenWrt的主Makefile文件只有100行,可以簡單分爲三部分,1~17行爲前導部分,19~31爲首次執行部分,33~101爲再次執行部分。
前導部分
CURDIR爲make默認變量,默認值爲當前目錄。
前導部分主要把變量TOPDIR賦值爲當前目錄,把變量LC_ALL、LANG賦值爲C,並使用變量延伸指示符export,把上述三個變量延伸到下層Makefile。
使用文件使用指示符include引入$(TOPDIR)/include/host.mk。在OpenWrt的主Makefile文件使用了多次include指示符,說明主Makefile文件被拆分成多個文件,被拆分的文件放在不同的目錄。拆分的目的是明確各部分的功能,而且增加其靈活性。
在前導部分比較費解的是使用world目標,在makefile中基本規則爲:
TARGETS : PREREQUISITES
COMMAND
…
即makefile規則由目標、依賴、命令三部分組成,在OpenWrt的主Makefile文件的第一個目標world沒有依賴和命令。它主要起到指示當make命令不帶目標時所要執行的目標,沒有設定依賴和命令部分表明此目標在此後將會有其他依賴關係或命令。world目標的命令需要進一步參考$(TOPDIR)/include/toplevel.mk和主Makefile文件的再次執行部分。
首次執行部分
OPENWRT_BUILD是區分首次執行與再次執行的變量。在首次執行時使用強制賦值指示符override把OPENWRT_BUILD賦值爲1,並使用變量延伸指示符export把OPENWRT_BUILD延伸。在OPENWRT_BUILD使用強制賦值指示符override意味着make命令行可能引入OPENWRT_BUILD參數。
引入$(TOPDIR)/include/debug.mk、$(TOPDIR)/include/depends.mk、$(TOPDIR)/include/toplevel.mk三個文件,由於TOPDIR是固定的,所以三個文件也是固定的。其中$(TOPDIR)/include/toplevel.mk的135行%::有效解釋首次執行時world目標的規則。
再次執行部分
引入rules.mk、$(INCLUDE_DIR)/depends.mk、$(INCLUDE_DIR)/subdir.mk、target/Makefile、package/Makefile、tools/Makefile、toolchain/Makefile七個文件,rules.mk沒有目錄名,即引入與主Makefile文件目錄相同的rules.mk。在rules.mk定義了INCLUDE_DIR爲$(TOPDIR)/include,所以$(INCLUDE_DIR)/depends.mk實際上與首次執行時引入的$(TOPDIR)/include/depends.mk是同一個文件。
四個子目錄下的Makefile實際上是不能獨立執行。主要利用$(INCLUDE_DIR)/subdir.mk動態建立規則,諸如$(toolchain/stamp-install)目標是靠$(INCLUDE_DIR)/subdir.mk的stampfile函數動態建立。在package/Makefile動態建立了$(package/ stamp-prereq)、$(package/ stamp-cleanup)、$(package/ stamp-compile)、$(package/ stamp-install)、$(package/
stamp-rootfs-prepare)目標。
定義一些使用變量命名的目標,其變量的賦值位置在$(INCLUDE_DIR)/subdir.mk的stampfile函數中。目標只有依賴關係,可能說明其工作順序,在$(INCLUDE_DIR)/subdir.mk的stampfile函數中有進一步說明其目標執行的命令,併爲目標建立一個空文件,即使用變量命名的目標爲真實的文件。
定義一些使用固定的目標規則。
其中:clean是清除編譯結果的目標,清除$(BUILD_DIR) $(BIN_DIR) $(BUILD_LOG_DIR)三個目錄的用意是十分明確。暫時不知道爲什麼執行make target/linux/clean。
dirclean是刪除所有編譯過程產生的目錄和文件的目標,執行dirclean目標依賴於clean,因此將執行clean目標所執行的命令,然後刪除$(STAGING_DIR) $(STAGING_DIR_HOST) $(STAGING_DIR_TOOLCHAIN) $(TOOLCHAIN_DIR) $(BUILD_DIR_HOST) $(BUILD_DIR_TOOLCHAIN)目錄,以及刪除$(TMP_DIR)目錄。上述目錄的變量均在rules.mk定義。好像刪除staging_dir目錄就意味着刪除staging_dir目錄下的所有子目錄,不知道爲什麼要強調刪除$(STAGING_DIR_HOST)
$(STAGING_DIR_TOOLCHAIN) $(TOOLCHAIN_DIR)目錄。同樣刪除builde_dir目錄就意味着刪除builde_dir目錄下的所有子目錄,不知道爲什麼要強調刪除$(BUILD_DIR_TOOLCHAIN)目錄。
tmp/.prereq_packages目標是對所需軟件包的預處理。目標依賴於.config,即執行make menuconfig後將會進行一次所需軟件包的預處理。不知什麼原因在編譯前刪除tmp目錄,執行時無法建立tmp/.prereq_packages文件。
prereq應該是預請求目標,在OpenWrt執行Makefile時好像都要先執行prereq目標。
prepare應該是準備目標,是world依賴的一個僞目標。依賴於文件.config和$(tools/stamp-install) $(toolchain/stamp-install)目標。
world就是編譯的目標。依賴於prepare爲目標和前面提到的變量命名目標。採用取消隱含規則方式執行package/index目標。package/index目標在package/Makefile的92行定義。
package/symlinks和package/symlinks-install是更新或安裝軟件包來源的目標,使用$(SCRIPT_DIR)/feeds腳本文件完成。
package/symlinks-clean是清除軟件包來源的目標,也是使用$(SCRIPT_DIR)/feeds腳本文件完成。
最後使用僞目標.PHONY說明clean dirclean prereq prepare world package/symlinks package/symlinks-install package/symlinks-clean屬於僞目標。通過僞目標說明可以知道可以執行的目標。
參考資料
Openwrt官網http://wiki.openwrt.org/start
軟件包相關:https://dev.openwrt.org/browser/branches/packages_10.03.2?order=name
關於包的依賴問題:http://wiki.openwrt.org/doc/devel/dependencies
問題記錄:
(1)【close】在移植exfat驅動到openwrt時遇到如下問題:
nickli@NewRouterDev:qsdk$ make package/exfat/{clean,prepare,compile,install} V=99
Collecting package info: done
make[1]: Entering directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk’
make[2]: Entering directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/package/exfat’
Makefile:56: warning: overriding commands for target `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat’
Makefile:56: warning: ignoring old commands for target `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat’
Makefile:56: warning: overriding commands for target `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat’
Makefile:56: warning: ignoring old commands for target `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat’
rm -f /home/nickli/puma_sdk/qualcomm_sdk/qsdk/staging_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/stamp/.exfat _installed
bash: line 2: [: packages/exfat: binary operator expected
rm -f /home/nickli/puma_sdk/qualcomm_sdk/qsdk/bin/ipq806x/packages/exfat_*
rm -f /home/nickli/puma_sdk/qualcomm_sdk/qsdk/staging_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/packages/exfat .list /home/nickli/puma_sdk/qualcomm_sdk/qsdk/staging_dir/host/packages/exfat .list
rm -rf /home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat
make[2]: Leaving directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/package/exfat’
make[1]: Leaving directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk’
make[1]: Entering directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk’
make[2]: Entering directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/package/exfat’
Makefile:56: *** target file `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/exfat’ has both : and :: entries. Stop.
make[2]: Leaving directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk/package/exfat’
make[1]: *** [package/exfat/prepare] Error 2
make[1]: Leaving directory `/home/nickli/puma_sdk/qualcomm_sdk/qsdk’
make: *** [package/exfat/prepare] Error 2
問題原因:在Makefile中版本號變量後面多了個空格。
PKG_NAME:=exfat
PKG_VERSION:=0.9.5$space_char
PKG_RELEASE:=1
解決方法:將版本號後面的空格去掉
結果:問題解決。
參考:https://forum.openwrt.org/viewtopic.php?id=20353
(2)【close】SDK修改目錄名稱後無法繼續編譯問題解決
問題現象:check 了一份openwrt的SDK,執行編譯之後又重新修改了該SDK的名稱,導致後續再編譯時無法編譯。
原因:有些編譯器是動態編譯生成的,但是make clean之後編譯器不會重新編譯,其路徑與絕對路徑有關,修改名稱後無法找到相關編譯器。
解決方法:進入SDK根目錄執行make distclean,將所有編譯記錄完整清除即可。
結果:問題解決(應該有更加優化的方法,即刪除其中交叉編譯鏈相關內容,後續有時間再解決)
(3)【close】編譯單個模塊軟件時,提示缺少C庫
編譯單個模塊軟件時,在最後一步執行打包操作時提示缺少libc.so.6而無法打包,信息如下:
Package skysoft_net6scan is missing dependencies for the following libraries:
libc.so.6
原因:編譯單個模塊時,缺少目標平臺相同的libc.so.6文件。
解決方法:
參考:http://my.oschina.net/hevakelcj/blog/411944
真正解決問題的參考:http://www.cnblogs.com/liushannet/p/3895092.html
查看依賴文件:
nickli@NewRouterDev:qsdk$ readelf -d build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/skysoft_net6scan/net6scan
Dynamic section at offset 0x2e28 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400c28
0x000000000000000d (FINI) 0x401ad0
0x0000000000000019 (INIT_ARRAY) 0x602e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x602e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
實際解決方法:
先檢查系統裏面有沒有這個庫存在,如果沒有,安裝 (在/lib /lib64 等目錄找)
之後添加庫openwrt的編譯環境
修改 libc.provides 平臺不同,可能目錄不同,find ./stagging_dir -name libc.provides 下
一般在 stagging_dir/target-mipsel_r2_uClibc-0.9.33.2/pkginfo/libc.provides
底部添加
libc.so.6
或其他需要的庫,編譯時候會把這個so轉爲openwrt平臺的庫
相關命令:
1332 find /lib* -name “libc.so.6”
1333 find ./staging_dir/ -name libc.provides
1334 vim ./staging_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/pkginfo/libc.provides
在該文件末尾添加libc.so.6後保存並退出,之後執行編譯:
1335 make package/skysoft_net6scan/{clean,prepare,compile,install} V=99
能正常打包了!
結果:問題解決。
(4)【close】編譯的模塊無法在opentwr系統中運行
root@rt4230w:~# /sbin/net6scan
/sbin/net6scan: line 1: syntax error: unexpected “(”
問題原因:在編譯的模塊Makefile中將編譯器寫死成了gcc,而實際上目標平臺爲arm需要使用交叉編譯器。
解決方法:修改源碼內的makefile,將gcc 改成$(CC),結果如下:
CFLAGS += -Wall -D_GNU_SOURCE
net6scan : main.o func.o $(CC) $(CFLAGS) -o net6scan main.o func.o main.o : main.c $(CC) $(CFLAGS) -c main.c func.o : func.c $(CC) $(CFLAGS) -c func.c clean: rm -rf ip6scan *.o |
結果:問題解決
(5)【close】在原odhcp6c源碼中增加了log.c log.h文件後,編譯時出現如下問題:
make[3]: Entering directory `/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13′
make[4]: Entering directory `/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13′
make[5]: Entering directory `/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13′
make[5]: Leaving directory `/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13′
make[5]: Entering directory `/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13′
[ 16%] Building C object CMakeFiles/odhcp6c.dir/src/odhcp6c.c.o
[ 33%] Building C object CMakeFiles/odhcp6c.dir/src/log.c.o
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c: In function ‘_get_time’:
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c:117:9: error: missing initializer [-Werror=missing-field-initializers]
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c:117:9: error: (near initialization for ‘tv.tv_usec’) [-Werror=missing-field-initializers]
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c:118:9: error: missing initializer [-Werror=missing-field-initializers]
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c:118:9: error: (near initialization for ‘time_value.tm_min’) [-Werror=missing-field-initializers]
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c: In function ‘debug_log_print’:
/home/nickli/cascade_sdk_new_platform/qualcomm_sdk/qsdk/build_dir/target-arm_v7-a_uClibc-0.9.33.2_eabi/odhcp6c-2015-07-13/src/log.c:140:51: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
cc1: all warnings being treated as errors
分析:error: missing initializer [-Werror=missing-field-initializers],應該是編譯程序時將警告當錯誤了,提示這裏需要初始化,而實際上一時間沒有看出哪兒來執行正確的初始化,所以從取消掉這種警告機制着手解決。
解決辦法:修改編譯目錄內的(build_dirvim) CMakeLists.txt文件,將-Werror改爲-Wno-missing-field-initializers,如下:
8 #add_definitions(-D_GNU_SOURCE -Wall -Werror -Wextra -pedantic)
9 add_definitions(-D_GNU_SOURCE -Wall –Wno-missing-field-initializers -Wextra -pedantic)
結果:問題解決。
小結:
(1)-Werror 選線會將警告升級爲錯誤來報告
(2)odhcp6c源碼編譯採用了Cmake而沒有使用我們一般使用的方式——在src目錄內寫一份Makefile文件來編譯文件,所以其規則文件都在和src目錄同級的CMakeLists.txt文件中。可以學習瞭解一下CMake機制。