Chinaunix首页 | 论坛 | 博客
  • 博客访问: 153731
  • 博文数量: 53
  • 博客积分: 2042
  • 博客等级: 大尉
  • 技术积分: 425
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-15 21:39
文章存档

2011年(6)

2010年(47)

分类: 嵌入式

2010-04-28 23:46:04

移动通信的免费开放平台

级别: 中级

Peter Seebach (), 作家, 自由作家

2007 12 10

OpenMoko 环境提供了一个完全免费的开发环境,可以在受支持的手机硬件上运行应用程序和系统代码,消除对私有代码的依赖性。本教程介绍了 OpenEmbedded 构建环境,使用它为 OpenMoko 手机(如 Neo 1973)创建文件系统映像。

开始之前

本教程介绍了 OpenMoko 开发平台并以 Neo 1973 为目标展示了它的具体应用。Neo 1973 是一款功能独特的手机,其开发目的就是可以在任何网络任何地点进行工作,并可以运行针对它开发的所有软件。

 

我将简要介绍平台的配置和刷新过程,首先说明需要准备的文件及其功能。我们的目的不是重复现有的文档,而是提供一个概述并将所有文件整合在一起。随后,我将详细介绍软件开发的具体细节。

 

本教程针对的读者是具有基础软件开发经验以及对电话和嵌入式系统感兴趣或具有相关经验的 Linux 开发人员。要学习本教程,还需要具备有关命令行的基本知识。

学习本教程并一定要使用 Neo 1973 手机(或者其他型号的手机),但是,如果拥有一个可运行的 OpenMoko 目标(即使是模拟目标),将更有利于您的学习。我在本教程中使用 Ubuntu 作为主机环境,但是实际上可以使用任何 Linux 系统。也可以使用 Mac OS X 甚至是 Windows 作为主机环境。

 

平台概览

Neo 1973 由大众电脑公司(First International Computer)生产,常常被称为 “OpenMoko”。实际上,OpenMoko 是一个 Linux 平台和一些相关应用程序,而非一款手机。但是,它是一款用于手机的 Linux 应用程序,在撰写本文时,Neo 1973 是惟一一款可完全支持该平台的手机。您也可以构建一个模拟环境进行软件开发,我将在本教程中介绍到,但是这种模拟环境不能很好地连接到手机网络。

OpenEmbedded

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 页面介绍了一些系统中的详细信息。

下载 MokoMakefile

根据自己的意愿命名一个新的目录,并放置在系统中的任意位置。我将其命名 “om” 并放在我的主目录中。切换到该目录并下载 MokoMakefile

准备工作

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)存放自己的子目录选择;cachecrossdeployrootfsstagingstamps work。无论针对主机环境还是目标环境进行构建,所需的文件基本都囊括在其中。我们主要关心的是 fic-gta01-angstrom-linux-gnueabi 子目录,它专门对应于 Neo 1973 手机。通常,只有在进行调试时才会浏览这个目录;您也许希望更详细地查看 stamps 目录,它指明了所有任务的最后执行时间。

目录

该目录包含 OpenEmbedded 发行版,囊括了所有可用的包。openembedded/packages 目录用来保存添加的新包 —— 您稍后将使用到。但是,它主要是 OpenEmbedded 的内部架构,一般不需要对其执行什么操作。

目录

openmoko 目录包含特定于 OpenMoko 的文件和文档,而不仅仅与 OpenEmbedded 有关。同样,您不应直接与这个目录进行过多交互。

目录

在本教程中,这个目录包含一个树结构,用于提供针对 openmokobitbake openembedded 的补丁,但是初始结构为空。

目录

sources 目录用来保存下载的源文件,将使用这些文件构建目标文件系统。它保存实际的发行版 tarball,以及 lock 文件和校验和。sources/svn 子目录保存通过 Subversion(而不是 tarball)下载的内容的 Subversion 检出。

目录

另一个时间戳集合,MokoMakefile 使用它检查最近更新了哪些组件。

 

构建一个简单的应用程序

由于 OpenMoko 是使用 OpenEmbedded 构建的,因此,构建测试应用程序的正确方法是使用 OpenEmbedded。为了确保能够顺利构建,首先使用一个命令行应用程序检验工具是否正常工作。

创建一个包目录

在前面一节中,我向您介绍了 openembedded/packages 子目录,其中包含已有的包。现在,我们来添加自己的包。我们通常将示例程序命名为 hello,这里也不例外;在 openembedded/packages 中创建一个名为 hello 的目录,并为指定的文件创建一个子目录。您基本上只需要两个文件;一个 BitBake recipe 和一个程序的源文件。

Hello,示例程序!

示例程序并不复杂;只需少量输出,然后检验是否能够正常工作。下面是我的示例程序,保存为 hello/files/hello.c


                    
#include 
 
int main(int argc, char *argv[]) {
   puts("Help!  I'm trapped in a sample program factory.");
   return 0;
}

bitbake recipe

来审查一下它的内容:一个 bitbake recipe 文件。幸运的是,做了大量简化工作。实质上,bitbake recipe 有点类似于高度专门化的 shell 脚本(也许更准确地说是包含 shell 命令的 python 脚本);下面提供了一个例子,与上面的程序相对应;我将其命名为 hello_0.0.bb


                    
DESCRIPTION = "Tutorial example"
PR = "r0"
SRC_URI = "file://hello.c"
do_compile() {
        ${CC} ${CFLAGS} ${LDFLAGS} ${WORKDIR}/hello.c -o hello
}
do_install() {
        install -m 0755 -d ${D}${bindir}
        install -m 0755 ${S}/hello ${D}${bindir}
}

变量分配

这个 recipe 首先对 3 个变量进行设置。其中的说明简单明了。PR 设置修订 —— 我稍后将解释。SRC_URI 是指向文件的 URI。您将注意到,在 file:// URI 中,没有使用第三个斜杠 —— 这是因为 bitbake 非常聪明,它可以自动查找文件目录。如果使用了第三个斜杠,将在本地驱动器的根目录中查找名为 hello.c 的文件。

修订

最棘手的部分就是最短小的部分:PR 变量,它用来表示项目的修订。如果对文件做了其他修改并且没有保存到 PR 变量中,bitbake 可能认为您不愿意更新这个修改。初始值为 r0 并可以任意累加。比如,即使构建失败(假如由于源文件的输入错误),如果没有修改 PR 的话,那么更新文件时不会对新版本进行复制。

do_thisdo_that

如果存在 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 应用程序在这种环境中需要更高的开销。这就好比一个国家决定在马路的左侧还是右侧驾驶;尽管两种方案都有争议,但是必定会遵从特定马路上所有人做出的相同 选择。

小型 GTK+ 程序

我将首先演示一个小型程序,它仅打开一个窗口而不执行任何操作。您甚至不需要使用 “quit” 选项,因为手机都使用一个很不错的约定来关闭应用程序 —— 摁下 power 按钮(事实上,这对于模拟器有些不方便,因为 USB 键盘驱动程序会窃取空格事件)。

下面显示了这个小型程序:


                    
#include 
 
int
main(int argc, char *argv[]) {
        GtkWidget *window;
 
        gtk_init(&argc, &argv);
 
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_widget_show(window);
 
        gtk_main();
 
        return 0;
}

更新 bitbake recipe

现在,构建 bitbake recipe 并不是很复杂,但是还是有一点难度。首先要指定应用程序依赖于 GTK+。可以通过 DEPENDS = "gtk+" 完成。您还需要更新修订,否则 bitbake 不会注意到您执行的改动 —— 当然对源文件的改动除外!另一处改动是编译器行,这有一点长。不能仅仅链接 libgtk —— 必须获得所有的 include 目录,并且涉及到的每个包都有一个独立的 include 包。最终的 do_compile() 规则类似于:


                    
do_compile() {
   ${CC} ${CFLAGS} ${LDFLAGS} -Wl,-O1 -g -o hello ${WORKDIR}/hello.c \
   -I ${STAGING_INCDIR}/pango-1.0 -I ${STAGING_INCDIR}/gtk-2.0 \
   -I ${STAGING_INCDIR}/cairo -I ${STAGING_LIBDIR}/gtk-2.0/include \
   -I ${STAGING_INCDIR}/glib-2.0 -I ${STAGING_INCDIR}/glib-2.0/glib \
   -I ${STAGING_INCDIR}/atk-1.0 -lgtk-x11-2.0
}

 

实际上,代码所做的就是将所有 include 目录和一个单独的目录包含进来。您不必指定目录路径;GTK+ 目录位于目标环境中的默认库位置(/usr/lib),因此默认配置可以很好地工作。

为何有这么多 include 目录?

对于一个只包括 的程序,需要指定 7 个额外的 include 目录,这似乎有点太多了。这是由两个问题引起的。第一个问题是, GTK+ 主头文件包含一些其他的头文件,而这些头文件又包含了更多的头文件。第二个问题是,和其它系统一样,为了减少冲突,OpenMoko 构建环境将每个包的 include 树放置在自己的子目录中。在本例中, 引用 /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(),它具备以下原型:


                    
GtkWidget* gtk_table_attach(GtkTable* table, GtkWidget* child,
   guint left_side, guint right_side,
   guint top_side, guint bottom_side,
   GtkAttachOptions xoptions, GtkAttachOptions yoptions,
   guint xpadding, guint ypadding);

 

在示例程序中,在每次调用函数时,我使用与这个原型相同的断行,以便更清楚地了解参数。现在,您将拥有一个非常简单的表格;顶部的单元格将提供内核版本,底部的单元格将显示一个 Quit 按钮(在模拟器中运行时添加)。

构建并填充表格

第一个版本的程序非常简单。它所做到就是创建两个标签和一个按钮,将它们放入表格中并显示出来。是不是很简单?首先,您需要创建一个表格,然后将它插入到窗口中:


                    
table = gtk_table_new(8, 3, TRUE);
gtk_container_add(GTK_CONTAINER(window), table);
gtk_container_set_border_width(GTK_CONTAINER(table), 10);

 

这个表格共有 8 3 列,并且每个单元格大小相同(这是由 gtk_table_new() TRUE 参数决定的,如果为 FALSE,则单元格的大小不一样)。

添加 Quit 按钮

以下是 Quit 按钮的代码,添加这个按钮可以更轻松地关闭应用程序:


                    
button = gtk_button_new_with_label("Quit");
gtk_table_attach(GTK_TABLE(table), button,
   1, 2,
   7, 8,
   GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
   0, 0);

 

它展示了一个 Quit 按钮,但是它执行什么操作?单击时毫无反应。

回调函数

需要使用两个回调函数来支持窗口的关闭。第一个是 delete_event(),用于表示用户希望终止应用程序;返回值若为 FALSE,则表示应该关闭应用程序,返回值若为 TRUE,表示不应执行任何操作。第二个函数为 destroy(),执行退出操作。注意,这些名称不是强制性的;但是,在 GTK+ 教程代码中使用它们可以方便以后的读者进行识别和理解。


                    
static gboolean
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
        return FALSE;
}
 
static gboolean
destroy(void)
{
        gtk_main_quit();
}

装配程序

现在,您有了按钮和函数可以关闭应用程序,现在应该将它们组织起来,方法就是将信号注册到回调函数。下面的代码实现了按钮单击事件并处理常规的窗口关闭事件:


                    
g_signal_connect(G_OBJECT(button), "clicked",
                 G_CALLBACK(destroy), NULL);
g_signal_connect(G_OBJECT(window), "delete_event",
                 G_CALLBACK(delete_event), NULL);
g_signal_connect(G_OBJECT(window), "destroy",
                 G_CALLBACK(destroy), NULL);

现在,获取一些数据

首先输出核心版本。您可能想知道如何获得核心版本。当然是使用 uname()!代码非常简单:


                    
struct utsname u;
[...]
uname(&u);
label = gtk_label_new(u.release);

 

struct utsname C 字符串填充,表示当前环境的特征。“release” 字符串通常主要由内核版本号组成。获得数据后,如何显示呢?

向表格添加标签

清单 11 显示的代码将向表格插入标签(出于演示目的我们只使用文本字段)。第一个标签位于左侧的列,是一个标题;第二个标签位于中间和右侧的列,存放 uname() 获得的版本号。


                    
label = gtk_label_new("Kernel version:");
gtk_table_attach(GTK_TABLE(table), label,
   0, 1,
   0, 1,
   GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
   0, 0);
 
uname(&u);
label = gtk_label_new(u.release);
gtk_table_attach(GTK_TABLE(table), label,
   1, 3,
   0, 1,
   GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
   0, 0);

 

再次调用 gtk_label_new() 来重写 “label” 变量似乎有些奇怪,但是前面分配的对象已经位于表格中,因此不会丢失。

完成应用程序

另一个改变是使用 gtk_widget_show_all() 替换 gtk_widget_show();顾名思义,gtk_widget_show_all() 不仅显示窗口,还显示其中包含的所有内容(在本例中包含一个表格、两个标签和一个按钮)。完整的结果代码如下所示:


                    
#include 
#include 
 
static gboolean
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
   return FALSE; 
}
 
static gboolean
destroy(void)
{
   gtk_main_quit();
}
 
int
main(int argc, char *argv[]) {
   GtkWidget *window;
   GtkWidget *table;
   GtkWidget *button;
   GtkWidget *label;
   struct utsname u;
 
   gtk_init(&argc, &argv);
 
   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   g_signal_connect(G_OBJECT(window), "delete_event",
                    G_CALLBACK(delete_event), NULL);
   g_signal_connect(G_OBJECT(window), "destroy",
                    G_CALLBACK(destroy), NULL);
   gtk_window_set_title(GTK_WINDOW(window), "Hello, System!");
   table = gtk_table_new(6, 3, TRUE);
   gtk_container_add(GTK_CONTAINER(window), table);
   gtk_container_set_border_width(GTK_CONTAINER(table), 10);
   button = gtk_button_new_with_label("Quit");
   gtk_table_attach(GTK_TABLE(table), button,
      1, 2,
      7, 8,
      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
      0, 0);
   g_signal_connect(G_OBJECT(button), "clicked",
                         G_CALLBACK(destroy), NULL);
 
   /* uname */
   label = gtk_label_new("Kernel version:");
   gtk_table_attach(GTK_TABLE(table), label,
      0, 1,
      0, 1,
      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
      0, 0);
 
   uname(&u);
   label = gtk_label_new(u.release);
   gtk_table_attach(GTK_TABLE(table), label,
      1, 3,
      0, 1,
      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
      0, 0);
 
   gtk_widget_show_all(window);
 
   gtk_main();
 
   return 0;
}

 

添加更多功能

我们已经开发了一个基本的界面,可以快速读取数据,下一个问题是,还有哪些数据有用?其中之一便是可用的磁盘空间。可以使用多种方法查询。

查询磁盘空间

您可以通过 statfs() 系统调用获得可用的磁盘空间。这个函数将生成一个包含磁盘当前使用情况的字符串:


                    
static char *
diskfree(void)
{
   static char capacity[16];
   struct statfs buf;
   if (statfs("/", &buf) < 0) {
      strcpy(capacity, "unavailable");
   } else {
      double used = 1.0 - ((double) buf.f_bavail / buf.f_blocks);
      sprintf(capacity, "%.1f%%", (used * 100));
   }
   return capacity;
}

 

statfs() 系统调用与 stat() 非常相似,生成有关目标路径的信息;然而,它生成的是文件系统的统计信息而不是文件统计信息。奇怪的是,尽管人们习惯通过磁盘使用百分比衡量容量,statfs() 只会告诉你当前可用空间是多少。代码将计算使用的磁盘空间量(范围为 0 1),然后将它转换为百分数。注意,错误检查功能另外需要使用 头文件,用于 strcpy()

插入信息

现在,您应该很熟悉这个过程了:


                    
/* disk space */
label = gtk_label_new("Disk usage:");
gtk_table_attach(GTK_TABLE(table), label,
    0, 1,
    1, 2,
    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
    0, 0);
 
label = gtk_label_new(diskfree());
gtk_table_attach(GTK_TABLE(table), label,
    1, 3,
    1, 2,
    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
    0, 0);

 

实际上,这些内容可以作为一种模式加以利用。惟一需要真正修改的是行号和两个字符串。

重构时间

总的来说,您可能需要使用一个函数将其中的内容放到显示表格中。高效编程的实质就是创新;我将这个函数命名为 label_in_table()。它需要哪些参数呢?表格、行、描述性标签(位于左侧)和标签中的文本(位于右侧)。因此:


                    
static void
label_in_table(GtkWidget *table, int row, char *name, char *value)
{
   GtkWidget *label;
 
   label = gtk_label_new(name);
   gtk_table_attach(GTK_TABLE(table), label,
      0, 1,
      row, row + 1,
      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
      0, 0);
 
   label = gtk_label_new(value);
   gtk_table_attach(GTK_TABLE(table), label,
      1, 3,
      row, row + 1,
      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
      0, 0);
}

调用 label_in_table()

接下来,让我们简化实际的主函数。与上文相比,现在执行起来更加简单:


                    
/* uname */
uname(&u);
label_in_table(table, 0, "Kernel version:", u.release);
 
/* disk space */
label_in_table(table, 1, "Disk usage:", diskfree());

 

这个例子演示了一些内容。其中之一是可以使用 table 作为全局变量;毕竟,只有一个表格。另外一处是,您可能认为可以使用一个静态或全局变量跟踪行号 —— 确实可以这样做,但是以后的更新很可能会替换现有行的内容,能够指定目标行将是一个很不错的功能。

内存信息

下一步是处理内存信息。首先调用 sysinfo()。由于调用 sysinfo() 会产生大量有用信息,使用其输出的代码可能复杂一些 —— 但是不会太过复杂。和 uname() statfs() 很相似,sysinfo() 将数据提取到一个结构中(struct sysinfo)。

可以使用下面的代码轻易地提取内存使用信息:


                    
sprintf(buffer, "%.1f%%",
    (100 * (1.0 - ((double) si.freeram / si.totalram))));
label_in_table(table, 2, "Memory usage:", buffer);

系统负载

系统负载应该很简单;1 分钟、5 分钟、15 分钟的平均负载以无符号长整型的方式保存在 struct sysinfo 中。然而,平均负载通常作为运行队列的平均长度导出给用户,一般会生成一个浮点数。如果平均长度为 1.00,表示系统处于完整负载的较低一端。因此,如何将无符号的长整型转换为用户更熟悉的值?sysinfo() 手册没有详细说明这点 ,但是关键在于使用称为 SI_LOAD_SHIFT 的宏,它持有换算系数;事实上,无符号的长整型被作为定点小数值处理。通常,SI_LOAD_SHIFT 16,表示平均负载 1.0 被表示为 65,536

这种计算将调用一个帮助器函数:


                    
static double
scaled_load(long int unscaled) {
        return (double) unscaled / (1 << SI_LOAD_SHIFT);
}

 

可以按照与其他值相同的显示方法将结果值输出给用户:


                    
sprintf(buffer, "%.2f %.2f %.2f",
    scaled_load(si.loads[0]), scaled_load(si.loads[1]),
    scaled_load(si.loads[2]));
label_in_table(table, 3, "Load:", buffer);

程序构建完成

现在查看完成后的程序。下面显示了屏幕截图:



 

未来方向

本教程介绍了 OpenMoko 开发环境,并以一个基础的系统状态 applet 为例演示了平台的基本用法。下一步应考虑如何进一步使用该环境。如果还不具备受支持的手机,建议您购买一款;我着重介绍了可以移植到模拟器的任务,但是手机还有很多其他的功能是模拟器无法替代的。

早期型号的 Neo 1973 手机没有提供 WiFi 硬件,但是后来的手机可能都具备这种功能。另一方面,手机网络也可以用来交换数据。虽然网络配置工作可以通过提供的软件完成,但是 Linux 网络无疑可提供优越的兼容性。

模拟器没有提供模拟的 GPS 芯片,但是 Neo 1973 具备内置的 GPS 部件,从而可以实现很多有趣的应用;比方说,使用手机向在家中运行的服务器发送临时数据包以告诉服务器自己所在的位置。这项功能在丢失手机的情况下非常有用,并且还很有趣。您还可以使用一种可用的地图绘制 API 构建一个 “where have I been today” 应用程序。

很奇怪,首先想到的 OpenMoko 平台开发思想与电话一点关系都没有。构建一个可以记录电话呼叫的应用程序如何?您希望确保这在权限之内是合法的,因为某些情况下,如果没有获得所有参与方的同意,这样做是禁止的,但是这是可行的。这个例子说明了 OpenMoko 环境可以实现其他大多数手机无法实现的功能 —— 通常,您可以自己编写应用程序,但不能够与电话应用程序交互。但是,在 OpenMoko 环境中,从一开始就提供了软件。

前途预测

当一种新的平台出现时,很难预测人们将它用于哪些用途。闭源模型,尤其是成本较高的模型,往往会发现大多数理解良好的业务应用程序可以满足很多既定的需求。但是,开放式开发可以获得真正的惊喜,这也是令 OpenMoko 如此有趣的原因之一。最大的潜力在于,已知的应用程序改善对手机的功能改进并没有多大帮助,只有全新的应用程序才可以做到这点。现在,您已经获得了足够的入门知识,请亲自尝试使用吧。

下载

描述

名字

大小

下载方法

本教程的示例代码

hello.tgz

1.5KB

 

文件: hello.tgz.tar
大小: 1KB
下载: 下载

 

阅读(885) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~