设计
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
中用的打印方法还是比较简单的,我前面的一些文章对这方面做了些总结:
本文分析了Maemo Linux手机平台上的D-Bus server开发,强调了服务器设计要点,包括精灵化、后台化操作,事件循环与电量管理,以及支持并行处理。文中提到避免长时间高频定时器,采用事件驱动模型处理多事件源,同时讨论了多线程和fork进程处理请求的优缺点,为开发者提供了宝贵的实践指导。
6005

被折叠的 条评论
为什么被折叠?



