LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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域套接字,等待来自客户端的连接。

  1. 初始化和启动:ubusd守护进程首先启动,它负责设置IPC机制,监听Unix域套接字,并接受来自客户端的连接请求。
  2. 监听和接受连接:ubusd通过usock函数创建一个Unix域套接字,并监听连接请求。当有新的客户端连接时,它使用accept系统调用来接受连接。
  3. 创建客户端结构:对于每个新的客户端连接,ubusd守护进程使用ubusd_proto_new_client函数创建一个新的ubus_client结构体实例。
  4. 注册事件循环:新创建的客户端实例会被注册到uloop事件循环中,通过uloop_fd_add函数添加读/写事件的处理回调。
  5. 客户端连接:应用程序使用libubus库来创建一个连接到ubusd的客户端。
  6. 发送和接收消息:
    • 客户端使用libubus库发送请求到ubusd守护进程。ubusd通过recvmsg和read系统调用来接收这些请求。
    • ubusd处理接收到的请求,并将响应发送回客户端。响应通过ubus_msg_writev等函数发送。
  7. 消息处理:ubusd根据接收到的消息调用相应的处理程序,例如调用ubusd_proto_receive_message来处理消息。
  8. 事件通知和订阅:客户端可以通过libubus订阅特定的事件。当事件发生时,ubusd会向所有订阅的客户端发送通知。
  9. ACL和权限管理:ubusd使用ACL(访问控制列表)来管理对不同服务的访问权限。libubus库在客户端请求服务时处理权限验证。
  10. 错误处理和信号:
    • ubusd处理各种错误情况,并根据需要向客户端发送错误响应。
    • ubusd还处理信号,如SIGHUP,用于重新加载配置或ACL。
  11. 断开连接:当客户端完成通信或出现错误时,它会从uloop事件循环中删除对应的文件描述符,并调用close来关闭套接字。
  12. 资源清理: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)机制,用于在不同进程间传递消息和调用远程方法。

它们在设计、实现和使用场景上存在一些关键的区别:

  1. 项目范围和复杂性:

    • ubus:专为OpenWrt操作系统设计,是一个轻量级的IPC机制,适合于嵌入式系统和资源受限的环境。
    • DBus:是一个更通用的IPC框架,被广泛应用于Linux桌面环境(如GNOME和Qt应用)和各种Linux发行版。
  2. 性能和资源使用:

    • ubus:设计上更注重减少内存占用和提高效率,适合于低内存和CPU性能受限的嵌入式设备。
    • DBus:提供了丰富的功能,但相比ubus可能更消耗资源。
  3. API和语言绑定:

    • ubus:提供了C语言的API,并通过libubus库简化了客户端的开发。
    • DBus:提供了多种语言的绑定,包括C、Python、Java等,这使得DBus可以在更多类型的应用程序中使用。
  4. 使用场景:

    • ubus:主要用于OpenWrt中的守护进程和应用程序之间的通信。
    • DBus:被设计为一个系统级的通信总线,用于各种桌面环境和应用程序,支持各种类型的服务和通信模式。
  5. 架构和实现:

    • ubus:使用Unix域套接字和TLV(类型-长度-值)消息格式进行通信。
    • DBus:使用了自己的一套协议和消息格式,支持多种通信方式,包括内存映射文件、管道和套接字。
  6. 服务发现和注册:

    • ubus:守护进程ubusd负责服务的注册和消息路由。
    • DBus:使用一个中央化的守护进程dbus-daemon来管理服务的注册和名称服务。
  7. 安全性:

    • ubus:可能具有更简单的安全模型,主要依赖于OpenWrt系统自身的安全机制。
    • DBus:提供了更复杂的安全特性,包括访问控制、认证和加密通信。
  8. 社区和支持:

    • ubus:作为OpenWrt项目的一部分,主要由OpenWrt社区维护和支持。
    • DBus:由于其广泛的应用,拥有一个较大的社区和更广泛的支持。
  9. 配置和扩展性:

    • 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 systemubus service服务。netifd提供ubus network服务。rpcd提供ubus file服务、ubus iwinfo服务、ubus session服务和ubus uci服务。

posted on 2024-08-11 23:59  ArnoldLu  阅读(953)  评论(0编辑  收藏  举报

导航