Maemo Linux手机平台系列分析:(14) Maemo平台开发之 设计D-Bus server时要注意的若干问题

本文分析了Maemo Linux手机平台上的D-Bus server开发,强调了服务器设计要点,包括精灵化、后台化操作,事件循环与电量管理,以及支持并行处理。文中提到避免长时间高频定时器,采用事件驱动模型处理多事件源,同时讨论了多线程和fork进程处理请求的优缺点,为开发者提供了宝贵的实践指导。
 
 
设计 D-Bus server 时要注意的若干问题
 
  • Server的定义
  • 精灵化,后台化
  • 事件循环和电量消耗
  • 支持并行处理能力
  • 调试
Server 的定义
就软件而言, server 一般理解成:能为 client 提供服务的软件组件。在 Linux 中, server 一般是 daemon 的形式运行的。 Daemon 是一个术语: daemon 是同 terminal 剥离的这么一个进程,一般运行于后台,处理一些用户不需要看见的动作。
 
有时候,你可能会听见人们把 server 称作 engine Engine 是个更通用的一个术语, Engine 和如何实现一个服务没有太大的关系 (Engine 可以单独作为一个进程;可以作为一个库,由 client 直接调用 ) 。更广泛点讲: Engine 是应用程序的一部分,主要是实现部分功能,但不是 interface 。在 Model-View-Controller 模型中, Engine 相当于 Model
 
目前为止,我们的 server 程序并不是以 deamon 的形式运行的,主要是想在 terminal/ 屏幕上看到更多的打印信息。如果我们运行 server 程序时,加个参数: "--stay-on-foreground" , 这样,我们的 server 就不会被“精灵化”,而保持在前台运行。对于调试,这是非常重要的。
 
默认情况下,一个 server 程序被 daemonize 后,它的输入 / 输出文件就会关闭了,这时再从 terminal 读取用户输入,或者写数据到 terminal 都会失败 ( 包括 printf g_print)
 
Daemonization( 精灵化 )
把一个进程 process 转化为一个后台 daemon 的目的就是想把它同其父进程剥离开,并为它单独创建一个会话。这样剥离是有好处的: server 的父进程不会自动关闭 server 了。把一个 process 变成 daemon 的过程如下:
  • fork 一个新的子进程,原进程推出,子进程进入init process中;
  • setsid为子进程创建一个新的会话;
  • 从当前工作目录切换至root目录,因此daemon不会阻止文件系统的正常卸载;
  • daemon创建的目录和文件设置umask值,不如其他进程访问。在Internet Tablet产品中,没有这样做,因为只有一个用户;
  • 关闭所有的标准I/O文件描述符,为了在terminal关闭时不致于产生SIGPIPE信号给daemon并且把所有的stdin, stdout, stderr只想/dev/null,这样所有的打印将会丢失掉。
函数 daemon 可以让你切换目录并且关闭已经打开的文件描述符:
#ifndef NO_DAEMON
 
 /* This will attempt to daemonize this process. It will switch this
     process' working directory to / (chdir) and then reopen stdin,
     stdout and stderr to /dev/null. Which means that all printouts
     that would occur after this, will be lost. Obviously the
     daemonization will also detach the process from the controlling
     terminal as well. */
  /* daemon函数用于把一个进程daemon化,它主要做2件事:1 切换当前目录到root目录;2 把stdin,stdout,stderr定位到/dev/null; 很明显,daemon处理后,当前进程就从其控制terminal剥离开了。D-Bus的daemon化,不是调用daemon函数的,而是遵循了上面的步骤,一步步搞的。*/
 if (daemon(0, 0) != 0) {
    g_error(PROGNAME ": Failed to daemonize./n");
 }
#else
 g_print(PROGNAME
          ": Not daemonizing (built with NO_DAEMON-build define)/n");
#endif
 
Makefile 控制是否 daemon 处理:
# -DNO_DAEMON : do not daemonize the server (on a separate line so can
#               be disabled just by commenting the line)
ADD_CFLAGS += -DNO_DAEMON
 
# Combine flags
CFLAGS := $(PKG_CFLAGS) $(ADD_CFLAGS) $(CFLAGS)
如果不想修改 makefile ,而编译出不同的版本时,可以采用 -U 选项:
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > CFLAGS='-UNO_DAEMON' make server
dbus-binding-tool --prefix=value_object --mode=glib-server /
 value-dbus-interface.xml > value-server-stub.h
cc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include
   -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include
   -g -Wall -DG_DISABLE_DEPRECATED -DNO_DAEMON -UNO_DAEMON
   -DPROGNAME=/"server/" -c server.c -o server.o
cc server.o -o server -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
gcc 是从左到用处理所有的 -D -U 选项的,加上 CFLAGS='-UNO_DAEMON' ,会把 makefile 中定义的宏变量取消掉。当然,你也可以直接修改 makefile.
下面我们去掉 (&), 直接运行 :
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > run-standalone.sh ./server
server:main Connecting to the Session D-Bus.
server:main Registering the well-known name (org.maemo.Platdev_ex)
server:main RequestName returned 1.
server:main Creating one Value object.
server:main Registering it on the D-Bus.
server:main Ready to serve requests (daemonizing).
[sbox-CHINOOK_X86: ~/glib-dbus-sync] >
 
由于 server 的信息看不到,我们用别的方法去查看 server 是否正在运行:
[sbox-CHINOOK_X86: ~/glib-dbus-sync] > ps aux | grep "/./server" | grep -v pts
user 8982 0.0 0.1 2780 664 ? Ss 00:14 0:00 ./server
上面 grep 的用法稍微有点费解。
 
事件循环和电量消耗
现在大部分的 CPU 都有多级省电的方式可选。每一级省电的能力各不相同,主要是考虑到价格的因素在内。对手机来说,能做好省电这块工作,很有意义:增加待机时间,这也是消费者比较关心的问题。
 
频繁的切换 CPU 的状态会耗电,要避免这样做。为了省电,我们要尽量地减少 CPU 的高负荷运转。
 
如何减少 CPU 的调用呢?这里我们先比较两种编程方法:基于事件编程和基于轮询 (polling) 编程。轮询方式:我们持续检查(轮询)我们关系的事件,只有在相关事件发生时,才采取动作。由于持续的检查, CPU 也会频繁的工作,我们需要尽力避免这个方案。
 
相对于轮询方式,基于事件的编程不需要单独的 polling 循环,至于当关心的事件发生时才调用回调函数。这就有个问题:何时激活回调。使用定时器看起来是个简单的办法。定时去查询我们关心的状态。这种模式也不好,由于频繁的使用定时器,导致 CPU 不能够进入深睡眠模式。
 
现在大部分操作系统内核都提供了唤醒机制:当所需数据可用时,唤醒该进程,否则退出当前运行队列。在 Linux 系统中,最常用的就是围绕 select/poll 的机制。在 Linux 中,很多介质都被称作“ file” 如果你的程序使用 Glib( 或者 GTK) 编程,你可以直接使用 GMainLoop, 这个对象内部已经封装了事件机制 (select/poll/ 其它 ) ,你所需要做的就是给特定的事件注册回调;启动主循环等。
 
如果你使用了一些文件描述符 (network sockets, open files, etc), 你可以通过 GIOChannels 把它们集成到 GMainLoop 中。
对于使用定时器的回调函数,你要注意避免下面的问题 :
  • 长时间(> 5)使用频率过高(> 1 HZ)的定时器;
  • 当某个事件发生时再触发其回调函数。不要自己手动去轮询那个事件。
举个例子:前面我们介绍过 LibOSSO 的一个程序: FlashLight ,这个程序使用定时器来保持背光。不过,那个定时器很慢 (45 ) ,因此,对于 CPU 的影响不是什么大的问题,不至于对电池电量造成什么太大的负面影响。
 
对于程序可能有很多界面,当由很多程序同时运行时,处于后台的程序不必老是更新,只更新前台 (top) 的程序界面,这样处理会更省电。 LibOSSO 支持这种处理方式。
 
另外一方面,由于 server 并没有界面,并不适合上面的处理方法。当 server 没有任何处于活动状态的 client 时,避免使用定时器。只有 client server 建立了 connection, server 再去使用相关的资源,当完成操作后,把相关资源释放。
 
如果可能的话,尽量使用 system bus D-Bus 信号来关闭一些活动。即使你处理的是一个无界面的 server, 也要监听系统关闭的信号,然后能安然关闭你的 server, 不至于导致一些崩溃。
 
总之,设计一个低耗电的产品(或者 server )并不是一件简单的事情,有 4 个原则可以遵守:
  • 尽量避免做额外的工作
  • 尽快做完
  • 尽可能少地调用它
  • 只要必须的资源,其它的不必多揽
对于 GUI 程序来讲,还要考虑“图形侧”的问题,这也是耗电的大户,尤其是刷新大屏。
 
支持并行请求操作
前面的例子中,带有延时的 server 有个主要的缺点:一次只能处理一个请求,其它的请求都要在外面等待。这样就会出现新的问题:一个 server 有不同的 client, 并且同时访问 server, 这个问题怎么解决呢?
通常情况下,通过在消息分发机制 ( 这里是 libdbus) 基础上增加一些复用机制来实现对并行操作的支持。大概有下面三种模型:
  • 1 为每个request启动一个单独的线程去处理它。这种方法看起来很简单,但是如何处理多个线程访问共享资源?而且多线程的调试更困难点。
  • 2 使用一个基于事件驱动的模型,来同时处理多个事件源,并且只有一个事件处于唤醒状态。在这种情况下,“select”和“poll”是最常用的。基于事件的方法比基于线程的方法要好:因为它不涉及到同步的问题,只需要在kernel和用户空间之间切换上下文。Glib在基于事件编程模型基础上做了封装,提供GMainLoop对象来处理事件。你可以使用GIOChannel对象来表示一个事件源,并且给它注册回调函数。
  • 3 使用fork去创建一份server进程的拷贝进程,这个新的进程仅处理一个request,并且在处理完后,退出该进程。这个方法所面临的问题:要额外创建进程、进程之间共享数据的同步。因此你必须要做到进程之间的同步。许多静态的web服务器使用这种模型,因为他们不必去共享数据。
引起 server 比较慢的原因在于: Glib/D-Bus 本身不能直接支持并行处理。即使使用 fork 模型,也会面临一个问题:多个进程访问同一个 D-Bus connection 。因此,你如果使用 Glib/D-Bus ,你需要自行把你自己的“复用”代码加入。
 
这种情况下,拿上面任何一种方法,并结合 libdbus 。这需要重写 server ,暂时舍弃 GType, 可能也会创建出一个轻量级的实现:把 libdbus 函数集成到 Glib GMainLoop 中。不支持 GType, 意味这你必须自己实现自省功能。
 
另外一种方案:伪造 client 调用的完成。这样 RPC 方法可以立马完成,但是 server 侧仍然在继续处理。这个方案的困难之处:很难知道哪个 client 对应于哪个响应。这样的话, server 需要把处理后的结果用信号广播给所有的 client, 这样会吧所有的 client 都唤醒的,即使大部分对该信号并不关系。
 
一句话,当你使用 Glib/D-Bus 时,没有简单的方案来处理并行操作。
 
调试
调试你的 server 最简单的方法就是使用打印。在 Maemo 中,如果 server 不是以 deamon 的形式运行的,则打印出关系的信息,如果是以 daemon 的形式运行的,这些打印信息不会打印。你可以把这个 idea 扩展:支持多级打印。
_func__ __file__ 比较有用:
/* A small macro that will wrap g_print and expand to empty when
   server will daemonize. We use this to add debugging info on
   the server side, but if server will be daemonized, it doesn't
   make sense to even compile the code in.
 
   The macro is quite "hairy", but very convenient. */
#ifdef NO_DAEMON
#define dbg(fmtstr, args...) /
 (g_print(PROGNAME ":%s: " fmtstr "/n", __func__, ##args))
#else
#define dbg(dummy...)
#endif
 
Maemo 中用的打印方法还是比较简单的,我前面的一些文章对这方面做了些总结:
 
 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值