原创 转贴:面向 OpenMoko Linux 手机的软件开发

2008-11-13 01:13 2469 6 6 分类: 软件与OS
转自:http://www.ibm.com/developerworks/cn/edu/l-dw-l-openmoko.html

平台概览


手机还是软件


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



点击看大图
OpenEmbedded

OpenMoko 使用 OpenEmbedded 构建,可实现交叉编译(参见 参考资料 中有关 OpenEmbedded 信息的链接)。不论您是想构建具有调试功能的本地程序,还是想构建经过优化的目标二进制程序,OpenEmbedded 都提供了工具和支持来构建和发布功能。因此,OpenMoko 开发需要了解如何使用 OpenEmbedded。



点击看大图
MokoMakefile

使用新环境的一大难点就是进行全面配置、正确准备文件,等等。进入 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 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(该工具用于实际构建目标二进制程序等内容)具有自己的目录,包含工具、文档等等。该目录存放 bitbake 发行版;除非您清楚自己的行为,否则不应弄混这个目录。


build 目录


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


openmoko 目录


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


patches 目录


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


sources 目录


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


stamps 目录


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


构建一个简单的应用程序


欢迎使用 OpenEmbedded


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



点击看大图


创建一个包目录


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



点击看大图


Hello,示例程序!


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


清单 1. 示例程序

                    
#include <stdio.h>

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:


清单 2. 示例 bitbake recipe

                    
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_this,do_that


如果存在 do_compile() 函数,它将提供程序编译指令。这些指令或简单或复杂;在本例中,它们非常简单。注意,可以使用大部分预定义的变量自动获得正确的构建设置。类似地,程序构建完毕后,将运行 do_install(),以将文件复制到目标根文件系统中的对应位置。要留心模式;我刚开始使用的示例 bitbake 文件以 0644(所有者可以读/写,组用户和其他人只拥有读权限)作为目标二进制程序的模式,但是如果没有执行权限,则不是十分有用。



点击看大图


添加包


要添加自己的包,回到 local.conf 文件并添加以下代码:


DISTRO_EXTRA_RDEPENDS += "hello"


注意,变量名以 RDEPENDS 结尾,而不是 DEPENDS。这行代码令 bitbake 将 hello 包(由上文的 bitbake 文件描述)添加到发行版中。



点击看大图


重新构建


要使用新添加的包重新构建,依次运行 make updatemake openmoko-devel-image。完成之后,可以在实际的手机或 QEMU 中使用新的映像(对于 QEMU,使用 make flash-qemu-local “刷新” 虚拟手机而使用 make run-qemu-snapshot 运行)。在终端运行 hello 将输出一条消息


这样就完成了一个简单的示例程序,觉得如何呢?



小型 GTK+ 应用程序


为什么选择 GTK+


很多用户,尤其那些实现过其他小型嵌入式系统的用户,都希望知道为什么他们不能使用 Qt。是的,您可以使用,但不应该使用。GTK+ 和 Qt 均被构建为共享库;这意味着您系统中的每个 GTK+ 应用程序在内存中使用同一个编译后的 GTK+ 代码的副本,而每个 Qt 应用程序使用同一个 Qt 副本。系统附带的所有标准应用程序都使用 GTK+,因此,您的应用程序如果使用 GTK+ 就可以利用这些已经装载了的代码。Qt 应用程序在这种环境中需要更高的开销。这就好比一个国家决定在马路的左侧还是右侧驾驶;尽管两种方案都有争议,但是必定会遵从特定马路上所有人做出的相同 选择。



点击看大图


小型 GTK+ 程序


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


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


清单 3. 一个小型 GTK 程序

                    
#include <gtk/gtk.h>

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() 规则类似于:


清单 4. 完整的编译器命令行

                    
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 目录?


对于一个只包括 <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+ 应用程序的编译和链接选项是否正确。现在,让我们来添加一些功能。



点击看大图


系统信息


系统信息 applet


浏览 OpenMoko 站点时,我发现了一个用于基于 stylus 的系统信息 applet 的设计文档。看上去非常有趣!可以很轻松地编写程序获得并显示系统信息,这种程序听上去十分有用。



点击看大图


表格布局


我习惯使用表格布局,因此我首先使用了它。GTK+ 表格布局可以让您将窗口划分为一个网格,然后将对象放入网格的单元格中。比较有趣的 API 块是 gtk_table_attach(),它具备以下原型:


清单 5. 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 按钮(在模拟器中运行时添加)。



点击看大图


构建并填充表格


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


清单 6. 创建一个表格

                    
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 按钮的代码,添加这个按钮可以更轻松地关闭应用程序:


清单 7. 填充表格

                    
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+ 教程代码中使用它们可以方便以后的读者进行识别和理解。


清单 8. 退出 GTK 应用程序

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

static gboolean
destroy(void)
{
gtk_main_quit();
}



点击看大图


装配程序


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


清单 9. 将按钮连接到回调函数

                    
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()!代码非常简单:


清单 10. 读取系统版本信息

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


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



点击看大图


向表格添加标签


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


清单 11. 显示一个标签

                    
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() 不仅显示窗口,还显示其中包含的所有内容(在本例中包含一个表格、两个标签和一个按钮)。完整的结果代码如下所示:


清单 12. 完整的小型程序

                    
#include <gtk/gtk.h>
#include <sys/utsname.h>

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() 系统调用获得可用的磁盘空间。这个函数将生成一个包含磁盘当前使用情况的字符串:


清单 13. 查找空闲的磁盘空间

                    
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),然后将它转换为百分数。注意,错误检查功能另外需要使用 <string.h> 头文件,用于 strcpy()



点击看大图


插入信息


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


清单 14. 更多标签

                    
/* 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()。它需要哪些参数呢?表格、行、描述性标签(位于左侧)和标签中的文本(位于右侧)。因此:


清单 15. 提取公共内容

                    
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()


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


清单 16. 修改过的更加简单的代码

                    
/* 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)。


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


清单 17. 查找内存使用信息

                    
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。


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


清单 18. 获得平均负载

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


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


清单 19. 显示平均负载

                    
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);



点击看大图


程序构建完成


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


图 2. 系统信息 applet
系统信息 applet

点击看大图



参考资料

学习

文章评论0条评论)

登录后参与讨论
我要评论
0
6
关闭 站长推荐上一条 /2 下一条