3个月了,终于把这个问题搞定了,前后学习加动手可能花了1个半月在这个系统搭建上面。我怀着复杂的心情写这篇文章,对很多懂这方面的专家我无话可说,对自己我更不知道该爱还是该恨。
6月中的时候我来到新的公司,接到的第一个任务就是交叉编译dbus,并在目标机上跑起来。我工作快3年了,一直搞的嵌入式开发,说实话在FSK很失败,以前开发环境都是搭好了,我们只是做一些应用程序的开发而已,我自认为最有成就的就是写了LCD和LED驱动。新公司真的有很多牛人,软硬件都懂,自己开始有点自卑。对第一个任务我甚至不知道怎么搭建嵌入式交叉编译环境,指定一个交叉编译工具链都花了我半天的时间,更不要说交叉编译dbus这么复杂的东西了,依赖的库太多,遇到的编译错误也让我摸不着头脑。幸好,这些东西做过就会了,在师傅的带领下我花了10天的时间把dbus交叉编译搞定。编译过程如下:
好了,该运行测试程序,运行的时候发现一些错误一个个解决掉,发现通信有问题。(其实就是交叉编译里面的一个隐患造成的)
接下来的1个月被师傅叫去接别的任务,说这个问题可以先缓一下以后解决。1个月后又开始接着搞,这次的任务是要把input subsystem、udev、hal、dbus全部搭建起来,预期的的效果是,当触摸屏有按键事件的时候能通过input驱动-> hal(addon)->dbus并被应用程序接收到。这个架构我花了3天的事件看,并找了一些资料,感觉给我的2个礼拜应该足够了,可是接下来并没有想象的那么顺利。
重新运行dbus-daemon后台进程,感觉OK, 但是我这里有个测试程序收发消息确不能向网上所说的那么顺畅:
/********************************************************************************/
/*
* Example low-level D-Bus code.
* Written by Matthew Johnson <>
*
* This code has been released into the Public Domain.
* You may do whatever you like with it.
*/
#include
#include
#include
#include
#include
/**
* Connect to the DBUS bus and send a broadcast signal
*/
void sendsignal(char* sigvalue)
{
DBusMessage* msg;
DBusMessageIter args;
DBusConnection* conn;
DBusError err;
int ret;
dbus_uint32_t serial = 0;
printf("Sending signal with value %s\n", sigvalue);
// initialise the error value
dbus_error_init(&err);
// connect to the DBUS system bus, and check for errors
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn) {
exit(1);
}
// register our name on the bus, and check for errors
ret = dbus_bus_request_name(conn, "test.signal.source", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
exit(1);
}
// create a signal & check for errors
msg = dbus_message_new_signal("/test/signal/Object", // object name of the signal
"test.signal.Type", // interface name of the signal
"Test"); // name of the signal
if (NULL == msg)
{
fprintf(stderr, "Message Null\n");
exit(1);
}
// append arguments onto signal
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &sigvalue)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send the message and flush the connection
if (!dbus_connection_send(conn, msg, &serial)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
printf("Signal Sent\n");
// free the message
dbus_message_unref(msg);
}
/**
* Call a method on a remote object
*/
void query(char* param)
{
DBusMessage* msg;
DBusMessageIter args;
DBusConnection* conn;
DBusError err;
DBusPendingCall* pending;
int ret;
bool stat;
dbus_uint32_t level;
printf("Calling remote method with %s\n", param);
// initialiset the errors
dbus_error_init(&err);
// connect to the system bus and check for errors
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn) {
exit(1);
}
// request our name on the bus
ret = dbus_bus_request_name(conn, "test.method.caller", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
exit(1);
}
// create a new method call and check for errors
msg = dbus_message_new_method_call("test.method.server",// target for the method call
"/test/method/Object", // object to call on
"test.method.Type", // interface to call on
"Method"); // method name
if (NULL == msg) {
fprintf(stderr, "Message Null\n");
exit(1);
}
// append arguments
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, ¶m)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send message and get a handle for a reply
if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) { // -1 is default timeout
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (NULL == pending) {
fprintf(stderr, "Pending Call Null\n");
exit(1);
}
dbus_connection_flush(conn);
printf("Request Sent\n");
// free message
dbus_message_unref(msg);
// block until we recieve a reply
dbus_pending_call_block(pending);
// get the reply message
msg = dbus_pending_call_steal_reply(pending);
if (NULL == msg) {
fprintf(stderr, "Reply Null\n");
exit(1);
}
// free the pending message handle
dbus_pending_call_unref(pending);
// read the parameters
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_BOOLEAN != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not boolean!\n");
else
dbus_message_iter_get_basic(&args, &stat);
if (!dbus_message_iter_next(&args))
fprintf(stderr, "Message has too few arguments!\n");
else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not int!\n");
else
dbus_message_iter_get_basic(&args, &level);
printf("Got Reply: %d, %d\n", stat, level);
// free reply
dbus_message_unref(msg);
}
void reply_to_method_call(DBusMessage* msg, DBusConnection* conn)
{
DBusMessage* reply;
DBusMessageIter args;
bool stat = true;
dbus_uint32_t level = 21614;
dbus_uint32_t serial = 0;
char* param = "";
// read the arguments
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message has no arguments!\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not string!\n");
else
dbus_message_iter_get_basic(&args, ¶m);
printf("Method called with %s\n", param);
// create a reply from the message
reply = dbus_message_new_method_return(msg);
// add the arguments to the reply
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &stat)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &level)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
// send the reply && flush the connection
if (!dbus_connection_send(conn, reply, &serial)) {
fprintf(stderr, "Out Of Memory!\n");
exit(1);
}
dbus_connection_flush(conn);
// free the reply
dbus_message_unref(reply);
}
/**
* Server that exposes a method call and waits for it to be called
*/
void listen()
{
DBusMessage* msg;
DBusMessage* reply;
DBusMessageIter args;
DBusConnection* conn;
DBusError err;
int ret;
char* param;
printf("Listening for method calls\n");
// initialise the error
dbus_error_init(&err);
// connect to the bus and check for errors
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn) {
fprintf(stderr, "Connection Null\n");
exit(1);
}
// request our name on the bus and check for errors
ret = dbus_bus_request_name(conn, "test.method.server",
DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
fprintf(stderr, "Not Primary Owner (%d)\n", ret);
exit(1);
}
// loop, testing for new messages
while (true) {
// non blocking read of the next available message
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// loop again if we haven't got a message
if (NULL == msg) {
sleep(1);
continue;
}
// check this is a method call for the right interface & method
if (dbus_message_is_method_call(msg, "test.method.Type", "Method"))
reply_to_method_call(msg, conn);
// free the message
dbus_message_unref(msg);
}
}
/**
* Listens for signals on the bus
*/
void receive()
{
DBusMessage* msg;
DBusMessageIter args;
DBusConnection* conn;
DBusError err;
int ret;
char* sigvalue;
printf("Listening for signals\n");
// initialise the errors
dbus_error_init(&err);
// connect to the bus and check for errors
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (NULL == conn) {
exit(1);
}
// request our name on the bus and check for errors
ret = dbus_bus_request_name(conn, "test.signal.sink", DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Name Error (%s)\n", err.message);
dbus_error_free(&err);
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
exit(1);
}
// add a rule for which messages we want to see
dbus_bus_add_match(conn, "type='signal',interface='test.signal.Type'", &err); // see signals from the given interface
dbus_connection_flush(conn);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Match Error (%s)\n", err.message);
exit(1);
}
printf("Match rule sent\n");
// loop listening for signals being emmitted
while (true) {
// non blocking read of the next available message
dbus_connection_read_write(conn, 0);
msg = dbus_connection_pop_message(conn);
// loop again if we haven't read a message
if (NULL == msg) {
sleep(1);
continue;
}
// check if the message is a signal from the correct interface and with the correct name
if (dbus_message_is_signal(msg, "test.signal.Type", "Test")) {
// read the parameters
if (!dbus_message_iter_init(msg, &args))
fprintf(stderr, "Message Has No Parameters\n");
else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args))
fprintf(stderr, "Argument is not string!\n");
else
dbus_message_iter_get_basic(&args, &sigvalue);
printf("Got Signal with value %s\n", sigvalue);
}
// free the message
dbus_message_unref(msg);
}
}
int main(int argc, char** argv)
{
char* param = "no param";
printf("#Kluter T1:argc=%d\n", argc);
if (2 > argc)
{
printf ("Syntax: dbus-example [send|receive|listen|query] [
]\n");
return 1;
}
if (3 >= argc && NULL != argv[2])
param = argv[2];
if (0 == strcmp(argv[1], "send"))
sendsignal(param);
else if (0 == strcmp(argv[1], "receive"))
receive();
else if (0 == strcmp(argv[1], "listen"))
listen();
else if (0 == strcmp(argv[1], "query"))
query(param);
else
{
printf ("Syntax: dbus-example [send|receive|listen|query] [
]\n");
return 1;
}
return 0;
}
/****************************************************************************/
PS:这是一个Dbus典型的测试程序, 交叉编译和makefile的部分我在以前的文章已经讲了,对高手来说应该不是问题了吧。
最初这个程序的dbus_bus_get()这个函数的第一个参数都是用的DBUS_BUS_SESSION这个参数,我也糊里糊涂的,也没有觉得用这个参数有什么不妥。但是我这样在开发板上运行(可执行文件为eg2):
#eg2 receive &
#eg2 send abcdefg
始终不能在接收端收到消息,郁闷ing~ 无意之中看到一篇文章说要起一个dbus-launch, 这个launch其实就是去launch了一个dbus-daemon, 当然这个launch起来的daemon是以session bus跑起来的。看到这个可能很多人应该明白了是什么原因了吧, 可当时的我还是糊里糊涂的。可是这样还是不行的,还要看dbus-launch官方介绍的文章:
里面提到了一点:
## test for an existing bus daemon, just to be safe
if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then
## if not found, launch a new one
eval `dbus-launch --sh-syntax --exit-with-session`
echo "D-Bus per-session daemon address is: $DBUS_SESSION_BUS_ADDRESS"
fi
莫名其妙的我打了一个eval `dbus-launch --sh-syntax --exit-with-session` 哈哈终于可以收发消息了!!高兴了1个小时,以为成功了。殊不知这个只是session bus通过了测试,后来发现问题之后又仔细阅读dbus-launch那篇文章,比对每一个参数等等过程不在描述了。总之一句话,没有方向,没有头脑,无限郁闷……
我在别人的提示下才知道用dbus_bus_get()这个函数的时候第一个param要用DBUS_BUS_SYSTEM才是合理的,应为整个事件的core是在system bus上面跑的(后来看hald源代码的时候才知道),只有应用程序之间才可以用session来通信,否则就会存在安全隐患,这个是我们金融相关行业最忌讳的东西!
接下来两三天痛苦的摸索着,终于在周末的时候把问题解决了,可是先申明问题不是我解决的,是我同事帮我解决的,他从配置文件和安全性方面入手从头到尾检查我的编译流程,发现在我编译dbus-1.0.2的时候make出错的一个问题我解决得有问题且刚好是和socket的安全问题相关。dbus-sysdeps-unix.c 这个文件中找不到struct ucred, 郁闷后来他告诉我新内核的这个结构体被改掉了,所以加入新kernel结构体
struct ucred {
pid_t pid; /* 发送进程的进程标识 */
uid_t uid; /* 发送进程的用户标识 */
gid_t gid; /* 发送进程的组标识 */
};
把这个该死的问题解决了,还得我和他被拿来和老大比较,当然被批评的那个人大家都应该清楚。。。。。。
dbus编译的问题解决了之后当然赶快试一下hald是否能挂起来,幸运的是果然成功了,应为在hald.c中调用hald_dbus_init_preprobe() 是去init DBUS_BUS_SYSTEM。又去跑文章开头那个测试程序,会报错:
Name Error (Connection ":1.3" is not allowed to own the service "test.signal.sink" due to security policies in the configuration file)
这次很明确了,肯定是配置文件的问题。这里我想说一下,做过这个系统的人应该很清楚这个问题,但是为什么网上没有人清楚的说明一下呢?那些做过的人做完了就算了,也不愿意共享一下资源,愿意共享的也只是把这个问题一带而过,害的我们又要花费半天时间,其实解决了真的觉得很简单一个问题,开始很讨厌OpenSource,很厌烦中国人的孽根性。话说回来,配置文件是在/etc/dbus-1/system.d下面,自己新建一个*.conf文件,我的配置文件内容供大家参考:
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"">
512不要问我为什么,I don't know. Just do it。 老外的东西就是这样要求的。
system bus通信正常之后,就试着使input设备能往上dbus报事件了。大家能感觉得到我说的有多笼统吗?不错当时我只知道这样肯定行得通,但是具体怎么做又是另一回事了……
首先在做这个事情之前,我先花了5天的时间去研究input subsystem,input device driver,本想写一个s3c2440上的按键驱动的,kconfig makefile都改好了,确怎么都没办法驱动起来,师傅说可能要在内核里面先去注册,因此转而用2440开发板自带的触摸屏驱动,它就是用input subsystem实现的,内核驱动的部分见:
http://blog.chinaunix.net/u3/99283/showart.php?id=2024189 我已经将trace代码的注解加进去了。当然开发板fs中自带的qtopia已经加载了tslib,据说是一个触摸屏校准程序(没有深究),也就是说qt帮我们open了/dev/input/event0。(PS:但是这样就违背了我们设计这个系统的初衷,应为大部分公司的大部分产品都是同步侦听这些驱动事件的,但我们系统是异步的。)
根据下面2点,我确定了input设备是由hald侦听的:
1. 参考“理解和使用Linux的硬件抽象层HAL.mht”,很多人转载了这篇文章。其中讲述到“由于不同的硬件,有着很不同的行为,HAL还会通过一些特定的Addon来监听设备的状态变更情况。例如针对input类设备,hald会启动一个 hald-addon-input 的Addon,它的其中一个功能就是监听键盘事件,如果其中有sw类的事件(如sw_lid 因该是表示笔记本盖的开合,好像电源管理会模拟这个事件)则会去修改设备对象对应的状态属性。”
2. 在pc机上#ps -A(省略无关部分)
PID TTY TIME CMD
。。。
6 ? 00:00:00 events/0
7 ? 00:00:00 events/1
1177 ? 00:00:00 udevd
2697 ? 00:00:00 dbus-daemon
2705 ? 00:00:00 hald
2706 ? 00:00:00 hald-runner
2712 ? 00:00:00 hald-addon-acpi
2717 ? 00:00:00 hald-addon-keyb
2730 ? 00:00:00 hald-addon-stor
3042 ? 00:00:00 dbus-launch
3043 ? 00:00:00 dbus-daemon
。。。
但是在我的开发板上只有dbus-daemon和hald hald-runner跑起来了,说明我的系统里面根本就没有去侦听input设备。一时又没有了方向。没办法只有去跟踪hald源代码。hald.c的main()函数中有这样一段:
case 0:
/* child */
dev_null_fd = open ("/dev/null", O_RDWR);
/* ignore if we can't open /dev/null */
if (dev_null_fd >= 0) {
/* attach /dev/null to stdout, stdin, stderr */
dup2 (dev_null_fd, 0);
dup2 (dev_null_fd, 1);
dup2 (dev_null_fd, 2);
close (dev_null_fd);
}
umask (022);
break;
我们必须拿掉那3行dup2才能在console下打印出hald的启动过程。
胡乱看了2遍,随便试了及格hal的执行文件,发现lshal的时候 HAL database里面只记录了1个设备和pc机上列出的72个设备相差甚远(实际上开发板上远没有那么多),又搜了几篇相关文章(搜索能力只有靠自己了哈),我怀疑和udev daemon相关,虽然我很确定input和udev无关,但是事后证明我的猜测是对的。
然后我在启动hald之前又加上了udevd --daemon 和 udevstart两条命令。果然lshal的时候列出了34个设备,其中包括input 触摸屏的:
udi = '/org/freedesktop/Hal/devices/computer_logicaldev_input'
info.capabilities = {'input', 'input.touchpad'} (string list)
info.category = 'input' (string)
info.parent = '/org/freedesktop/Hal/devices/computer' (string)
info.product = 's3c2410 TouchScreen' (string)
info.udi = '/org/freedesktop/Hal/devices/computer_logicaldev_input' (string)
input.device = '/dev/input/event0' (string)
input.product = 's3c2410 TouchScreen' (string)
linux.device_file = '/dev/input/event0' (string)
linux.hotplug_type = 2 (0x2) (int)
linux.subsystem = 'input' (string)
linux.sysfs_path = '/sys/devices/virtual/input/input0/event0' (string)
既然input设备已经加载到了hal database,那么是不是hald开始侦听它了呢?结果ps的时候也没有出现类似pc上的“2717 ? 00:00:00 hald-addon-keyb”这个进程,又郁闷了……
感觉离胜利很近了却偏偏不知道问题出在哪里,开源的东西真的很烦。不过至少让我确定了启动hald-input-addon这个daemon的方向是正确的。
做事一定要执着,要永不放弃,继续阅读hal spec,到网上找文章,终于让我发现要写一个*.fdi的文件的文章,窃喜,因为很可能是这个原因。修改了/usr/share/hal/fdi/policy/10osvendor中的10-input-policy.fdi文件:
Yeah! ps之后终于在addon-input终于在后台通过hald-runner跑起来了!
后来的事情就会轻松很多,到dbus上收input事件的消息只要跟踪一下addon-input.c这些代码就知道是怎么回事了,这里不再解释了。
总之,我写这篇文章的目的不是说我有多懂,我在这个月没有少挨过批评教育,没有少煎熬过,甚至一度想通过离职来解脱,我也在网上1000圆征求具体解决方案,可是逃避又能怎样呢?除非我这辈子都不搞技术了。我只是希望做过开源或者一些通用平台搭建的朋友能把你们的心得和遇到的问题都无私的奉献出来,中国的开源项目才步入起步阶段,大公司里面凭借雄厚的实力已经非常稳定了,可是小公司和在校的朋友可能根本就没有机会接触。虽然说只有靠自己解决问题才是生存之道,但是凭心而论,有几个高人是一开始就懂这么多呢?还不是从借鉴开始的。
在解决这些问题的过程中我就走了很多弯路,甚至某段时间导致问题是发散的,其实解决的方法就在那些英文文档和英文论坛里面,只有概念性的东西我们中国人才喜欢写,其实那些东西都没有多大意义,不外乎告诉别人你懂很多,真正困扰我们的是做的时候遇到的难题,希望一些通性的东西大家共享,opensource才会有更多高人加入进来!
欢迎有识之士交流探讨