openwrt作為一個(gè)基于linux開(kāi)發(fā)的比較完善的嵌入式系統(tǒng),可以快速移植到各種平臺(tái)上。初次下載開(kāi)源代碼后,簡(jiǎn)單瀏覽后很是詫異,居然沒(méi)看到uboot和kernel部分的代碼,甚至沒(méi)看到任何模塊的代碼,最多只是些patch和配置文件。
按照文檔編譯后,發(fā)現(xiàn)多了些工程目錄,進(jìn)而發(fā)現(xiàn)了很多源碼,猜測(cè)到大概是Makefile或feed腳本在編譯時(shí)在線下載的代碼。為了后來(lái)者,初次入門openwrt少踩坑,完成此章,內(nèi)容多為我入門的困惑與解答。
1. uboot和kernel在哪里?
事實(shí)上,拿到marvell release代碼后會(huì)看到:
$ tree -L 1 marvell/
marvell/
├── fastpath
├── fota
├── linux
├── lte-telephony
├── obm
├── services
├── swd
├── uboot
└── webui
這些明顯是marvell自己定制過(guò)的一些代碼包,針對(duì)這個(gè)平臺(tái)算是找到了正確的代碼位置,但我們并不了解這個(gè)過(guò)程,并且開(kāi)源代碼部分是沒(méi)有這種結(jié)構(gòu)的,所以我們要繼續(xù)探究下去,提出問(wèn)題:
- 官方release的openwrt沒(méi)有定制的情況下uboot和kernel在哪?
- marvell這種定制是如何實(shí)現(xiàn)的?
在目錄結(jié)構(gòu)和功能不了解的情況下會(huì)感覺(jué)無(wú)從下手而四處亂摸,通過(guò)上一章了解了目錄結(jié)構(gòu),很自然的就會(huì)想到package目錄,進(jìn)去看看:
$ cd package
$ tree -L 1 boot
boot/
├── uboot-mvebu
├── uboot-mxs
├── uboot-omap
├── uboot-oxnas
├── ...
└── yamonenv
最終發(fā)現(xiàn),package/boot/uboot-mmp/ :
$ cat package/boot/uboot-mmp/Makefile
include $(TOPDIR)/rules.mk
PKG_NAME:=u-boot
PKG_VERSION:=2014
PKG_RELEASE:=1
USE_SOURCE_DIR:=$(MRVLDIR)/uboot
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_NAME)-$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
這里面看到了'MRVLDIR',查詢后發(fā)現(xiàn)MRVLDIR:=$(TOPDIR)/marvell,
$ ls -l build_dir/target-arm_cortex-a7+neon-vfpv4_uClibc-1.0.25_eabi/u-boot-nezha3_dkb/
total 0
lrwxrwxrwx 1 tjd tjd 56 May 15 15:56 u-boot-2014 -> /home/tjd/datadisk/marvell/OpenWrt/openwrt/marvell/uboot
確認(rèn)無(wú)疑了!!
package/boot/uboot-mmp/就是marvell的uboot定義的位置,變量USE_SOURCE_DIR指定了uboot使用的源碼。
知道了marvell定制uboot源碼的方法,再看一下官方release版本的uboot,已Beaglebone black板項(xiàng)目為例:
$ cat package/boot/uboot-omap/Makefile
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_VERSION:=2017.01
PKG_RELEASE:=2
PKG_HASH:=6c425175f93a4bcf2ec9faf5658ef279633dbd7856a293d95bd1ff516528ecf2
include $(INCLUDE_DIR)/u-boot.mk
include $(INCLUDE_DIR)/package.mk
define U-Boot/Default
BUILD_TARGET:=omap
UBOOT_IMAGE:=u-boot.img MLO
UENV:=default
endef
define U-Boot/omap4_panda
NAME:=Pandaboard
BUILD_DEVICES:=omap4-panda
endef
define U-Boot/am335x_boneblack
NAME:=TI AM335x BeagleBone Black
BUILD_DEVICES:=am335x-boneblack
endef
...
很遺憾,這里沒(méi)有USE_SOURCE_DIR指定源碼位置,只是指定了使用的uboot的版本號(hào),目錄中也沒(méi)有源碼:
package/boot/uboot-omap/
├── files
│ └── uEnv-default.txt
├── Makefile
└── patches
├── 101-disable-thumb-omap3.patch
├── 102-minify-spl.patch
├── 103-disable-fat-write-spl.patch
├── 104-omap3-overo-enable-thumb.patch
└── 105-serial-ns16550-bugfix-ns16550-fifo-not-enabled.patch
2 directories, 7 files
這里保存了針對(duì)bbb的uboot相關(guān)patch,聯(lián)想到與硬件相關(guān)的內(nèi)容是以patch存在的,u-boot應(yīng)該是官方發(fā)布的原版代碼,上一章提到了dl目錄保存了從網(wǎng)絡(luò)上下載的代碼包,編譯時(shí)會(huì)解壓源碼包到build_dir中,現(xiàn)在看一下:
$ ls build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/
api cmd configs drivers fs Kconfig MAINTAINERS MLO.byteswap README spl tools u-boot.cfg u-boot.lds u-boot.srec
arch common disk dts include lib Makefile net scripts System.map u-boot u-boot.cfg.configs u-boot.map u-boot.sym
board config.mk doc examples Kbuild Licenses MLO post snapshot.commit test u-boot.bin u-boot.img u-boot-nodtb.bin
前面看到package/boot/uboot-omap/中有patch,是不是已經(jīng)合入了呢? 對(duì)比看看:
$ cat package/boot/uboot-omap/patches/101-disable-thumb-omap3.patch
Index: u-boot-2017.01/include/configs/ti_omap3_common.h
===================================================================
--- u-boot-2017.01.orig/include/configs/ti_omap3_common.h
+++ u-boot-2017.01/include/configs/ti_omap3_common.h
@@ -80,4 +80,9 @@
/* Now bring in the rest of the common code. */
#include <configs/ti_armv7_omap.h>
+/* beagleboard doesnt boot with thumb */
+#ifdef CONFIG_SYS_THUMB_BUILD
+#undef CONFIG_SYS_THUMB_BUILD
+#endif
+
#endif /* __CONFIG_TI_OMAP3_COMMON_H__ */
$ diff dl/u-boot-2017.01_unpack/include/configs/ti_omap3_common.h build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/include/configs/ti_omap3_common.h
82a83,87
> /* beagleboard doesnt boot with thumb */
> #ifdef CONFIG_SYS_THUMB_BUILD
> #undef CONFIG_SYS_THUMB_BUILD
> #endif
看來(lái)patch是已經(jīng)打上去了。
- 官方的uboot先解壓到
build_dir中,再打上package/boot/uboot-xxx/patches中的patch,再編譯。- marvell通過(guò)變量
USE_SOURCE_DIR指定本地代碼庫(kù),連接到build_dir中,然后編譯。
再看看kernel應(yīng)該是類似的:區(qū)別是kernel不在package中而是target中,引用外部庫(kù)的變量不是USE_SOURCE_DIR而是CONFIG_EXTERNAL_KERNEL_TREE
$ cat target/linux/mmp/Makefile
include $(TOPDIR)/rules.mk
ARCH:=arm
BOARD:=mmp
BOARDNAME:=Marvell MMP
SUBTARGETS=pxa1826
FEATURES:=squashfs jffs2_nand nand ubifs
LINUX_VERSION:=3.10.33
CONFIG_EXTERNAL_KERNEL_TREE:=$(MRVLDIR)/linux
define Target/Description
Build firmware images for Marvell MMP SoC
endef
include $(INCLUDE_DIR)/target.mk
2. 如何編譯自己的代碼包?
用戶態(tài)模塊
新建getevent包最終如下:
$ tree package/xxx/
xxx/
└── getevent
├── Makefile
└── src
├── getevent.c
├── getevent.h
└── Makefile
配置頂層Makefile
$ vi package/xxx/getevent/Makefile
include $(TOPDIR)/rules.mk
PKG_NAME:=getevent
PKG_RELEASE:=1
# 定義編譯軟件包的路徑
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
# 軟件包描述,可以在make menuconfig中體現(xiàn)出來(lái)
define Package/getevent
SECTION:=Xxxx
CATEGORY:=xxx ddd
TITLE:=linux inputevent test tool
endef
# 本包安裝的配置文件,一行一個(gè)
define Package/dnsmasq/conffiles
#/etc/config/dhcp
#/etc/dnsmasq.conf
endef
# make package/xxx/getevent/prepare時(shí)被執(zhí)行
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
# 軟件包的介紹信息
define Package/helloworld/description
helloworld,first self-made.
endef
# 對(duì)執(zhí)行./configure時(shí)的特殊處理
define Build/Configure
endef
# 執(zhí)行make或make package/xxx/getevent/compile時(shí)處理
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS) -Wall" \
LDFLAGS="$(TARGET_LDFLAGS)"
endef
# make install時(shí)處理
# INSTALL_DIR 決定了通過(guò)opkg安裝ipk的目標(biāo)目錄
define Package/getevent/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/getevent $(1)/usr/sbin/
endef
$(eval $(call BuildPackage,getevent))
編輯src目錄下的Makefile
CC = gcc
CFLAGS = -Wall
OBJS = getevent.o
all: getevent
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
getevent: $(OBJS)
$(CC) -o $@ $(OBJS)
clean:
rm -f getevent *.o
注意,這里一個(gè)代碼包中有兩個(gè)Makefile
下一步時(shí)make menuconfig,選擇CONFIG_PACKAGE_getevent=m或CONFIG_PACKAGE_getevent=y否則編譯不出東西,會(huì)報(bào):
WARNING: skipping getevent -- package not selected
config配置成m時(shí)會(huì)編譯成ipk文件,可以按需安裝;配置成y時(shí),是buildin,內(nèi)置到release軟件中,無(wú)需安裝。
內(nèi)核模塊
最終包如下:
$ tree khello/
khello/
├── Makefile
└── src
├── khello.c
└── Makefile
1 directory, 3 files
配置頂層Makefile
$ cat khello/Makefile
#
# Copyright (C) 2008 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=khello
PKG_RELEASE:=2
include $(INCLUDE_DIR)/package.mk
define KernelPackage/khello
# 指定menuconfig的路徑
SUBMENU:=Other modules
TITLE:=kernel Helloworld test
# 依賴的模塊
DEPENDS:=
# 目標(biāo)文件
FILES:=$(PKG_BUILD_DIR)/khello.ko
KCONFIG:=
endef
define KernelPackage/khello/description
Kernel module for helloword example.
endef
EXTRA_KCONFIG:= \
CONFIG_HELLO=m
EXTRA_CFLAGS:= \
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
MAKE_OPTS:= \
ARCH="$(LINUX_KARCH)" \
CROSS_COMPILE="$(TARGET_CROSS)" \
SUBDIRS="$(PKG_BUILD_DIR)" \
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
$(EXTRA_KCONFIG)
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \
$(MAKE_OPTS) \
modules
endef
$(eval $(call KernelPackage,khello))
編輯src目錄下的Makefile
$ cat package/revoview/khello/src/Makefile
obj-$(CONFIG_HELLO) += khello.o
編輯khello.c
$ cat package/revoview/khello/src/khello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk("Hello Kernel\n");
return 0;
}
module_init(hello_init);
static void __exit hell_exit(void)
{
printk("Bye Kernel");
}
module_exit(hell_exit);
MODULE_LICENSE("GPL");
3. 模塊如何單獨(dú)編譯?
首先有個(gè)頂層Makefile,如上面的兩個(gè)案例
如何模塊需要區(qū)別于默認(rèn)編譯選項(xiàng),Makefile中可以包含下面幾個(gè)重要元素:
-
Build/Preparemake xxx/xxx/prepare在編譯前解壓或在build_dir/準(zhǔn)備代碼時(shí)調(diào)用,如:
define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef -
Build/Configuremake xxx/xxx/configure在編譯前執(zhí)行./configure 做特殊處理,如:
define Build/Configure $(CP) $(SCRIPT_DIR)/config.guess $(SCRIPT_DIR)/config.sub $(PKG_BUILD_DIR)/support/ $(call Build/Configure/Default, \ --enable-shared \ --enable-static \ --without-curses \ ) endef也可以這樣用:
define Build/Configure $(call Build/Configure/Default) $(if $(CONFIG_PCAP_HAS_USB),,$(SED) '/^#define PCAP_SUPPORT_USB/D' $(PKG_BUILD_DIR)/config.h) $(if $(CONFIG_PCAP_HAS_USB),,$(SED) 's/pcap-usb-linux.c *//' $(PKG_BUILD_DIR)/Makefile) $(if $(CONFIG_PCAP_HAS_BT),,$(SED) '/^#define PCAP_SUPPORT_BT/D' $(PKG_BUILD_DIR)/config.h) $(if $(CONFIG_PCAP_HAS_BT),,$(SED) 's/pcap-bt-linux.c *//' $(PKG_BUILD_DIR)/Makefile) endef需要注意的是:
$(call Build/Configure/Default)是必需的。 -
Build/Compilemake xxx/xxx/compilemake時(shí)執(zhí)行,可以增加編譯參數(shù)
define Build/Compile $(TARGET_CONFIGURE_OPTS) \ CFLAGS="$(TARGET_CFLAGS) -I$(STAGING_DIR)/usr/include -I$(PKG_BUILD_DIR)" \ LDFLAGS="$(TARGET_LDFLAGS)" \ $(MAKE) -C $(PKG_BUILD_DIR) endef -
Build/Install和Build/InstallDevmake xxx/xxx/install在編譯完成,安裝時(shí)執(zhí)行的特殊處理,如:
define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include $(CP) $(PKG_INSTALL_DIR)/usr/include/lua{,lib,conf}.h $(1)/usr/include/ $(CP) $(PKG_INSTALL_DIR)/usr/include/lauxlib.h $(1)/usr/include/ $(CP) $(PKG_INSTALL_DIR)/usr/include/lnum_config.h $(1)/usr/include/ $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/liblua.{a,so*} $(1)/usr/lib/ ln -sf liblua.so.$(PKG_VERSION) $(1)/usr/lib/liblualib.so $(INSTALL_DIR) $(1)/usr/lib/pkgconfig $(CP) $(PKG_BUILD_DIR)/etc/lua.pc $(1)/usr/lib/pkgconfig/ endef
4. ipk是如何編譯出來(lái)的
openwrt的編譯要區(qū)分是不是buildin, 對(duì)于buildin是指,選定的包編譯時(shí)就就安裝到rootfs中,否則,就是編譯成ipk文件,通過(guò)opkg命令安裝。
區(qū)分是否buildin通過(guò)menuconfig配置,最終體現(xiàn)在.config中。
< CONFIG_PACKAGE_getevent=y
---
> CONFIG_PACKAGE_getevent=m
`=m` 代表編譯成ipk, 目標(biāo):`bin/pxa1826/packages/base/getevent_1_pxa1826.ipk`
`=y` 代表buildin,目標(biāo):`staging_dir/target-arm_cortex-a7+neon-vfpv4_uClibc-1.0.25_eabi/root-mmp/usr/sbin/getevent`
5. Patches是如何自動(dòng)打上去的?
通過(guò)make -n提取的make V=s package/utils/busybox/{clean,prepare}的過(guò)程如下
. /source/marvell/include/shell.sh; bzcat /source/marvell/dl/u-boot-2017.01.tar.bz2 | tar -C /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/.. -xf -
[ ! -d ./src/ ] || cp -fpR ./src/. /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01
if [ -d "./patches" ] && [ "$(ls ./patches | wc -l)" -gt 0 ]; then export PATCH="patch"; if [ -s "./patches/series" ]; then sed -e s,\\\#.*,, ./patches/series | grep -E \[a-zA-Z0-9\] | xargs -n1 /source/marvell/scripts/patch-kernel.sh "/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01" "./patches"; else /source/marvell/scripts/patch-kernel.sh "/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01" "./patches"; fi; fi
- 解壓
bzcat /source/marvell/dl/u-boot-2017.01.tar.bz2 | tar -C /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/.. -xf -
- 打patch
/source/marvell/scripts/patch-kernel.sh "/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01“ "./patches"
可以在package/boot/uboot-omap目錄下運(yùn)行patch-kernel.sh測(cè)試。
6. 如何創(chuàng)建自己的patch?
-
安裝quilt
sudo apt-get install quilt -
準(zhǔn)備quilt的配置文件
cat > ~/.quiltrc <<EOF QUILT_DIFF_ARGS="--no-timestamps --no-index -p ab --color=auto" QUILT_REFRESH_ARGS="--no-timestamps --no-index -p ab" QUILT_SERIES_ARGS="--color=auto" QUILT_PATCH_OPTS="--unified" QUILT_DIFF_OPTS="-p" EDITOR="nano" EOF 創(chuàng)建第一個(gè)patch
$ make package/utils/fbtest/{clean,prepare} V=s QUILT=1
$ cd build_dir/target-*/fbtest
$ quilt new patches/0001-test.patch
$ quilt edit fbtest.c
查看修改的內(nèi)容
$ quilt diff
刷新0001-test.patch
$ quilt refresh
把patch挪到package目錄
$ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/
編譯看看patch是否生效
$ make package/utils/fbtest/{clean,prepare} V=s
- 增加 patch
和上面步驟一樣,只要注意patch的名字序號(hào)遞增。
$ make package/utils/fbtest/{clean,prepare} V=s QUILT=1
$ cd build_dir/target-*/fbtest
$ quilt new patches/0002-test2.patch
$ quilt edit Makefile
查看修改的內(nèi)容
$ quilt diff
刷新0002-test.patch
$ quilt refresh
把patch挪到package目錄
$ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/
編譯看看patch是否生效
$ make package/utils/fbtest/{clean,prepare} V=s
-
修改patch
$ make package/utils/fbtest/{clean,prepare} V=s QUILT=1 $ cd build_dir/target-*/fbtest $ quilt push patches/0002-test2.patch $ quilt edit Makefile 查看修改的內(nèi)容 $ quilt diff 刷新0002-test.patch $ quilt refresh 把patch挪到package目錄 $ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/ 編譯看看patch是否生效 $ make package/utils/fbtest/{clean,prepare} V=s
生成patch特別要注意的是:
- 各個(gè)patch是不能有互相依賴關(guān)系的
- 如果有依賴關(guān)系需要做特殊處理,比如
0002依賴0001,需要將0001先導(dǎo)入(但不能用quilt導(dǎo)入,可以是patch -p1之類的),然后再用quilt生成0002- 最后一步
cp patch時(shí)不要將series文件也拷貝到package包中。
7. rootfs源頭在哪?
有兩個(gè)位置:
- package/base-file
- target/linux/xxx/base-files