http://www.pokylinux.org/
poky是一个distribution,采用 openembedded 构建,包含一个基于GNOME的embedded linux software stack。poky封装了openembedded,选择了openembedded的核心,外加更多的脚本,作为开发者,如果想学习openembedded这一强大的工具,建议从poky开始。即便如此,poky的学习曲线仍然是陡峭的(虽然很好用)。
poky的思想大致是:开发者提供元数据(食谱或菜谱),每份食谱都是描述某个软件的‘清单’,比如源代码从哪里下载,需要哪些补丁文件,可能需要的特殊编译链接选项,打包时的需要的特殊配置,依赖其他什么软件等信息。食谱是由一种标记性的语言所写,由元工具(bitbake)解析、执行。元工具则抽象了构建一个软件过程中的fetch,unpack,patch,configure,compile,package等任务;同时,元工具还负责按不同软件之间的依赖关系有序的执行。
我最早接触的poky其实已经不是当前最新的poky了。如果用git来取得poky的源码仓库的话,那其实是名叫pinky-3.1的分支。可惜这个分支从09年左右就停止维护了,现在的poky(如master分支)叫"Yocto",这应该已经是OpenHand被intel收购之后的事了。成为Yocto之后,poky已不再简单地定位为一个distribution,而是一个tool-maker,或者是“ 协作开发软件,增加了更多的模版、脚本、工具等,来帮助开发者定制嵌入式系统 ”,相对于增加了更多layers的Yocto,早期的poky(pinky)显得更“单纯”,简单的说,就是更少的文件(meta bb file,meta class file等)。对于一个初学者来说,这意味着遇到问题后更容易解决(不管你的native system同poky要求的多么匹配,问题仍然是难免的)。当然,缺点就是不再被社区支持后,各软件包的版本均停留在了pinky-3.1发布的那一刻,想升级,想使用更新的软件的话,要么就老老实实切换到poky(Yocto),与社区同步;要么就Do It Yourself。相对与前者,后者这种行为无疑是‘重新发明轮子’,但重新发明轮子有时也不是一件坏事,按候捷的话说(非原话)“学往深处学,用往易处用”。用我的话来说,就是“想学会游泳的方法就是下水”。下面,我就从零开始自己动手添加一个食谱,这个食谱将产生一个较新的linux内核包(2.6.37),取代poky(pinky)中还较早版本(2.6.24)的内核,以此来学习poky,bitbake中的一些概念。
一步步创建inux kernel的食谱
创建linux_2.6.37.bb 食谱文件,食谱的文件名很重要,bitbake会从文件名中提取信息来设置一些环境变量。这些环境变量决定比如在哪里找到源代码,最后所生成的包的名字等等。
首先,我要将kernel源代吗从网上下下来。bitbake通过SRC_URI 变量知道到哪里去下载源码的,linux 2.6.37的source code从git repository取得,而poky(pinky)时期git冒似还未流行,kernel源代码仍然是通过ftp、http方式下载的tar.bz压缩包,所以我无法直接copy linux_2.6.24.bb 中的SRC_URI 设置。
于是,第一步,在linux_2.6.37.bb 中添加:
SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git;protocol=git"
protocol告诉bitbake用git作为source fetcher。
因为我已决定了用2.6.37的kernel release版本,所以这里要用‘tag’标记来指定它。通过git tag知道在master分支下对应的tag名为‘v2.6.37’,对于git来说,这里的tag也可以设置成一个任何一个git commit。
SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git;protocol=git;tag=v2.6.37“
再加上linux.inc 头文件,一个很简单的食谱文件就完成了。linux.inc 是提炼出来的可适用于所有不同linux kernel食谱文件的“公共文件”,同对应的class文件一样,目的就在于code reuse,减少冗余。比如“inherit kernel”这样的指令可以在linux.inc 中看到。
require linux.inc SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git;protocol=git;tag=v2.6.37“
我们可以用bitbake -c fetch linux 来验证以上内容是否正确。耐心的等待一段不短的时间,bitbake就会调用git clone从SRC_URI 的地址在我们的本地克隆我们想要的仓库。fetch下来的源码放到什么地方的?因为是用git,所以源码会被下载到变量GITDIR 所指定的目录里。该变量可以在bitbake.conf 中找到:
DL_DIR ?= "${TMPDIR}/downloads" GITDIR = "${DL_DIR}/git"
bitbake.conf 设置了bitbake所需要的环境变量的默认值。根据poky的需要,一些变量也被local.conf 进行了重置。
# Where to cache the files Poky downloads DL_DIR ?= "${OEROOT}/sources"
在poky根目录的sources目录下,可以看到fetch linux的结果:
bitbake的git fetcher已经将其指定的tag版本checkout并压缩成了tar.gz文件。文件名则是由SRC_URI 变化而来。
执行bitbake -c unpack linux ,将其解压到由变量WORKDIR 定义的工作目录下,该变量也定义在bitbake.conf 中:
PN = "${@bb.parse.BBHandler.vars_from_file(bb.data.getVar('FILE',d),d)[0] or 'defaultpkgname'}" PV = "${@bb.parse.BBHandler.vars_from_file(bb.data.getVar('FILE',d),d)[1] or '1.0'}" PR = "${@bb.parse.BBHandler.vars_from_file(bb.data.getVar('FILE',d),d)[2] or 'r0'}" PF = "${PN}-${EXTENDPE}${PV}-${PR}" EXTENDPE = "${@['','${PE/x7d_'][bb.data.getVar('PE',d,1) > 0]}" TMPDIR = "${TOPDIR}/tmp" WORKDIR = "${TMPDIR}/work/${PF}"
在${WORKING} 目录下,可以看到unpack task执行后产生的名叫git的目录,该目录里面就是解压后的2.6.37的kernel code。如果此时执行bitbake -c build linux 会失败,因为bitbake找不到解压后的source code在哪里。原因是bitbake通过环境变量S 来知道源码目录在什么地方的。在bitbake.conf 中:
P = "${PN}-${PV}" S = "${WORKDIR}/${P}"
原来,如果对应的bb文件名叫“linux_2.6.37.bb ”,那么默认bitbake会到${WORKDIR}/linux-2.6.37 下面去执行build动作。但是因为源代码是被git fetcher取下来的,source code被unpack到了${WORKDIR}/git 下,于是bitbake报错找不到文件。所以,要在linux_2.6.37.bb 文件中重置S 变量,显示地告诉bitbake源代码放在哪里:
require linux.inc SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git;protocol=git;tag=v2.6.37" S = "${WORKDIR}/git"
poky将build kernel的策略中的不变的部分抽象出来成为类(比如总是通过make oldconfig来配置内核而非make menuconfig等决策),在kernel.bbclass 中可以看到:
kernel_do_configure() { yes '' | oe_runmake oldconfig }
oldconfig需要源文件目录下有.config文件作为配置输入,刚clone下来的kernel source是没有.config的,于是,poky提供头文件为大多数不同版本的kernel‘食谱’文件来封装这个拷贝.config文件的过程,在linux.inc 中,有:
do_configure_prepend() { echo "" > ${S}/.config sed -e '/CONFIG_AEABI/d' / -e '/CONFIG_OABI_COMPAT=/d' / -e '/CONFIG_CMDLINE=/d' / -e '/CONFIG_LOGO=/d' / -e '/CONFIG_LOGO_LINUX_CLUT224=/d' / < '${WORKDIR}/defconfig' >>'${S}/.config' yes '' | oe_runmake oldconfig }
从上可知, linux.inc 希望在${WORKDIR} 目录下能找到defconfig 文件,该文件就是linux的.config 文件模板,在做一些定制化的修改后,再拷贝到${S} 目录下重命名为.config 文件。注意到该方法的名为“do_configure_prepend ”,意思是重载(overload) linux.bbclass 类中定义的的默认configure方法,这正式是bitbake这种‘方言’的一个强大特性。这种类似面向对象的方法可以积累前人的工作并且很方便的对其进行扩展。
在之前执行过的unpack 任务中,bitbake会把在SRC_URI 变量中定义的所有文件都拷贝到${WORKDIR} 目录下(这其实也是poky的base.bbclass脚本指定的),于是,我只要在SRC_URI 里添加我希望make oldconfig时要用到的defconfig 文件即可:
require linux.inc PR="r2" SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git;protocol=git;tag=v2.6.37 / file://defconfig " S = "${WORKDIR}/git"
上面还增加了对变量PR 的重置(默认为r0),该环境变量意思是指定一个软件包的修订版本。
SRC_URI 中的“file://defconfig”地址表示defconfig文件应该放在同当前被执行的bb文件同级的一个名叫linux-2.6.37的目录中(因为正在被执行的bb文件名叫linux_2.6.37.bb),bitbake正是根据bb文件名来决定file协议的目录名应该叫什么。所以,我找了一个在x86系统上可以工作的.config文件(其实就是来自于自己的ubuntu 10.10),然后拷贝换名到linux-2.6.37目录下,毕竟不同的kernel配置差不多就行了。
截个图来说明这些目录:
右边的目录是我们编辑linux kernel菜谱源文件的目录,bitbake也是从该目录取得菜谱文件。
左边的目录是bitbake以linux kernel菜谱文件为指导而进行构建、打包的工作目录(${WORKDIR} )。
这样,一个最简单的linux kernel食谱文件就准备好了,再执行bitbake linux命令 ,经过漫长的时间后(依赖于处理器的速度、内存等),bitbake会根据linux.inc以及kernel.bbclass文件中定义的脚本来配置、编译内核、编译模块、安装、打包等动作。因为我选择的package打包策略为deb(poky还支持rpm,ipkg),bitbake命令结束后,在${DEPLOY_DIR_DEB} 目录 下面就可以看到生成的linux kernel的package文件,同时还有很多kernel module package文件(每个module都属于一个deb包);这些指导bitbake的环境变量同样定义在bitbake.conf 文件中:
DEPLOY_DIR = "${TMPDIR}/deploy" DEPLOY_DIR_TAR = "${DEPLOY_DIR}/tar" DEPLOY_DIR_IPK = "${DEPLOY_DIR}/ipk" DEPLOY_DIR_RPM = "${DEPLOY_DIR}/rpm" DEPLOY_DIR_DEB = "${DEPLOY_DIR}/deb" DEPLOY_DIR_IMAGE = "${DEPLOY_DIR}/images" DEPLOY_DIR_TOOLS = "${DEPLOY_DIR}/tools"
生成的.deb文件:
以上过程从一个菜谱文件到构建完一个包结束。这离生成一个根文件系统、发布内核、得到一个可以运行的系统还有一段距离。因为目前还没有处理依赖关系,升级了内核后,对应的一些软件组件也要跟着变,比如libc的头文件等。解决这些问题都需要丰富的领域知识。当然,已有的poky、openembedded菜谱文件很有重用价值。
借助poky或者openembedded,可以帮助我们节省了很多dirty works,让我们不用把大量时间浪费在敲键盘上面。我认为即how-to的部分。但what-to和why-to,还是需要我们开发者自己思考的。