- 在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配置文件内容的功能。
- 先从files开始分析,这个案例中,我们不需要用到配置文件,所以不用去编写conf的内容。
- 因为是要实现ubus接口,所以必须将其加入procd管理,可以在启动脚本中进行编写(内容不深入分析)。
- 其次就是编写我们软件的主要代码。
- src下的Makefile主要功能就是编译我们的代码,是最简单的Makefile。
- 软件包下顶层的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接口一些信息,包括方法名和调用的处理方法。
- 最后是第一个框,编写对应调用的处理方法(重要)。
- 大致流程是固定套路,重点在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是大头,直接在模板上修改就可以,我们需要修改的内容只有:
- PKG_NAME 是我们软件包的名称
- SECTION 和 CATEGORY 是我们在menuconfig中的分类
- DEPENDS 是依赖的链接库
- 最后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)))
到这里,我们软件包的编写就完成了,接下来就是编译安装阶段了。
编译安装自己的软件包 编译
- 首先回到openwrt的顶层目录下,执行make menuconfig,进入配置菜单界面,找到自己的软件包
在自己的软件包中使用m选中,然后保存退出。
当然也有高效的方法–>直接对.config文件内容作修改,在.config中加上一句:CONFIG_PACKAGE_ubus_demo=m,这个是固定形式,可以了解并应用。
- 返回顶层目录,执行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编译出错。