ubusopenwrt平台开发中的进程间通信提供了一个通用的框架,它让进程间通信的实现变得非常简单,并且ubus具有很强的可移植性,可以很方便地移植到其他linux平台上使用。ubus源码可通过Gitgit://nbd.name/luci2/ubus.git获得,其依赖的ubox库的gitgit://nbd.name/luci2/ubox.git

ubus的实现框架

ubus实现的基础是unix socket,即本地socket,它相对于用于网络通信的inet socket更高效,更具可靠性。unix socket客户端和服务器的实现方式和网络socket类似,一个简单的unix socket服务器和客户端需要做如下工作:

  1. 建立一个socket server端,绑定到一个本地socket文件,并监听clients的连接;

  2. 建立一个或多个socket client端,连接server

  3. clientserver相互发送消息;

  4. clientserver收到对方消息后,针对具体消息进行相应处理。

ubus同样实现了上述组件,并对socket连接以及消息传输和处理进行了封装:

  1. ubus提供了一个socket serverubusd);

  2. ubus提供了创建socket client端的接口,并且提供了三种现成的客户端供用户直接使用:a.为shell脚本提供client端,b.为lua脚本提供client接口,c.为C语言提供client接口;

  3. ubusclientserver之间通信的消息格式进行了定义,clientserver都必须将消息封装成json消息格式;

  4. ubusclient端的消息处理抽象出对象(object)和方法(method)的概念。一个对象中包含多个方法,client需要向server注册收到特定json消息时的处理方法,对象和方法都有自己的名字,发送请求方只需在消息中指定要调用的对象和方法的名字即可。

使用ubus时需要引用一些动态库,主要包括:

  • libubus.soubus向外部提供的编程接口,例如创建socket、进行监听和连接、发送消息等接口函数;

  • libubox.soubus向外部提供的编程接口,例如等待和读取消息;

  • 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的初始化所做的工作如下:

  1. epoll_create(32)创建出一个poll_fd

  2. 创建一个UDP unix socket,并添加到poll_fd的监听队列;

  3. 进行epoll_wait()等待消息,收到消息后的处理函数定义如下:

static struct uloop_fd server_fd = {  
    .cb = server_cb,  
};  

server_cb()函数中的工作为:

  1. 进行accept(),接受client连接,并为该连接生成一个client_fd

  2. client分配一个client id,用于ubusd区分不同的client

  3. client发送一个HELLO消息作为连接建立的标志;

  4. client_fd添加到poll_fd的监听队列中,用于监听client发过来的消息,消息处理函数为client_cb()

也就是说ubusd监听两种消息,一种是新client的连接请求,一种是现有的每个client发过来的数据。

ubusd收到一个client的数据后,调用client_cb()函数的处理过程:

  1. 先检查一下是否有需要向这个client回复的数据(可能是上一次请求没处理完),如果有,先发送这些遗留数据。

  2. 读取socket上的数据,根据消息类型(数据中都指定了消息类型的)调用相应的处理函数;

  3. 处理完成后,向client发送处理结果,例如UBUS_STATUS_OK

client的工作流程

ubus call obj method的工作流程:

  1. 创建一个unix socket(UDP)连接ubusd,并接收到server发过来的HELLO消息;

  2. ubus call命令由ubus_cli_call()函数进行处理,先向ubusd发送lookup消息请求objid,然后向ubusd发送invoke消息来调用objmethod方法;

  3. 创建epoll_fd并将clientfd添加到监听列表中等待消息;

  4. client收到消息后的处理函数为ubus_handle_data(),其中UBUS_MSG_DATA类型的数据receive_call_result_data()函数协助解析。

参考文章

ubus移植到openwrt
openwrt中使用ubus实现进程通信的原理
ubus实现进程间通信举例


txgcwm
764 声望71 粉丝

Linux C/C++


引用和评论

0 条评论