OpenWRT(7):OpenWRT进程间通信-ubus、ubusd、libubus等
ubus提供了各种守护进程和应用程序之间的进程间通信。它包括如下几部分:
- 守护进程ubusd:在系统启动时运行,负责进程间的消息路由和传递。其他进程可以通过注册到ubusd进程来发送和接收消息。这些消息通过Unix套接字和TLV(类型-长度-值)格式进行传输 。
- 库文件libubus:为了简化开发,OpenWrt提供了libubus库,它允许应用程序轻松地连接到ubus、注册对象和方法、调用其他对象的方法、监听事件以及发送事件消息 。
- 辅助工具ubus:ubus命令行工具和ubusd交互,执行如列出服务、调用方法、监听事件等操作。
1 守护进程ubusd
ubusd被procd调用(参考OpenWRT(4):启动流程以及添加自己的服务 5.1 procd启动流程)。
ubusd守护进程作为核心组件被启动。它负责设置和维护IPC机制,包括监听Unix域套接字,等待来自客户端的连接。
- 初始化和启动:ubusd守护进程首先启动,它负责设置IPC机制,监听Unix域套接字,并接受来自客户端的连接请求。
- 监听和接受连接:ubusd通过usock函数创建一个Unix域套接字,并监听连接请求。当有新的客户端连接时,它使用accept系统调用来接受连接。
- 创建客户端结构:对于每个新的客户端连接,ubusd守护进程使用ubusd_proto_new_client函数创建一个新的ubus_client结构体实例。
- 注册事件循环:新创建的客户端实例会被注册到uloop事件循环中,通过uloop_fd_add函数添加读/写事件的处理回调。
- 客户端连接:应用程序使用libubus库来创建一个连接到ubusd的客户端。
- 发送和接收消息:
- 客户端使用libubus库发送请求到ubusd守护进程。ubusd通过recvmsg和read系统调用来接收这些请求。
- ubusd处理接收到的请求,并将响应发送回客户端。响应通过ubus_msg_writev等函数发送。
- 消息处理:ubusd根据接收到的消息调用相应的处理程序,例如调用ubusd_proto_receive_message来处理消息。
- 事件通知和订阅:客户端可以通过libubus订阅特定的事件。当事件发生时,ubusd会向所有订阅的客户端发送通知。
- ACL和权限管理:ubusd使用ACL(访问控制列表)来管理对不同服务的访问权限。libubus库在客户端请求服务时处理权限验证。
- 错误处理和信号:
- ubusd处理各种错误情况,并根据需要向客户端发送错误响应。
- ubusd还处理信号,如SIGHUP,用于重新加载配置或ACL。
- 断开连接:当客户端完成通信或出现错误时,它会从uloop事件循环中删除对应的文件描述符,并调用close来关闭套接字。
- 资源清理:ubusd守护进程负责清理不再使用的客户端资源,调用free来释放分配的内存。
main
uloop_init
mkdir_sockdir--创建/var/run/ubus/ubus.sock
usock--创建ubus socket句柄。
uloop_fd_add--监控ubus socket读事件,当有数据到达时调用server_fd进行处理。
server_cb
get_next_connection--尝试接受新的客户端连接,并返回一个布尔值表示是否继续监听更多的连接请求。如果接受成功,它将调用ubusd_proto_new_client来创建一个新的客户端结构,并使用uloop_fd_add将其添加到事件循环中。
accept
ubusd_proto_new_client--创建一个新的ubus_client结构,为新连接初始化回调和套接字。
ubusd_acl_init_client
ubus_alloc_id
ubusd_proto_init_retmsg
ubusd_send_hello
uloop_fd_add
client_cb--绑定到客户端套接字的回调函数,处理客户端的读写事件。它首先尝试发送任何待处理的数据,然后接收新消息,并根据消息类型调用ubusd_monitor_message和ubusd_proto_receive_message。
ubus_msg_writev--用于将消息发送到客户端套接字的辅助函数。
recvmsg--从socket读取客户端消息头部和控制信息。
ubus_msg_new--创建新的ubus消息缓存。
read--读取剩余消息内容。
ubusd_monitor_message--将接收到的消息发送到所有注册的监视器。
ubusd_proto_receive_message--处理接收到的ubus消息。
cb--根据消息类型,调用handlers中的回调函数进行处理。
ubusd_acl_load--加载ACL规则。通过ACL(访问控制列表)配置文件来管理对ubus服务的访问权限。
ubusd_acl_load_file
ubusd_send_event
ubusd_create_sequence_event_msg
uloop_run
2 命令行工具ubus
ubus命令行工具允许与ubusd服务器交互(与当前所有注册的服务交互)。这对于调查/调试注册的命名空间以及编写shell脚本非常有用。它使用用户友好的JSON格式调用过程、传递参数和返回响应。
Usage: ubus [<options>] <command> [arguments...]
Options:
-s <socket>--设置要连接的unix域套接字。
-t <timeout>--设置命令完成的超时时间(以秒为单位)。
-S--使用简化的输出(用于脚本)。
-v--更详细的输出。
-m <type>--(用于监视器):包含特定的消息类型。
-M <r|t>--(用于监视器):仅捕获接收或传输的流量。
Commands:
- list [<path>]--列出对象。
- call <path> <method> [<message>]--调用一个对象的方法。
- listen [<path>...]--监听事件。
- send <type> [<message>]--发送事件。
- wait_for <object> [<object>...]--等待多个对象出现在ubus上。
- monitor--监视ubus流量。
ubus list查看当前运行的服务:
ubus list container dhcp dnsmasq dnsmasq.dns file--《[OpenWrt Wiki] ubus file》。 hotplug.dhcp hotplug.iface hotplug.neigh hotplug.net hotplug.ntp hotplug.tftp iwinfo--《[OpenWrt Wiki] ubus iwinfo》。 log luci luci-rpc network--《[OpenWrt Wiki] ubus network》。 network.device network.interface network.interface.lan network.interface.loopback network.rrdns network.wireless rc service--《[OpenWrt Wiki] ubus service》。 session--《[OpenWrt Wiki] ubus session》。 system--《[OpenWrt Wiki] ubus system》。 uci--《[OpenWrt Wiki] ubus uci》。
查看某一服务的详细调用方法以及参数设置:
ubus -v list network.interface.lan 'network.interface.lan' @a4e4fa83 "up":{} "down":{} "renew":{} "status":{} "prepare":{} "dump":{} "add_device":{"name":"String","link-ext":"Boolean","vlan":"Array"} "remove_device":{"name":"String","link-ext":"Boolean","vlan":"Array"} "notify_proto":{} "remove":{} "set_data":{}
对接口下电、上电、查询状态:
ubus call network.interface.lan down { "network.interface": {"action":"ifdown","interface":"lan"} } { "network.interface": {"action":"ifdown","interface":"lan"} } [15644.690084] br-lan: port 1(eth0) entered disabled state [15644.736803] device eth0 left promiscuous mode [15644.739185] br-lan: port 1(eth0) entered disabled state
ubus call network.interface.lan up [15649.443247] 8021q: adding VLAN 0 to HW filter on device eth0 [15649.473157] br-lan: port 1(eth0) entered blocking state [15649.474143] br-lan: port 1(eth0) entered disabled state [15649.477896] device eth0 entered promiscuous mode [15649.516979] br-lan: port 1(eth0) entered blocking state [15649.518063] br-lan: port 1(eth0) entered forwarding state { "network.interface": {"action":"ifup","interface":"lan"} } [15650.421589] IPv6: ADDRCONF(NETDEV_CHANGE): br-lan: link becomes ready
ubus call network.interface.lan status { "up": true, "pending": false, "available": true, "autostart": true, "dynamic": false, "uptime": 4, "l3_device": "br-lan", "proto": "static", "device": "br-lan", ... "data": { } }
ubus listen监听socket,并且消息内容:
ubus call network.interface.lan down { "network.interface": {"action":"ifdown","interface":"lan"} } { "network.interface": {"action":"ifdown","interface":"lan"} } { "network.interface": {"action":"ifdown","interface":"lan"} } { "network.interface": {"action":"ifdown","interface":"lan"} } [16069.748937] br-lan: port 1(eth0) entered disabled state [16069.755847] device eth0 left promiscuous mode [16069.757284] br-lan: port 1(eth0) entered disabled state { "ubus.object.remove": {"id":-1416894893,"path":"dnsmasq.dns"} } { "ubus.object.remove": {"id":1290637834,"path":"dnsmasq"} } { "ubus.object.remove": {"id":-1416894893,"path":"dnsmasq.dns"} } { "ubus.object.remove": {"id":1290637834,"path":"dnsmasq"} } { "ubus.object.add": {"id":-760849594,"path":"dnsmasq"} } { "ubus.object.add": {"id":-760849594,"path":"dnsmasq"} } { "ubus.object.add": {"id":-165461039,"path":"dnsmasq.dns"} } { "ubus.object.add": {"id":-165461039,"path":"dnsmasq.dns"} }
发送一个事件:
ubus send foo '{"bar":"baz"}' { "foo": {"bar":"baz"} } { "foo": {"bar":"baz"} }
3 ubus和dbus异同
ubus和DBus都是进程间通信(IPC)机制,用于在不同进程间传递消息和调用远程方法。
它们在设计、实现和使用场景上存在一些关键的区别:
-
项目范围和复杂性:
- ubus:专为OpenWrt操作系统设计,是一个轻量级的IPC机制,适合于嵌入式系统和资源受限的环境。
- DBus:是一个更通用的IPC框架,被广泛应用于Linux桌面环境(如GNOME和Qt应用)和各种Linux发行版。
-
性能和资源使用:
- ubus:设计上更注重减少内存占用和提高效率,适合于低内存和CPU性能受限的嵌入式设备。
- DBus:提供了丰富的功能,但相比ubus可能更消耗资源。
-
API和语言绑定:
- ubus:提供了C语言的API,并通过libubus库简化了客户端的开发。
- DBus:提供了多种语言的绑定,包括C、Python、Java等,这使得DBus可以在更多类型的应用程序中使用。
-
使用场景:
- ubus:主要用于OpenWrt中的守护进程和应用程序之间的通信。
- DBus:被设计为一个系统级的通信总线,用于各种桌面环境和应用程序,支持各种类型的服务和通信模式。
-
架构和实现:
- ubus:使用Unix域套接字和TLV(类型-长度-值)消息格式进行通信。
- DBus:使用了自己的一套协议和消息格式,支持多种通信方式,包括内存映射文件、管道和套接字。
-
服务发现和注册:
- ubus:守护进程ubusd负责服务的注册和消息路由。
- DBus:使用一个中央化的守护进程dbus-daemon来管理服务的注册和名称服务。
-
安全性:
- ubus:可能具有更简单的安全模型,主要依赖于OpenWrt系统自身的安全机制。
- DBus:提供了更复杂的安全特性,包括访问控制、认证和加密通信。
-
社区和支持:
- ubus:作为OpenWrt项目的一部分,主要由OpenWrt社区维护和支持。
- DBus:由于其广泛的应用,拥有一个较大的社区和更广泛的支持。
-
配置和扩展性:
- ubus:配置相对简单,主要通过OpenWrt的配置系统进行管理。
- DBus:提供了更多的配置选项和扩展性,支持复杂的系统和服务架构。
总的来说,ubus是为OpenWrt定制的轻量级IPC机制,而DBus是一个更为通用和功能丰富的IPC框架,适用于更广泛的环境和需求。
4 libubus
libubus是一个为OpenWrt系统提供的进程间通信(IPC)框架,它允许不同的后台进程和应用程序之间进行通信。libubus的主要功能和特点如下:
- 初始化和连接:libubus提供ubus_connect函数来初始化客户端上下文并连接到ubusd守护进程,如果连接断开,ubus_auto_connect函数可以自动重连。
struct ubus_context *ubus_connect(const char *path); int ubus_connect_ctx(struct ubus_context *ctx, const char *path); void ubus_auto_connect(struct ubus_auto_conn *conn); int ubus_reconnect(struct ubus_context *ctx, const char *path); void ubus_free(struct ubus_context *ctx); void ubus_shutdown(struct ubus_context *ctx);
- 事件处理:通过ubus_register_event_handler函数可以注册新事件,而ubus_send_event函数用于发出事件消息。
int ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data); int ubus_register_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *pattern); static inline int ubus_unregister_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev) { return ubus_remove_object(ctx, &ev->obj); }
- 对象管理:libubus允许用户通过ubus_add_object向ubusd服务器添加新对象,并通过ubus_remove_object删除对象。
int ubus_add_object(struct ubus_context *ctx, struct ubus_object *obj); int ubus_remove_object(struct ubus_context *ctx, struct ubus_object *obj);
- 消息查找和调用:ubus_lookup和ubus_lookup_id函数分别用于查询对象信息和对象ID,这对于后续的消息调用很重要。
int ubus_lookup(struct ubus_context *ctx, const char *path, ubus_lookup_handler_t cb, void *priv); int ubus_lookup_id(struct ubus_context *ctx, const char *path, uint32_t *id);
- 消息传递:libubus允许进程间通过TLV格式传递消息,并能够以JSON格式与用户进行数据交换,这为数据的封装和解析提供了便利。
- 异步调用和回调:使用ubus_invoke函数可以调用远程对象的方法,并且可以指定一个回调函数来处理响应或超时。
- 多线程支持:libubus对多线程的支持有所限制,建议在设计使用libubus通信的应用时避免多线程请求同一服务,以防止不可预知的结果。
- 适用场景:libubus适用于少量数据传输的进程间通信,不适合大量数据或频繁的数据交换。此外,它不推荐用于递归调用的场景。
- API和工具:libubus提供了丰富的API接口,同时ubus命令行工具可以用于脚本编程和问题诊断。
5 基于ubus的应用
通过HTTP协议访问ubus。ubus提供了一种机制,允许通过HTTP协议进行远程管理。这是通过uhttpd的uhttpd-mod-ubus插件实现的,它将ubus API暴露为HTTP JSON-RPC 2.0接口。更多参考《Access to ubus over HTTP》。
通过lua脚本访问ubus,ubus提供了一个Lua模块,允许Lua脚本以一种简单直观的方式与ubus守护进程进行交互。更多参考《Lua module for ubus》。
procd提供ubus system和ubus service服务。netifd提供ubus network服务。rpcd提供ubus file服务、ubus iwinfo服务、ubus session服务和ubus uci服务。