ubus
为openwrt
平台开发中的进程间通信提供了一个通用的框架,它让进程间通信的实现变得非常简单,并且ubus
具有很强的可移植性,可以很方便地移植到其他linux
平台上使用。ubus
源码可通过Git
库git://nbd.name/luci2/ubus.git
获得,其依赖的ubox
库的git
库git://nbd.name/luci2/ubox.git
。
ubus
的实现框架
ubus
实现的基础是unix socket
,即本地socket
,它相对于用于网络通信的inet socket
更高效,更具可靠性。unix socket
客户端和服务器的实现方式和网络socket
类似,一个简单的unix socket
服务器和客户端需要做如下工作:
建立一个
socket server
端,绑定到一个本地socket
文件,并监听clients
的连接;建立一个或多个
socket client
端,连接server
;client
和server
相互发送消息;client
或server
收到对方消息后,针对具体消息进行相应处理。
ubus
同样实现了上述组件,并对socket
连接以及消息传输和处理进行了封装:
ubus
提供了一个socket server
(ubusd
);ubus
提供了创建socket client
端的接口,并且提供了三种现成的客户端供用户直接使用:a
.为shell
脚本提供client
端,b
.为lua
脚本提供client
接口,c
.为C
语言提供client
接口;ubus
对client
和server
之间通信的消息格式进行了定义,client
和server
都必须将消息封装成json
消息格式;ubus
对client
端的消息处理抽象出对象(object
)和方法(method
)的概念。一个对象中包含多个方法,client
需要向server
注册收到特定json
消息时的处理方法,对象和方法都有自己的名字,发送请求方只需在消息中指定要调用的对象和方法的名字即可。
使用ubus
时需要引用一些动态库,主要包括:
libubus.so
:ubus
向外部提供的编程接口,例如创建socket
、进行监听和连接、发送消息等接口函数;libubox.so
:ubus
向外部提供的编程接口,例如等待和读取消息;libblobmsg.so
/libjson.so
:提供了封装和解析json
数据的接口,编程时不需要直接使用libjson.so
,而是使用libblobmsg.so
提供的更灵活的接口函数。
使用ubus
进行进程间通信不需要编写大量代码,只需按照固定模式调用ubus
提供的API
即可,在ubus
源码中examples
目录下有一些例子可以参考。
ubus
内部的处理机制:
编译安装
下载:
$ git clone git://nbd.name/luci2/ubus.git
修改CMakeList.txt
:
cmake_minimum_required(VERSION 2.6) PROJECT(ubus C) ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) OPTION(BUILD_LUA "build Lua plugin" ON) OPTION(BUILD_EXAMPLES "build examples" ON) OPTION(ENABLE_SYSTEMD "systemd support" ON) SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") SET(UBUS_UNIX_SOCKET "/var/run/ubus.sock") SET(UBUS_MAX_MSGLEN 1048576) INCLUDE_DIRECTORIES("xxx/json-c/include/json-c") INCLUDE_DIRECTORIES("xxx/json-c/include") LINK_DIRECTORIES("xxx/json-c/lib") INCLUDE_DIRECTORIES(xxx/libubox/include/libubox) INCLUDE_DIRECTORIES(xxx/libubox/include) LINK_DIRECTORIES(xxx/libubox/lib) SET(CMAKE_INSTALL_PREFIX "xxx/ubus") SET(DESTINATION "xxx/ubus") ADD_DEFINITIONS( -DUBUS_UNIX_SOCKET="${UBUS_UNIX_SOCKET}") ADD_DEFINITIONS( -DUBUS_MAX_MSGLEN=${UBUS_MAX_MSGLEN}) IF(APPLE) INCLUDE_DIRECTORIES(/opt/local/include) LINK_DIRECTORIES(/opt/local/lib) ENDIF() ADD_LIBRARY(ubus SHARED libubus.c libubus-io.c libubus-obj.c libubus-sub.c libubus-req.c) TARGET_LINK_LIBRARIES(ubus ubox) ADD_EXECUTABLE(ubusd ubusd.c ubusd_id.c ubusd_obj.c ubusd_proto.c ubusd_event.c) TARGET_LINK_LIBRARIES(ubusd ubox) #find_library(json NAMES json-c json) ADD_EXECUTABLE(cli cli.c) SET_TARGET_PROPERTIES(cli PROPERTIES OUTPUT_NAME ubus) TARGET_LINK_LIBRARIES(cli ubus ubox blobmsg_json json-c) #ADD_SUBDIRECTORY(lua) #ADD_SUBDIRECTORY(examples) INSTALL(TARGETS ubus cli LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) INSTALL(TARGETS ubusd RUNTIME DESTINATION sbin ) INSTALL(FILES ubusmsg.h ubus_common.h libubus.h DESTINATION include) # FIXME: this works but certainly can be done better: SET(UBUSD_BINARY "${CMAKE_INSTALL_PREFIX}/sbin/ubusd") # do this after the installs so we have the proper paths #IF(ENABLE_SYSTEMD) # INCLUDE(FindPkgConfig) # PKG_CHECK_MODULES(SYSTEMD systemd) # IF(SYSTEMD_FOUND) # ADD_SUBDIRECTORY(systemd) # ENDIF() #ENDIF()
编译安装:
$ mkdir cmake-build $ cd cmake-build/ $ cmake ../ $ make $ make install
ubus
的应用场景和局限性
ubus
可用于两个进程之间的通信,并以类似json
格式进行数据交互。ubus
的常见场景为:
“客户端-服务器”形式的交互,即进程
A
注册一系列的服务,进程B
去调用这些服务;ubus
支持以“订阅-通知”的方式进行进程通信,即进程A
提供订阅服务,其它进程可以选择订阅或退订该服务,进程A
可以向所有订阅者发送消息。
由于ubus
实现方式的限制,在一些场景中不适宜使用ubus
:
ubus
用于少量数据的传输,如果数据量很大或是数据交互很频繁,则不宜用ubus
,当ubus
一次传输数据量超过60KB
,就不能正常工作了;ubus
对多线程支持的不好,例如在多个线程中去请求同一个服务,就有可能出现不可预知的结果;不建议递归调用
ubus
,例如进程A
去调用进程B
的服务,而B
的该服务需要调用进程C
的服务,之后C
将结果返回给B
,然后B
将结果返回给A
。
ubus
源码简析
ubusd
工作流程
ubusd
的初始化所做的工作如下:
epoll_create(32)
创建出一个poll_fd
;创建一个
UDP unix socket
,并添加到poll_fd
的监听队列;进行
epoll_wait()
等待消息,收到消息后的处理函数定义如下:
static struct uloop_fd server_fd = { .cb = server_cb, };
server_cb()
函数中的工作为:
进行
accept()
,接受client
连接,并为该连接生成一个client_fd
;为
client
分配一个client id
,用于ubusd
区分不同的client
;向
client
发送一个HELLO
消息作为连接建立的标志;将
client_fd
添加到poll_fd
的监听队列中,用于监听client
发过来的消息,消息处理函数为client_cb()
。
也就是说ubusd
监听两种消息,一种是新client
的连接请求,一种是现有的每个client
发过来的数据。
当ubusd
收到一个client
的数据后,调用client_cb()
函数的处理过程:
先检查一下是否有需要向这个
client
回复的数据(可能是上一次请求没处理完),如果有,先发送这些遗留数据。读取
socket
上的数据,根据消息类型(数据中都指定了消息类型的)调用相应的处理函数;处理完成后,向
client
发送处理结果,例如UBUS_STATUS_OK
。
client
的工作流程
ubus call obj method
的工作流程:
创建一个
unix socket(UDP)
连接ubusd
,并接收到server
发过来的HELLO
消息;ubus call
命令由ubus_cli_call()
函数进行处理,先向ubusd
发送lookup
消息请求obj
的id
,然后向ubusd
发送invoke
消息来调用obj
的method
方法;创建
epoll_fd
并将client
的fd
添加到监听列表中等待消息;client
收到消息后的处理函数为ubus_handle_data()
,其中UBUS_MSG_DATA
类型的数据receive_call_result_data()
函数协助解析。