DBus使用非缺省Glib的maincontext的问题
MMI中间层封装库已经移植到linux平台下稳定运行一两个月了,最近在给其加入插件系统时,却遇到了一点麻烦。插件中的数据库数据变化的Glib信号处理函数在其他应用对数据库进行插入、修改等操作时不会被调用,即无法接收到数据变化的Glib信号。 我们的数据库封装采用C/S架构,客户端使用DBus和服务端通讯,当数据库的数据发生变化时(插入、修改、删除)时,服务端通过DBus向客户端发送
·
MMI中间层封装库已经移植到linux平台下稳定运行一两个月了,最近在给其加入插件系统时,却遇到了一点麻烦。插件中的数据库数据变化的Glib信号处理函数在其他应用对数据库进行插入、修改等操作时不会被调用,即无法接收到数据变化的Glib信号。
我们的数据库封装采用C/S架构,客户端使用DBus和服务端通讯,当数据库的数据发生变化时(插入、修改、删除)时,服务端通过DBus向客户端发送相应的Glib信号,通知客户端数据发生变化。MMI中间层也是采用C/S架构,也使用DBus实现客户端和服务端的通讯,如接收应用的请求,返回处理结果,主动上报GSM模组的事件。MMI是一个多线程的服务进程,其主线程获取了一个DBus连接,用来和客户端的通讯。在我们的设计中,MMI还要操作数据库,监控数据库的数据变化,如接收到新短信后,需要合并长短信,将短信存储到数据库中。这些和数据库的交互我们将其交给插件去实现,简化了MMI中间层的实现,和数据库解耦,并使得MMI中间层可以较容易的移植到Nucleus等嵌入式实时操作系统上,这些系统一般没有数据库。MMI中间层的插件加载后,通过数据库封装的持久化对象也获取了一个DBus连接,用来和数据库服务端通讯。现在的问题是,插件获取的 DBus连接不能接收到数据库服务端发出的数据变化的Glib信号,通过调试信息的输出,发现数据库服务端在数据变化时已确实发出了Glib信号。因此,问题应该出在DBus的信号发送过程上。
年初的时候花了一段时间看DBus的代码,对DBus的机制和实现有了一定的了解。为了简化问题的查找和重现,我使用DBus的Glib封装自带的信号发送和接收的例子,将其修改为多线程程序,模拟MMI插件方式使用DBus,编译、运行。从问题的现象看,感觉是我们获得的DBus连接是共享的导致的问题,共享的连接使用同一个消息派发函数和DBus的watch,可能是这个导致Glib信号不能接收。于是子线程改为获取私有的连接,但还是不行。于是重新检查代码,觉得应该和子线程的DBus没有绑定到缺省的GThread的maincontext有关,因为之前我们发现子线程若使用缺省的maincontext在和主线程用管道等进行通讯时会有问题,因此子线程使用非缺省的maincontext。为了将DBus绑定到非缺省的maincontext,在调用函数dbus_g_bus_get()后,调用函数dbus_connection_setup_with_g_main(),而DBus绑定到缺省的maincontext时,不需要调用后面这个函数,此时是能接收到Glib信号的,看来是调用了函数dbus_connection_setup_with_g_main()引起问题的。在函数dbus_g_bus_get()中,为了将获取的DBus连接绑定到缺省的maincontext,已经调用了一次函数dbus_connection_setup_with_g_main(),传入的maincontext为NULL。当我们要将DBus连接绑定到非缺省的maincontext时,再次调用了函数dbus_connection_setup_with_g_main(),将DBus连接绑定到传入的maincontext上,似乎这次绑定没有成功。但DBus作为一个使用已较为广泛的软件,而且为了查找问题,我使用了DBus的1.0.2版本,Glib封装的0.72版本,应该不会出现API函数调用失效的问题吧,是否我们哪里使用不当呢。让我们深入函数dbus_connection_setup_with_g_main(),看看DBus是如何绑定到maincontext上的,如何接收Glib信号,并调用Glib信号处理函数的。
在深入分析之前,我们先看看DBus的是如何与Glib的mainloop关联的,如何监控传输端口的。DBus是一个按面向对象设计和实现的软件,因此我将这些C结构称之为对象。描述DBus连接的对象类型是DBusConnection,其Glib封装的对象类型是DBusGConnection。该对象中有一个名为watches的DBusWatchList类型的链表,这是由所有传输端口监控器(按其英文原意我称之为看守者对象)组成的链表,还有一个Glib封装使用的对象数据ConnectionSetup,通过函数dbus_connection_get_data()和dbus_connection_set_data()来获取和设置。
看守者对象(DBusWatch)监控传输端口,该传输端口的文件描述符由成员fd保存,看守对象的处理函数及处理函数的用户数据由函数指针成员handler和数据指针handler_data保存。看守者对象如何与Glib的mainloop关联呢,关键就在其对象数据data中,data指向一个IOHandler类型的对象,IOHandler对象类型中有一个成员source指向GSource类型的对象。熟悉Glib的朋友都知道GSource是Glib的mainloop的源,mainloop在运行时会遍历所有绑定在其上的源,当某个源可用时(对传输端口而言是数据已发送或接收到数据),会调用这个源的回调函数,在IOHandler类型的对象中,这个回调函数就是io_handler_dispatch()。这个函数调用DBusWatch类型的函数dbus_watch_handler(),最终调用看守对象的处理函数,实现传输端口数据发送和接收的处理,在我们的这个问题中,则是进行数据接收的处理。当服务端的Glib对象发送出一个导出到客户端的Glib的信号时,DBus将该信号打包为一个DBus的signal类型的消息,通过传输端口经DBus后台进程转发传送到客户端。当客户端接收到这个signal类型的消息时,通过上述的mainloop机制,看守者对象的处理函数将被调用,该处理函数将signal类型的消息解包并将其转化为Glib信号发送出去,应用相应的Glib信号处理函数就会调用。
链表watches中有几个函数指针add_watch_function,remove_watch_function,watch_toggled_function,分别指向Glib封装实现的对应函数,当向watches增加watch、从watches移除watch、触发watches中的watch时,会调用相应的函数指针指向的函数,完成Glib封装附加的处理。当调用函数dbus_connection_setup_with_g_main(),将DBus连接绑定到指定的maincontext时,需要调用函数dbus_connection_set_watch_function(),将Glib封装实现的add_watch、remove_watch、watch_toggled这几个函数设置到watches的函数指针中。Glib封装实现的add_watch函数将看守者绑定到指定的maincontext中,该函数创建了一个IOHandler类型的对象,并将该对象指向的GSource类型的对象绑定到DBus连接要绑定到的maincontext中。Glib封装实现的remove_watch函数将看守者从指定的maincontext中脱离,该函数将IOHandler指向的GSource类型的对象从maincontext的绑定脱离,并销毁这个GSource类型的对象。Glib封装实现的watch_toggled函数与我们的问题无关,就不介绍了。函数dbus_connection_set_watch_function()除了将Glib封装实现的函数设置到watches的函数指针中,还对watches中的看守者逐个调用add_watch函数将其绑定到指定的maincontext中,逐个调用remove_watch函数将其从指定的maincontext中脱离。
了解了DBus和Glib的mainloop的绑定机制,我们就可以来分析我们的问题了。前面的初步分析已经指出在再次调用函数dbus_connection_setup_with_g_main()将DBus连接绑定到指定的maincontext上后,客户端不能接收Glib信号。从上面的绑定机制来看,则是重新设置了add_watch、remove_watch、watch_toggled这几个函数,并对watches的每个看守者对象多调用了一次函数add_watch和remove_watch。给函数指针设置新的函数是不会有问题的,问题应该是多一次的add_watch和remove_watch函数调用引起的。于是在函数add_watch和remove_watch中加入调试信息的打印。在函数add_watch中,创建IOHandler类型的对象,将该对象的source对象绑定到maincontext时,打印出该source对象的内存地址。在函数remove_watch中,将IOHandler中的source对象从maincontext脱离并销毁时,打印出该source对象的内存地址。再次运行程序后,发现第二此调用函数dbus_connection_setup_with_g_main()时,函数remove_watch销毁的source对象和函数add_watch创建的IOHandler中的source对象是同一个对象。因函数remove_watch在函数add_watch之后调用,最终这个source对象是被销毁了,不能被mainloop遍历,也就无法监控传输端口的数据了,导致客户端不能接收服务端发送出的Glib信号。这就是问题的根本原因。
问题的原因找到了,但是却很不好解决,主要是看守者对象只有一个对象数据data用来保存IOHandler类型的对象,也就只有一个source对象和其关联。当add_watch函数将创建的IOHandler类型的对象保存到data中后,为了将看守者对象从原先的maincontext脱离,对该看守者调用remove_watch函数时将和其关联的source对象销毁时,销毁的是新创建的source对象,无法区别对待。为解决这个问题,我们只好重新写了一个获取DBus连接的函数dbus_g_bus_get_private()以获取私有的DBus连接,并将该连接绑定到指定的非缺省的maincontext中。这个函数只调用一次dbus_connection_setup_with_g_main()函数,避免出现看守者关联的source对象被销毁的问题。不知各位有否其他解决方法,请不吝赐教。
我们的数据库封装采用C/S架构,客户端使用DBus和服务端通讯,当数据库的数据发生变化时(插入、修改、删除)时,服务端通过DBus向客户端发送相应的Glib信号,通知客户端数据发生变化。MMI中间层也是采用C/S架构,也使用DBus实现客户端和服务端的通讯,如接收应用的请求,返回处理结果,主动上报GSM模组的事件。MMI是一个多线程的服务进程,其主线程获取了一个DBus连接,用来和客户端的通讯。在我们的设计中,MMI还要操作数据库,监控数据库的数据变化,如接收到新短信后,需要合并长短信,将短信存储到数据库中。这些和数据库的交互我们将其交给插件去实现,简化了MMI中间层的实现,和数据库解耦,并使得MMI中间层可以较容易的移植到Nucleus等嵌入式实时操作系统上,这些系统一般没有数据库。MMI中间层的插件加载后,通过数据库封装的持久化对象也获取了一个DBus连接,用来和数据库服务端通讯。现在的问题是,插件获取的 DBus连接不能接收到数据库服务端发出的数据变化的Glib信号,通过调试信息的输出,发现数据库服务端在数据变化时已确实发出了Glib信号。因此,问题应该出在DBus的信号发送过程上。
年初的时候花了一段时间看DBus的代码,对DBus的机制和实现有了一定的了解。为了简化问题的查找和重现,我使用DBus的Glib封装自带的信号发送和接收的例子,将其修改为多线程程序,模拟MMI插件方式使用DBus,编译、运行。从问题的现象看,感觉是我们获得的DBus连接是共享的导致的问题,共享的连接使用同一个消息派发函数和DBus的watch,可能是这个导致Glib信号不能接收。于是子线程改为获取私有的连接,但还是不行。于是重新检查代码,觉得应该和子线程的DBus没有绑定到缺省的GThread的maincontext有关,因为之前我们发现子线程若使用缺省的maincontext在和主线程用管道等进行通讯时会有问题,因此子线程使用非缺省的maincontext。为了将DBus绑定到非缺省的maincontext,在调用函数dbus_g_bus_get()后,调用函数dbus_connection_setup_with_g_main(),而DBus绑定到缺省的maincontext时,不需要调用后面这个函数,此时是能接收到Glib信号的,看来是调用了函数dbus_connection_setup_with_g_main()引起问题的。在函数dbus_g_bus_get()中,为了将获取的DBus连接绑定到缺省的maincontext,已经调用了一次函数dbus_connection_setup_with_g_main(),传入的maincontext为NULL。当我们要将DBus连接绑定到非缺省的maincontext时,再次调用了函数dbus_connection_setup_with_g_main(),将DBus连接绑定到传入的maincontext上,似乎这次绑定没有成功。但DBus作为一个使用已较为广泛的软件,而且为了查找问题,我使用了DBus的1.0.2版本,Glib封装的0.72版本,应该不会出现API函数调用失效的问题吧,是否我们哪里使用不当呢。让我们深入函数dbus_connection_setup_with_g_main(),看看DBus是如何绑定到maincontext上的,如何接收Glib信号,并调用Glib信号处理函数的。
在深入分析之前,我们先看看DBus的是如何与Glib的mainloop关联的,如何监控传输端口的。DBus是一个按面向对象设计和实现的软件,因此我将这些C结构称之为对象。描述DBus连接的对象类型是DBusConnection,其Glib封装的对象类型是DBusGConnection。该对象中有一个名为watches的DBusWatchList类型的链表,这是由所有传输端口监控器(按其英文原意我称之为看守者对象)组成的链表,还有一个Glib封装使用的对象数据ConnectionSetup,通过函数dbus_connection_get_data()和dbus_connection_set_data()来获取和设置。
看守者对象(DBusWatch)监控传输端口,该传输端口的文件描述符由成员fd保存,看守对象的处理函数及处理函数的用户数据由函数指针成员handler和数据指针handler_data保存。看守者对象如何与Glib的mainloop关联呢,关键就在其对象数据data中,data指向一个IOHandler类型的对象,IOHandler对象类型中有一个成员source指向GSource类型的对象。熟悉Glib的朋友都知道GSource是Glib的mainloop的源,mainloop在运行时会遍历所有绑定在其上的源,当某个源可用时(对传输端口而言是数据已发送或接收到数据),会调用这个源的回调函数,在IOHandler类型的对象中,这个回调函数就是io_handler_dispatch()。这个函数调用DBusWatch类型的函数dbus_watch_handler(),最终调用看守对象的处理函数,实现传输端口数据发送和接收的处理,在我们的这个问题中,则是进行数据接收的处理。当服务端的Glib对象发送出一个导出到客户端的Glib的信号时,DBus将该信号打包为一个DBus的signal类型的消息,通过传输端口经DBus后台进程转发传送到客户端。当客户端接收到这个signal类型的消息时,通过上述的mainloop机制,看守者对象的处理函数将被调用,该处理函数将signal类型的消息解包并将其转化为Glib信号发送出去,应用相应的Glib信号处理函数就会调用。
链表watches中有几个函数指针add_watch_function,remove_watch_function,watch_toggled_function,分别指向Glib封装实现的对应函数,当向watches增加watch、从watches移除watch、触发watches中的watch时,会调用相应的函数指针指向的函数,完成Glib封装附加的处理。当调用函数dbus_connection_setup_with_g_main(),将DBus连接绑定到指定的maincontext时,需要调用函数dbus_connection_set_watch_function(),将Glib封装实现的add_watch、remove_watch、watch_toggled这几个函数设置到watches的函数指针中。Glib封装实现的add_watch函数将看守者绑定到指定的maincontext中,该函数创建了一个IOHandler类型的对象,并将该对象指向的GSource类型的对象绑定到DBus连接要绑定到的maincontext中。Glib封装实现的remove_watch函数将看守者从指定的maincontext中脱离,该函数将IOHandler指向的GSource类型的对象从maincontext的绑定脱离,并销毁这个GSource类型的对象。Glib封装实现的watch_toggled函数与我们的问题无关,就不介绍了。函数dbus_connection_set_watch_function()除了将Glib封装实现的函数设置到watches的函数指针中,还对watches中的看守者逐个调用add_watch函数将其绑定到指定的maincontext中,逐个调用remove_watch函数将其从指定的maincontext中脱离。
了解了DBus和Glib的mainloop的绑定机制,我们就可以来分析我们的问题了。前面的初步分析已经指出在再次调用函数dbus_connection_setup_with_g_main()将DBus连接绑定到指定的maincontext上后,客户端不能接收Glib信号。从上面的绑定机制来看,则是重新设置了add_watch、remove_watch、watch_toggled这几个函数,并对watches的每个看守者对象多调用了一次函数add_watch和remove_watch。给函数指针设置新的函数是不会有问题的,问题应该是多一次的add_watch和remove_watch函数调用引起的。于是在函数add_watch和remove_watch中加入调试信息的打印。在函数add_watch中,创建IOHandler类型的对象,将该对象的source对象绑定到maincontext时,打印出该source对象的内存地址。在函数remove_watch中,将IOHandler中的source对象从maincontext脱离并销毁时,打印出该source对象的内存地址。再次运行程序后,发现第二此调用函数dbus_connection_setup_with_g_main()时,函数remove_watch销毁的source对象和函数add_watch创建的IOHandler中的source对象是同一个对象。因函数remove_watch在函数add_watch之后调用,最终这个source对象是被销毁了,不能被mainloop遍历,也就无法监控传输端口的数据了,导致客户端不能接收服务端发送出的Glib信号。这就是问题的根本原因。
问题的原因找到了,但是却很不好解决,主要是看守者对象只有一个对象数据data用来保存IOHandler类型的对象,也就只有一个source对象和其关联。当add_watch函数将创建的IOHandler类型的对象保存到data中后,为了将看守者对象从原先的maincontext脱离,对该看守者调用remove_watch函数时将和其关联的source对象销毁时,销毁的是新创建的source对象,无法区别对待。为解决这个问题,我们只好重新写了一个获取DBus连接的函数dbus_g_bus_get_private()以获取私有的DBus连接,并将该连接绑定到指定的非缺省的maincontext中。这个函数只调用一次dbus_connection_setup_with_g_main()函数,避免出现看守者关联的source对象被销毁的问题。不知各位有否其他解决方法,请不吝赐教。
更多推荐
已为社区贡献3条内容
所有评论(0)