平台概览
Neo 1973 由大众电脑公司(First International Computer)生产,常常被称为 “OpenMoko”。实际上,OpenMoko 是一个 Linux 平台和一些相关应用程序,而非一款手机。但是,它是一款用于手机的 Linux 应用程序,在撰写本文时,Neo 1973 是惟一一款可完全支持该平台的手机。您也可以构建一个模拟环境进行软件开发,我将在本教程中介绍到,但是这种模拟环境不能很好地连接到手机网络。
OpenMoko 使用 OpenEmbedded 构建,可实现交叉编译(参见 参考资料 中有关 OpenEmbedded 信息的链接)。不论您是想构建具有调试功能的本地程序,还是想构建经过优化的目标二进制程序,OpenEmbedded 都提供了工具和支持来构建和发布功能。因此,OpenMoko 开发需要了解如何使用 OpenEmbedded。
使用新环境的一大难点就是进行全面配置、正确准备文件,等等。进入 MokoMakefile(参见 参考资料 中的有关链接),这个大型 makefile 文件可以为您完成 OpenMoko 环境的所有配置任务。MokoMakefile 目前由 Rod Whitby 开发和维护;它目前还没有纳入正式的 OpenMoko 环境。
如前所述,如果您没有手机,那么可以配置一个模拟器。QEMU 模拟器可以完整地模拟 OpenMoko 手机环境。QEMU 的一个分支程序几乎可以完全模拟手机硬件,仅缺少 GPS 功能。有关设置基于 QEMU 的模拟器的更多信息,请查看 “OpenMoko under QEMU” 上的 OpenMoko Wiki 页面(参见 参考资料 中的链接)。
显然,使用真实的手机更加方便,然而使用模拟器也有其优点。最重要的是,您可以立即下载模拟器,并且是免费的。OpenMoko wiki(参见 参考资料 中的链接)提供了有关配置模拟器的更多信息。本教程同时使用了模拟器和真实手机,但是并不是所有功能都可以在模拟器上进行测试。
如果您有一个 Neo 1973 手机,您应该将其中的 OpenMoko 软件刷新至当前版本。此处不会重复 OpenMoko wiki 已有的优秀指导,请查找 参考资料 中的 “Flashing openmoko” 链接。手机设置完毕后,您现在就可以开机或者阅读后面的模拟器设置。
组装环境
要运行 OpenMoko,你需要使用一些方便的工具;各种 Linux 发行版已经安装了其中一些工具,但并不是全部,因此您需要进行准备。也许最重要的一点,您必须安装 gcc 3.4 —— 而不是任何 gcc 4.X —— 以构建和运行 QEMU(从某方面说,这显然是个 bug,但目前还没有被修复)。您还需要子版本、各种与文档有关的工具(例如 texinfo)、用于 ncurses 的开发头文件、一个压缩库(zlib 或 libz)、OpenSSL 和 GTK++。不同的系统可能缺少不同的工具。MokoMakefile wiki 页面介绍了一些系统中的详细信息。
根据自己的意愿命名一个新的目录,并放置在系统中的任意位置。我将其命名 “om” 并放在我的主目录中。切换到该目录并下载 MokoMakefile http://www.rwhitby.net/files/openmoko/Makefile。
MokoMakefile 可以从头构建您需要的任何内容。这将耗费一些时间,因此,让我们做一些准备工作。如果您使用的是多核系统,那么可以通过如下操作提高性能:首先为工作目录创建一个 build/conf 子目录,在其中创建一个名为 local.conf 的文件,其中使用下面两行代码指定一个并行构建:
PARALLEL_MAKE = "-j 4"
BB_NUMBER_THREADS = "4"
其中数字 4 是针对双核系统计算而来;有些人建议运行的线程数应该比系统所具有的内核多一个,还有些人认为每个内核应该运行两个线程。在一个双核系统中,运行 4 个线程就可以很好地工作。
首先要运行 make setup
以实现初始设置和配置。然后运行 make openmoko-devel-image
,查看如何构建完整的 OpenMoko 环境 —— 但也许您并不希望查看完整过程,因为它可能要耗费 5 个小时或更长的时间。如果您计划使用 QEMU 模拟器,也可使用 make build-qemu
立即构建。
构建过程产生的目录树
输入 make setup
后,您看到的目录可能只包含一个 makefile 文件,或者一个 makefile 文件和一个配置文件。现在,您应该拥有名字为 “bitbake”、“images” 和 “sources” 的目录。这些是 OpenEmbedded 构建环境的工作结构。
Bitbake(该工具用于实际构建目标二进制程序等内容)具有自己的目录,包含工具、文档等等。该目录存放 bitbake 发行版;除非您清楚自己的行为,否则不应弄混这个目录。
build 目录保存了配置文件(build/conf)、QEMU 内容(build/qemu)和一个用来保存中间状态的临时目录。临时目录(build/tmp)存放自己的子目录选择;cache、cross、 deploy、rootfs、staging、stamps 和 work。无论针对主机环境还是目标环境进行构建,所需的文件基本都囊括在其中。我们主要关心的是 fic-gta01-angstrom-linux-gnueabi 子目录,它专门对应于 Neo 1973 手机。通常,只有在进行调试时才会浏览这个目录;您也许希望更详细地查看 stamps 目录,它指明了所有任务的最后执行时间。
该目录包含 OpenEmbedded 发行版,囊括了所有可用的包。openembedded/packages 目录用来保存添加的新包 —— 您稍后将使用到。但是,它主要是 OpenEmbedded 的内部架构,一般不需要对其执行什么操作。
openmoko 目录包含特定于 OpenMoko 的文件和文档,而不仅仅与 OpenEmbedded 有关。同样,您不应直接与这个目录进行过多交互。
在本教程中,这个目录包含一个树结构,用于提供针对 openmoko、bitbake 或 openembedded 的补丁,但是初始结构为空。
sources 目录用来保存下载的源文件,将使用这些文件构建目标文件系统。它保存实际的发行版 tarball,以及 lock 文件和校验和。sources/svn 子目录保存通过 Subversion(而不是 tarball)下载的内容的 Subversion 检出。
另一个时间戳集合,MokoMakefile 使用它检查最近更新了哪些组件。
构建一个简单的应用程序
由于 OpenMoko 是使用 OpenEmbedded 构建的,因此,构建测试应用程序的正确方法是使用 OpenEmbedded。为了确保能够顺利构建,首先使用一个命令行应用程序检验工具是否正常工作。
在前面一节中,我向您介绍了 openembedded/packages 子目录,其中包含已有的包。现在,我们来添加自己的包。我们通常将示例程序命名为 hello,这里也不例外;在 openembedded/packages 中创建一个名为 hello 的目录,并为指定的文件创建一个子目录。您基本上只需要两个文件;一个 BitBake recipe 和一个程序的源文件。
示例程序并不复杂;只需少量输出,然后检验是否能够正常工作。下面是我的示例程序,保存为 hello/files/hello.c:
|
来审查一下它的内容:一个 bitbake recipe 文件。幸运的是,做了大量简化工作。实质上,bitbake recipe 有点类似于高度专门化的 shell 脚本(也许更准确地说是包含 shell 命令的 python 脚本);下面提供了一个例子,与上面的程序相对应;我将其命名为 hello_0.0.bb:
|
这个 recipe 首先对 3 个变量进行设置。其中的说明简单明了。PR 设置修订 —— 我稍后将解释。SRC_URI 是指向文件的 URI。您将注意到,在 file:// URI 中,没有使用第三个斜杠 —— 这是因为 bitbake 非常聪明,它可以自动查找文件目录。如果使用了第三个斜杠,将在本地驱动器的根目录中查找名为 hello.c 的文件。
最棘手的部分就是最短小的部分:PR 变量,它用来表示项目的修订。如果对文件做了其他修改并且没有保存到 PR 变量中,bitbake 可能认为您不愿意更新这个修改。初始值为 r0 并可以任意累加。比如,即使构建失败(假如由于源文件的输入错误),如果没有修改 PR 的话,那么更新文件时不会对新版本进行复制。
如果存在 do_compile()
函数,它将提供程序编译指令。这些指令或简单或复杂;在本例中,它们非常简单。注意,可以使用大部分预定义的变量自动获得正确的构建设置。类似地,程序构建完毕后,将运行 do_install()
,以将文件复制到目标根文件系统中的对应位置。要留心模式;我刚开始使用的示例 bitbake 文件以 0644(所有者可以读/写,组用户和其他人只拥有读权限)作为目标二进制程序的模式,但是如果没有执行权限,则不是十分有用。
要添加自己的包,回到 local.conf 文件并添加以下代码:
DISTRO_EXTRA_RDEPENDS += "hello"
注意,变量名以 RDEPENDS 结尾,而不是 DEPENDS。这行代码令 bitbake 将 hello 包(由上文的 bitbake 文件描述)添加到发行版中。
要使用新添加的包重新构建,依次运行 make update
和 make openmoko-devel-image
。完成之后,可以在实际的手机或 QEMU 中使用新的映像(对于 QEMU,使用 make flash-qemu-local
“刷新” 虚拟手机而使用 make run-qemu-snapshot
运行)。在终端运行 hello
将输出一条消息
这样就完成了一个简单的示例程序,觉得如何呢?
小型 GTK+ 应用程序
很多用户,尤其那些实现过其他小型嵌入式系统的用户,都希望知道为什么他们不能使用 Qt。是的,您可以使用,但不应该使用。GTK+ 和 Qt 均被构建为共享库;这意味着您系统中的每个 GTK+ 应用程序在内存中使用同一个编译后的 GTK+ 代码的副本,而每个 Qt 应用程序使用同一个 Qt 副本。系统附带的所有标准应用程序都使用 GTK+,因此,您的应用程序如果使用 GTK+ 就可以利用这些已经装载了的代码。Qt 应用程序在这种环境中需要更高的开销。这就好比一个国家决定在马路的左侧还是右侧驾驶;尽管两种方案都有争议,但是必定会遵从特定马路上所有人做出的相同 选择。
我将首先演示一个小型程序,它仅打开一个窗口而不执行任何操作。您甚至不需要使用 “quit” 选项,因为手机都使用一个很不错的约定来关闭应用程序 —— 摁下 power 按钮(事实上,这对于模拟器有些不方便,因为 USB 键盘驱动程序会窃取空格事件)。
下面显示了这个小型程序:
|
现在,构建 bitbake recipe 并不是很复杂,但是还是有一点难度。首先要指定应用程序依赖于 GTK+。可以通过 DEPENDS = "gtk+"
完成。您还需要更新修订,否则 bitbake 不会注意到您执行的改动 —— 当然对源文件的改动除外!另一处改动是编译器行,这有一点长。不能仅仅链接 libgtk —— 必须获得所有的 include 目录,并且涉及到的每个包都有一个独立的 include 包。最终的 do_compile()
规则类似于:
|
实际上,代码所做的就是将所有 include 目录和一个单独的目录包含进来。您不必指定目录路径;GTK+ 目录位于目标环境中的默认库位置(/usr/lib),因此默认配置可以很好地工作。
对于一个只包括 <gtk/gtk.h>
的程序,需要指定 7 个额外的 include 目录,这似乎有点太多了。这是由两个问题引起的。第一个问题是, GTK+ 主头文件包含一些其他的头文件,而这些头文件又包含了更多的头文件。第二个问题是,和其它系统一样,为了减少冲突,OpenMoko 构建环境将每个包的 include 树放置在自己的子目录中。在本例中,<gtk/gtk.h>
引用 /usr/include/gtk-2.0/gtk/gtk.h,但是如果您安装了多个版本的 GTK+,改变这些 include 路径将随之改动您获得的头文件,而不需要您更新大量的源文件。
再一次使用 make openmoko-devel-image
构建应用程序。可以看到 hello 应用程序被重新构建。现在,如果您使用的是模拟器,则使用 make flash-qemu-local
将它放入手机中。如果您使用的是手机,则复制文件。运行应用程序将得到一个不执行任何操作的灰色屏幕。很好!我们的目标并不是让应用程序执行任何操作,而是验证小型 GTK+ 应用程序的编译和链接选项是否正确。现在,让我们来添加一些功能。
系统信息
浏览 OpenMoko 站点时,我发现了一个用于基于 stylus 的系统信息 applet 的设计文档。看上去非常有趣!可以很轻松地编写程序获得并显示系统信息,这种程序听上去十分有用。
我习惯使用表格布局,因此我首先使用了它。GTK+ 表格布局可以让您将窗口划分为一个网格,然后将对象放入网格的单元格中。比较有趣的 API 块是 gtk_table_attach()
,它具备以下原型:
|
在示例程序中,在每次调用函数时,我使用与这个原型相同的断行,以便更清楚地了解参数。现在,您将拥有一个非常简单的表格;顶部的单元格将提供内核版本,底部的单元格将显示一个 Quit 按钮(在模拟器中运行时添加)。
第一个版本的程序非常简单。它所做到就是创建两个标签和一个按钮,将它们放入表格中并显示出来。是不是很简单?首先,您需要创建一个表格,然后将它插入到窗口中:
|
这个表格共有 8 行 3 列,并且每个单元格大小相同(这是由 gtk_table_new()
的 TRUE 参数决定的,如果为 FALSE,则单元格的大小不一样)。
以下是 Quit 按钮的代码,添加这个按钮可以更轻松地关闭应用程序:
|
它展示了一个 Quit 按钮,但是它执行什么操作?单击时毫无反应。
需要使用两个回调函数来支持窗口的关闭。第一个是 delete_event()
,用于表示用户希望终止应用程序;返回值若为 FALSE,则表示应该关闭应用程序,返回值若为 TRUE,表示不应执行任何操作。第二个函数为 destroy()
,执行退出操作。注意,这些名称不是强制性的;但是,在 GTK+ 教程代码中使用它们可以方便以后的读者进行识别和理解。
|
现在,您有了按钮和函数可以关闭应用程序,现在应该将它们组织起来,方法就是将信号注册到回调函数。下面的代码实现了按钮单击事件并处理常规的窗口关闭事件:
|
首先输出核心版本。您可能想知道如何获得核心版本。当然是使用 uname()
!代码非常简单:
|
struct utsname
由 C 字符串填充,表示当前环境的特征。“release” 字符串通常主要由内核版本号组成。获得数据后,如何显示呢?
清单 11 显示的代码将向表格插入标签(出于演示目的我们只使用文本字段)。第一个标签位于左侧的列,是一个标题;第二个标签位于中间和右侧的列,存放 uname()
获得的版本号。
|
再次调用 gtk_label_new()
来重写 “label” 变量似乎有些奇怪,但是前面分配的对象已经位于表格中,因此不会丢失。
另一个改变是使用 gtk_widget_show_all()
替换 gtk_widget_show()
;顾名思义,gtk_widget_show_all()
不仅显示窗口,还显示其中包含的所有内容(在本例中包含一个表格、两个标签和一个按钮)。完整的结果代码如下所示:
|
添加更多功能
我们已经开发了一个基本的界面,可以快速读取数据,下一个问题是,还有哪些数据有用?其中之一便是可用的磁盘空间。可以使用多种方法查询。
您可以通过 statfs()
系统调用获得可用的磁盘空间。这个函数将生成一个包含磁盘当前使用情况的字符串:
|
statfs()
系统调用与 stat()
非常相似,生成有关目标路径的信息;然而,它生成的是文件系统的统计信息而不是文件统计信息。奇怪的是,尽管人们习惯通过磁盘使用百分比衡量容量,statfs()
只会告诉你当前可用空间是多少。代码将计算使用的磁盘空间量(范围为 0 至 1),然后将它转换为百分数。注意,错误检查功能另外需要使用 <string.h>
头文件,用于 strcpy()
。
现在,您应该很熟悉这个过程了:
|
实际上,这些内容可以作为一种模式加以利用。惟一需要真正修改的是行号和两个字符串。
总的来说,您可能需要使用一个函数将其中的内容放到显示表格中。高效编程的实质就是创新;我将这个函数命名为 label_in_table()
。它需要哪些参数呢?表格、行、描述性标签(位于左侧)和标签中的文本(位于右侧)。因此:
|
接下来,让我们简化实际的主函数。与上文相比,现在执行起来更加简单:
|
这个例子演示了一些内容。其中之一是可以使用 table
作为全局变量;毕竟,只有一个表格。另外一处是,您可能认为可以使用一个静态或全局变量跟踪行号 —— 确实可以这样做,但是以后的更新很可能会替换现有行的内容,能够指定目标行将是一个很不错的功能。
下一步是处理内存信息。首先调用 sysinfo()
。由于调用 sysinfo()
会产生大量有用信息,使用其输出的代码可能复杂一些 —— 但是不会太过复杂。和 uname()
或 statfs()
很相似,sysinfo()
将数据提取到一个结构中(struct sysinfo
)。
可以使用下面的代码轻易地提取内存使用信息:
|
系统负载应该很简单;1 分钟、5 分钟、15 分钟的平均负载以无符号长整型的方式保存在 struct sysinfo
中。然而,平均负载通常作为运行队列的平均长度导出给用户,一般会生成一个浮点数。如果平均长度为 1.00,表示系统处于完整负载的较低一端。因此,如何将无符号的长整型转换为用户更熟悉的值?sysinfo()
手册没有详细说明这点 ,但是关键在于使用称为 SI_LOAD_SHIFT
的宏,它持有换算系数;事实上,无符号的长整型被作为定点小数值处理。通常,SI_LOAD_SHIFT
为 16,表示平均负载 1.0 被表示为 65,536。
这种计算将调用一个帮助器函数:
|
可以按照与其他值相同的显示方法将结果值输出给用户:
|
现在查看完成后的程序。下面显示了屏幕截图:
文章评论(0条评论)
登录后参与讨论