ubus分析

  • Post author:
  • Post category:其他

在学习openwrt的时就我一直很好奇页面上显示的wan/lan/wifi状态以及统计信息是从哪里获取的。最近因为项目需要把goahead这个嵌入式web server移植到了op上,并且需要获取wan/lan状态数据。于是去分析luci的获取方法,发现原来使用的是ubus+netifd。op里好几个shell命令也都使用到了ubus,比如/sbin/wifi、/sbin/ifstatus、/sbin/ifup等。鉴于ubus这么好用,于是决定要在项目其他模块中也把它引入进去,因此还是有必要把它摸透。

ubus

简介

通俗点说,ubus就是一个用于进程间通信的通用框架。ubus具有很强的可移植性,可以很方便的移植到其他Linux平台上使用。

ubus模块被设计用于提供守护进程(daemons)和应用程序(applications)间的通讯,包含了守护进程ubusd、库以及一些例子。ubusd可以认为是一个消息管理服务器(Server),需要通信的进程可以通过提供的libubus使用ubus,而ubus又依赖于ubox。其源码下载地址如下:

ubus:git://nbd.name/luci2/ubus.git或者http://git.openwrt.org/project/ubus.git
ubox:git://nbd.name/luci2/ubox.git

在op中这两个库的配置目录分别位于./package/system/ubus/ 和 ./package/system/ubox/。

ubus的内部框架大致如下图所示:
在这里插入图片描述其中Ubus Daemoon就是ubusd,它是一个服务管理的服务器。上图右下角的组件是一个Client,用于向ubusd请求服务。而左下角是一个服务提供者(相对于ubusd它其实也是个Client,这里称之为Server其实是相对于服务请求者Client而言,不要搞混了)。上图中Server和Client之间通信的消息采用json格式。

几个概念

对象(Object)

为了便于对消息进行处理,ubusd抽象出了“对象”和“方法”的概念。一个对象中包含多个方法,它们均可以指定名字,便于其他地方使用。

方法(Method)

方法是对象提供的一些可以被调用的操作。
在使用ubus调用(Call)某个方法时,Client会发送call消息给服务提供端,并等待回复。

通知(Notification)& 订阅者(Observer)

订阅者只关心自己感兴趣的服务,通知者通过该服务广播消息,通知所有的订阅者。它们之间是多对一的关系。

ubus的工作原理

进程Client1向ubusd注册了对象test,其中包含三个方法:hello、watch、count,对应的处理函数分别是test_hello、test_watch、test_count。

进程Client2要把信息传递给Client1需要借助ubusd中转。client2要调用client1提供的方法hello,并且携带参数。Client2的实现方式支持shell、c、lua。

Clinet1收到Client2的消息后执行test_hello函数,处理完成后回复消息给Client2。

如何使用ubus

在介绍怎么使用之前需要说一下ubus的应用场景和局限性:(copy过来的,没有亲测)

(1) ubus用于少量数据的传输,如果数据量很大或是数据交互很频繁,则不宜用ubus。经过测试,当ubus一次传输数据量超过60KB,就不能正常工作了。
(2) 对多线程的支持不好。例如在多个线程中去请求同一个服务,就有可能出现不可预知的结果。
(3) 不建议递归调用ubus,例如进程A去调用进程B的服务,而B的该服务需要调用进程C的服务,之后C将结果返回给B,然后B将结果返回给A。如果不得不这样做,需要在调用过程中避免全局变量的重用问题。

命令行方式

参考ubus命令。

(1) list

(2) call

调用指定命名空间中指定的方法,并且通过消息传递给它,消息参数必须是有效的JSON字符串,并且携带函数所要求的键及值。

(3) listen

(4) send

通过C API调用(invoke方式)

对于提供服务(Service)的进程(服务提供者)

需要:

  • 定义对象及对外提供的方法,方法的处理函数实现步骤如下:
    ① 调用blobmsg_parse从blob_attr链表中解析各个blob_attr消息
    ② 获取blob_attr携带的参数
    ③ 准备blob_buf用于回复给busd,再由ubusd转发到请求端

a) 调用blob_buf_init初始化blob_buf
b) 如果有数据返回,调用blobmsg_add_string/blobmsg_add_u32等函数填充数据
c) 调用ubus_send_reply回复

注意:如果在请求中指定了deferred标志,则不需要回复。
说明:blob子模块中几个结构体的关系如下,消息格式使用TLV(type-length-value)。

  • 使用ubus_connect连接到服务管理进程ubusd,得到ubus_context(包含了连接fd、注册fd的回调等)
  • 通过ubus_add_uloop将ubus_context里包含的连接fd加入到epoll的描述符集,用于监听
    注意:在使用ubus前必须要调用uloop_init准备好epoll专用句柄。
  • 将定义的对象通过ubus_add_object加入到ubusd
  • 调用uloop_run循环epoll的文件描述符集
  • 资源释放
    如果不想再使用ubus,要依次调用ubus_free和uloop_done将前面申请的资源释放掉。

对于服务(Service)的请求进程(Client)

  • 调用ubus_connect连接到ubusd,得到ubus_context
  • 通过ubus_add_uloop将ubus_context里包含的连接fd加入到epoll的描述符集,用于监听
    注意:在使用ubus前必须要调用uloop_init准备好epoll专用句柄。
  • 调用ubus_lookup_id查找对象对应的id
  • 将调用参数填充到blob_buf
    ① 通过blob_buf_init初始化blob_buf
    ② 通过blobmsg_add_u32等函数往blob_buf中加入数据
  • 调用ubus_invoke,等待回应
    如果不需要等待回复,则调用ubus_invoke_async
  • 资源释放
    如果不想再使用ubus,要依次调用ubus_free和uloop_done将前面申请的资源释放掉。

通过C API调用(notification方式)

对于订阅者(Subscriber)

  • 调用ubus_connect连接到ubusd,得到ubus_context
  • 通过ubus_add_uloop将ubus_context里包含的连接fd加入到epoll的描述符集,用于监听
  • 定义ubus_subscriber实例并调用ubus_register_subscriber将其注册到ubusd
  • 订阅感兴趣的对象(Object)

对于通知发送者(Notifier,Notification Sender)

  • 调用ubus_connect连接到ubusd,得到ubus_context
  • 通过ubus_add_uloop将ubus_context里包含的连接fd加入到epoll的描述符集,用于监听
  • 定义一个通知对象(ubus_object)
  • ubus_add_object将通知对象加入到ubusd
  • 当事件发生,准备消息类型及参数
  • ubus_notify将通知广播到ubusd
  • 资源释放
    如果不想再使用ubus,要依次调用ubus_free和uloop_done将前面申请的资源释放掉。

通过C API调用(event方式)

event是一个广播消息,事件的发送方不需要知道谁要接收这个消息。网卡的状态通知就是通过这种方式实现的,谁要是关心这个事件就自己接收处理。

实现的步骤和前面两种类似,这里只介绍不同的部分。

(1)对于事件接收者

  • 定义事件监听器ubus_event_handler
  • 调用ubus_register_event_handler注册事件处理机制

(2)对于事件发送者

  • 填充blob_buf,构造事件内容
  • 调用ubus_send_event将事件广播出去

通过lua调用

(略)

参考资料

ubus&ubus/examples&netifd源码
http://www.doc88.com/p-9813308540412.html
http://blog.csdn.net/jasonchen_gbd/article/details/46055885
http://blog.csdn.net/jasonchen_gbd/article/details/45627967
www.wifidog.pro/2015/08/10/openwrt-ubus架构.html


版权声明:本文为m0_37651140原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。