栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

在openwrt使用C语言增加ubus接口(包含C uci操作)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap

在openwrt使用C语言增加ubus接口(包含C uci操作)

在openwrt使用C语言增加ubus接口(包含C uci操作)

文章目录
  • 在openwrt使用C语言增加ubus接口(包含C uci操作)
    • 创建自己的软件包
      • 软件包结构
    • 编写代码和启动脚本(重点)
      • 案例大致分析
      • 实现过程
        • ubus_demo.init
        • ubus_demo.c
          • main函数中注册ubus接口
          • 设计ubus_object
          • 实现调用方法(重点)
        • Makefile编写
    • 编译安装自己的软件包
      • 编译
      • 安装/卸载
    • 测试ubus
    • 可能遇到的一些问题

本文主要讲解在openwrt中,如何使用C语言实现增加自己的ubus接口,其中会涉及一些调用uci接口的操作。


创建自己的软件包 软件包结构

首先需要下载openwrt的源码,并且自己编译出镜像文件并创建虚拟机。
在openwrt的package中创建自己的软件包,如下图所示。

  • files:配置文件和启动脚本
  • Makefile:控制这个软件包的编译
  • src:代码和编译代码的Makefile

编写代码和启动脚本(重点) 案例大致分析

接下来就举个简单的案例:实现一个ubus接口,提供查看uci配置文件内容的功能。

  1. 先从files开始分析,这个案例中,我们不需要用到配置文件,所以不用去编写conf的内容。
  2. 因为是要实现ubus接口,所以必须将其加入procd管理,可以在启动脚本中进行编写(内容不深入分析)。
  3. 其次就是编写我们软件的主要代码。
  4. src下的Makefile主要功能就是编译我们的代码,是最简单的Makefile。
  5. 软件包下顶层的Makefile用来控制整个软件包的编译,其中大部分都是模板,需要修改的地方并不多。

实现过程 ubus_demo.init
  • 脚本实现的主要功能:加入procd管理、监控配置文件的变化。
  • 因为设置了USE_PROCD=1,所以start、stop、reload函数名变成start_service、stop_service、reload_service。
  • START、STOP变量设置是控制程序在/etc/init.d中的启动、停止顺序。
  • service_triggers设置了触发器,当配置文件ubus_demo(/etc/config/ubus_demo.conf)发生改变,就会触发reload_service方法。(这里我们没有编写ubus_demo.conf,所以这个方法并不会起作用)
  • start_service方法中,使用procd提供的方法将这个程序加入到procd管理(成为’杀不死’的守护进程)
#!/bin/sh /etc/rc.common
# ubus_demo script

USE_PROCD=1
START=15
STOP=85
PROG=/etc/ubus_demo

service_triggers() {
    procd_add_reload_trigger "ubus_demo"
}
 
start_service() {
    echo "start ubus_demo!"
    procd_open_instance
    procd_set_param command "$PROG"
    procd_set_param respawn
    procd_set_param file "/etc/config/ubus_demo"
    procd_close_instance
}
 
stop_service() {
    echo "stop ubus_demo!"
}

reload_service() {
    stop
    echo "reloading"
    start
}

ubus_demo.c

所需头文件:

#include 
#include 
#include 
#include 
#include 
#include 

大致可以分成以下三部分来完成:

  • 首先是main函数,里面是ubus注册接口流程,基本都是不变的。
  • 其次是第二个框,设置ubus_object对象,设置ubus接口一些信息,包括方法名和调用的处理方法。
  • 最后是第一个框,编写对应调用的处理方法(重要)。

main函数中注册ubus接口
  • 大致流程是固定套路,重点在ubus_add_object这个方法,其中的demo_object是需要我们自己编写设计的。
  • UBUSD_SOCK是ubus套接字的路径(/var/run/ubus/ubus.sock)。

int main(int argc, char* argv[])
{
    // 创建epoll句柄
    uloop_init();

    // 建立连接
    struct ubus_context *ctx = ubus_connect(UBUSD_SOCK);
    if (ctx == NULL) {
        printf("ctx null...n");
    } else {
        printf("ctx not nulln");
    }

    // 向ubusd请求增加一个新的object(这个object在第二部分中实现)
    ubus_add_object(ctx, &demo_object);

    // 把建立的连接加入到uloop中
    ubus_add_uloop(ctx);

    // 等待事件发生,调用对应函数
    uloop_run();

    // 关闭连接
    ubus_free(ctx);

    // 关闭epoll句柄
    uloop_done();

    return 0;
}
设计ubus_object
  • ubus_object中主要需要有接口对象名称,以及对应的调用方法
  • ubus_method中设置对应调用的方法,两种实现方法,代码注释中有详细解释
// * ubus_object对象中注册的方法
static struct ubus_method demo_methods[] = {
    // * 添加成员方法的操作(policy是传入的参数) { .name = "uci_get", .handler = get_uci, .policy = stu_policy, .n_policy = ARRAY_SIZE(stu_policy) },
    // * 没有传参可以省略{ .name = "test", .handler = func }, 
    // * 使用宏定义的方法来简化操作 UBUS_METHOD(ubus调用名,对应处理函数,传入的参数)
    UBUS_METHOD("uci_get", get_uci, uci_get_policy),	// uci_get就是ubus中的接口名称,get_uci是调用的处理函数
    UBUS_METHOD("uci_set", set_uci, uci_set_policy)		// 这边set的实现不详细讲
};

// * ubus_object对象中类型
static struct ubus_object_type demo_object_type = 
	UBUS_OBJECT_TYPE("test", demo_methods);	// 直接调宏定义函数即可

// * 设置ubus_object对象
static struct ubus_object demo_object = {
    .name = "test",	// 接口对象名称
    .type = &demo_object_type,	// object类型
    .methods = demo_methods,	// 对应调用的方法
    .n_methods = ARRAY_SIZE(demo_methods),	// 方法个数
};
实现调用方法(重点)
  • 需要定义好接受的参数个数及类型(blobmsg_policy)
#define PACKAGE_NAME 0
#define SECTION_NAME 1
#define OPTION_NAME 2
#define __UCI_GET_MAX 3

// .name是参数名称,.type是参数的数据类型
static const struct blobmsg_policy uci_get_policy[__UCI_GET_MAX] = {
    [PACKAGE_NAME] = {.name = "package", .type = BLOBMSG_TYPE_STRING},
    [SECTION_NAME] = {.name = "section", .type = BLOBMSG_TYPE_STRING},
    [OPTION_NAME] = {.name = "option", .type = BLOBMSG_TYPE_STRING}
};	
  • 处理方法中参数列表是固定的(cv即可),实现过程分为三步:
  • 解析参数:使用blobmsg_parse方法进行解析
  • 处理过程:我们这边通过uci提供的接口去读取配置文件的内容
  • 响应过程:使用ubus_send_reply方法来响应
// * get对应的处理方法
static int get_uci(struct ubus_context *ctx, struct ubus_object *obj,
                     struct ubus_request_data *req, const char *method,
                     struct blob_attr *msg)
{
    char buf[256] = {0};
    char value[256] = {0};
    char package_name[256] = {0};
    char section_name[256] = {0};
    char option_name[256] = {0};
    struct blob_attr *param[__UCI_GET_MAX] = {0};
    struct uci_context *uci_ctx = uci_alloc_context();	// 分析uci上下文内容
    struct uci_package *pkg = NULL;
    struct uci_section *sec = NULL;
    struct uci_option *opt = NULL;
    struct uci_ptr ptr = {0};
    struct blob_buf b_buf = {0};

    // * 解析传入的参数
    blobmsg_parse(uci_get_policy, ARRAY_SIZE(uci_get_policy), param, blob_data(msg), blob_len(msg));

    // * 处理过程
    if (uci_ctx == NULL) {
        return -1;
    }
    snprintf(package_name, sizeof(package_name), "%s", blobmsg_get_string(param[PACKAGE_NAME]));
    snprintf(section_name, sizeof(section_name), "%s", blobmsg_get_string(param[SECTION_NAME]));
    snprintf(option_name, sizeof(option_name), "%s", blobmsg_get_string(param[OPTION_NAME]));

    if (uci_load(uci_ctx, package_name, &pkg) != UCI_OK) {	// 加载配置文件到pkg中
        snprintf(package_name, sizeof(package_name), "package no exist");
        goto RESPONSE;
    }
    sec = uci_lookup_section(uci_ctx, pkg, section_name);	// 读取pkg中的配置节到sec中
    if (sec == NULL) {
        snprintf(section_name, sizeof(section_name), "section no exist");
        goto RESPONSE;
    }
    opt = uci_lookup_option(uci_ctx, sec, option_name);	// 读取sec中的选项内容到opt中
    if (opt == NULL) {
        snprintf(option_name, sizeof(option_name), "option no exist");
        goto RESPONSE;
    }

    // 根据buf读取内容到ptr中
    snprintf(buf, sizeof(buf), "%s.%s.%s", package_name, section_name, option_name);
    if (uci_lookup_ptr(uci_ctx, &ptr, buf, true) == UCI_OK) {	// 读取选项中的值到ptr中
        strncpy(value, ptr.o->v.string, sizeof(value) - 1);
    }
    
RESPONSE:
    uci_free_context(uci_ctx);	// 释放uci上下文内容

    // * 响应过程
    blob_buf_init(&b_buf, 0);	// 初始化blob_buf
    // * blobmsg_add_* 可以添加不同类型
    blobmsg_add_string(&b_buf, "response_config", package_name);
    blobmsg_add_string(&b_buf, "response_section", section_name);
    blobmsg_add_string(&b_buf, "response_option", option_name);
    blobmsg_add_string(&b_buf, "response_value", value);

    // 响应
    ubus_send_reply(ctx, req, b_buf.head);	// 响应内容给req
    blob_buf_free(&b_buf);	// 释放blob_buf
    return 0;
}

Makefile编写

src下的Makefile只是需要编译.c文件,记得添加编译选项-luci -lubus -lbox

.PHONY:all clean
all:
	$(CC) $(CFLAGS) -g -luci -lubus -lubox ubus_demo.c -o ubus_demo 
clean:
	rm *.o ubus_demo

软件包下顶层的Makefile是大头,直接在模板上修改就可以,我们需要修改的内容只有:

  1. PKG_NAME 是我们软件包的名称
  2. SECTION 和 CATEGORY 是我们在menuconfig中的分类
  3. DEPENDS 是依赖的链接库
  4. 最后package/install中是我们软件包的文件(.c .conf .init)
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
 
PKG_NAME:=ubus_demo
PKG_RELEASE:=1.0
 
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
PKG_CONFIG_DEPENDS :=
 
include $(INCLUDE_DIR)/package.mk
 
define Package/$(PKG_NAME)
	SECTION:=utils
	CATEGORY:=Milesight
	TITLE:=ubus_demo utility
	DEPENDS:=+libuci +libubus +libubox
endef
 
define Package/$(PKG_NAME)/description
	This is Ubus Test OpenWrt.
endef
 
define Build/Prepare
	mkdir -p $(PKG_BUILD_DIR)
	$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
 
 
define Build/Configure
endef
 
define Build/Compile
	$(MAKE) -C $(PKG_BUILD_DIR) 
		$(TARGET_CONFIGURE_OPTS) 
		CFLAGS="$(TARGET_CFLAGS)" 
		CPPFLAGS="$(TARGET_CPPFLAGS)" 
		LDFLAGS="$(TARGET_LDFLAGS)"
endef
 
define Package/$(PKG_NAME)/install	
	$(INSTALL_DIR) $(1)/etc/init.d
	$(INSTALL_BIN) ./files/ubus_demo.init $(1)/etc/init.d/ubus_demo
	
	$(INSTALL_DIR) $(1)/etc
	$(INSTALL_BIN) $(PKG_BUILD_DIR)/ubus_demo $(1)/etc/ubus_demo
endef
 
$(eval $(call BuildPackage,$(PKG_NAME)))

到这里,我们软件包的编写就完成了,接下来就是编译安装阶段了。


编译安装自己的软件包 编译
  1. 首先回到openwrt的顶层目录下,执行make menuconfig,进入配置菜单界面,找到自己的软件包

在自己的软件包中使用m选中,然后保存退出。

当然也有高效的方法–>直接对.config文件内容作修改,在.config中加上一句:CONFIG_PACKAGE_ubus_demo=m,这个是固定形式,可以了解并应用。

  1. 返回顶层目录,执行make package/ubus_demo/compile V=s进行编译,编译成功后可以进入安装。

安装/卸载
  • 在openwrt系统中,可以使用opkg来进行安装和卸载。

  • 其中卸载只需要程序名即可(也可以通过opkg list-installed | grep ubus_demo查看安装信息)

  • 检验是否成功安装,查看/etc是否有我们的软件。

  • 查看是否加入了procd管理(ubus call service list ‘{“name”:“ubus_demo”}’)

  • 查看自己是否成功注册了ubus接口(ubus list)

现在也正常安装到openwrt中了,接下来进行测试。


测试ubus
  • 查看我们所注册的方法(ubus -v list test)

  • 试着调用方法,比如查看network中mng的ipaddr
  • 调用ubus方法ubus call test uci_get ‘{“package”:“network”,“section”:“mng”,“option”:“ipaddr”}’

返回的值跟我们查看的值是一致的,说明这个方法成功调用了。


可能遇到的一些问题

参考本人整理的一些错误:ubus编译出错。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1041282.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号