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

2023-11-19

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

本文主要讲解在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 <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <uci.h>
#include <libubus.h>

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

  • 首先是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 null\n");
    }

    // 向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编译出错

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在openwrt使用C语言增加ubus接口(包含C uci操作) 的相关文章

  • Locale.getDefault() 始终返回 en

    unix 机器上的服务器始终使用 en 作为默认区域设置 以下是区域设置输出 LANG en US LC CTYPE C LC NUMERIC C LC TIME C LC COLLATE C LC MONETARY C LC MESSAG
  • 何时使用 pthread 条件变量?

    线程问题 看来 只有在其他线程调用 pthread cond notify 之前调用 pthread cond wait 时 条件变量才起作用 如果在等待之前发生通知 那么等待将被卡住 我的问题是 什么时候应该使用条件变量 调度程序可以抢占
  • 在 Linux 上更快地分叉大型进程?

    在现代 Linux 上达到与 Linux 相同效果的最快 最好的方法是什么 fork execve combo 从一个大的过程 我的问题是进程分叉大约 500MByte 大 并且一个简单的基准测试只能从进程中实现约 50 个分叉 秒 比较最
  • 安装“opkg”?

    我正在尝试将软件包安装到我的 OpenWrt SDK 中 为此 您必须通过键入以下内容来使用 OPKG 包管理器 opkg update opkg install
  • Bash 解析和 shell 扩展

    我对 bash 解析输入和执行扩展的方式感到困惑 对于输入来说 hello world 作为 bash 中的参数传递给显示其输入内容的脚本 我不太确定 Bash 如何解析它 Example var hello world displaywh
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 如何禁用 GNOME 桌面屏幕锁定? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何阻止 GNOME 桌面在几分钟空闲时间后锁定屏幕 我已经尝试过官方手册了在红帽 https access redhat com doc
  • 我可以从命令行打印 html 文件(带有图像、css)吗?

    我想从脚本中打印带有图像的样式化 html 页面 谁能建议一个开源解决方案 我使用的是 Linux Ubuntu 8 04 但也对其他操作系统的解决方案感兴趣 你可以给html2ps http user it uu se jan html2
  • 从 Python 调用 PARI/GP

    我想打电话PARI GP http pari math u bordeaux fr dochtml gpman html仅从Python计算函数nextprime n 对于不同的n是我定义的 不幸的是我无法得到帕里蟒蛇 http code
  • sendfile64 只复制约2GB

    我需要使用 sendfile64 复制大约 16GB 的文件 到目前为止我所取得的成就是 include
  • Linux 中的动态环境变量?

    Linux 中是否可以通过某种方式拥有动态环境变量 我有一个网络服务器 网站遵循以下布局 site qa production 我想要一个环境变量 例如 APPLICATION ENV 当我在 qa 目录中时设置为 qa 当我在生产目录中时
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 添加要在给定命令中运行的 .env 变量

    我有一个 env 文件 其中包含如下变量 HELLO world SOMETHING nothing 前几天我发现了这个很棒的脚本 它将这些变量放入当前会话中 所以当我运行这样的东西时 cat env grep v xargs node t
  • 如何在 Linux shell 中将十六进制转换为 ASCII 字符?

    假设我有一个字符串5a 这是 ASCII 字母的十六进制表示Z 我需要找到一个 Linux shell 命令 它将接受一个十六进制字符串并输出该十六进制字符串代表的 ASCII 字符 所以如果我这样做 echo 5a command im
  • Linux 内核标识符中前导和尾随下划线的含义是什么?

    我不断遇到一些小约定 比如 KERNEL Are the 在这种情况下 是内核开发人员使用的命名约定 还是以这种方式命名宏的语法特定原因 整个代码中有很多这样的例子 例如 某些函数和变量以 甚至 这有什么具体原因吗 它似乎被广泛使用 我只需
  • os.Mkdir 和 os.MkdirAll 权限

    我正在尝试在程序开始时创建一个日志文件 我需要检查是否 log如果不创建目录 则目录存在 然后继续创建日志文件 好吧 我尝试使用os Mkdir 也os MkdirAll 但无论我在第二个参数中输入什么值 我都会得到一个没有权限的锁定文件夹
  • 安装J语言的JQt IDE,出现错误

    我一直按照这里的说明进行操作 http code jsoftware com wiki System Installation Linux http code jsoftware com wiki System Installation L
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • jpegtran 优化而不更改文件名

    我需要优化一些图像 但不更改它们的名称 jpegtran copy none optimize image jpg gt image jpg 但是 这似乎创建了 0 的文件大小 当我对不同的文件名执行此操作时 大小仍然完全相同 怎么样 jp
  • ubuntu:升级软件(cmake)-版本消歧(本地编译)[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我的机器上安装了 cmake 2 8 0 来自 ubuntu 软件包 二进制文件放置在 usr bin cmake 中 我需要将 cmake 版本至少

随机推荐

  • VUE路由的跳转的4中方式

    vue 路由跳转四种方式 带参数 router link 带参数 不带参数 this router push 函数里面调用 1 2 3 this router replce 用法同上 push this router go n 一 1 ro
  • 一步真实解决TypeError: unlink() got an unexpected keyword argument ‘missing_ok‘

    在谷歌服务器colab上跑yolo代码 训练自己的yolov5模型 遇到这个错误 可把我整不会了 已经做好了数据集 建立了新的data下的yaml和model下的yaml文件 然后使用代码 python train py 运行train p
  • 新仙魔九界研发及设计分析

    玩法本质属于捕鱼RPG玩法 美术风格属于国风 非常有特色 目前整体的玩法 性能优化和体验各方面都做的不错 从第三方数据平台获知该游戏目前月流水在600万 且有未来还有巨大的增长空间 这也不是波克城市第一次做出尝试 早在2015年就尝试过一个
  • druid 解析select查询sql获取表名,字段名,where条件

    解析select sql生成QueryModelInfo param dbTypeName mysql oracle param selectSql return public static QueryModelInfo parse Str
  • Vue将页面中Echarts动态图导出Gif动图

    Vue将页面中Echarts动态图导出Gif动图 首先 准备工作需要三个依赖的js 第一个js采用npm进行安装 npm install html2canvas 第二个js采用下载放入项目中进行使用 下载链接 https pan baidu
  • $refs用法

    refs获取dom元素 今天我们主要说一下几点 1 vue 获取普通元素 基础 2 vue 获取列表 基础 一 vue获取dom节点 普通元素 我们之前获取页面上的dom元素 很容易 比如原生js的 document getElements
  • Angular实现发送请求下载文件

    Angular实现下载文件到本地 第一步 获取下载地址 获取下载地址 download let param 参数 json形式 this download param subscribe res any gt if res success
  • 【无标题】AttributeError: ‘TransposedFont‘ object has no attribute ‘getbbox‘

    Python 运行 Wordcloud的时候出现了这个错误 from wordcloud import WordCloud from PIL import Image import matplotlib pyplot as plt topi
  • 3D游戏编程与设计作业——血条预制

    一 作业要求 血条 Health Bar 的预制设计 具体要求如下 分别使用 IMGUI 和 UGUI 实现 使用 UGUI 血条是游戏对象的一个子元素 任何时候需要面对主摄像机 分析两种实现的优缺点 给出预制的使用方法 二 实现效果 血条
  • ROS自学实践(10):ROS节点同时订阅多个话题并进行消息时间同步

    一 前言 在进行SLAM建图或自动驾驶系统设计的过程中 往往涉及到多种传感器进行环境感知和信息采集 这就不仅需要一个节点接收多个传感器数据 还要求传感器数据的时间戳同步 这样才能实现环境数据的实时感知和处理 本文基于ROS操作系统 从C 和
  • Android 5.x 权限问题解决方法

    0 关于selinux的基础知识 可以参见http www cnblogs com shell812 p 6379321 html TE语言规则 参见http www cnblogs com shell812 p 6379370 html
  • npx报错“Path must be a string. Received undefined”in windows解决方法

    在使用Windows上使用较老版本的nodejs 如何我使用的v8 9其自带的npx的版本为9 7 在Windows上使用会存在 Path must be a string Received undefined 的错误 通过 GitHub
  • QT程序运行时依赖设置

    本文主要解决Qt程序运行时容易遇到的两个问题 找不到相关的Qt6Core dll Qt6Widgets dll等库 这种情况下 把相关DLL所在目录添加到系统的环境变量 PATH 中 就可以了 无法导入Qt相关的插件 qt qpa plug
  • PCL调错:(3)error C2589“(“:“::“右边的非法标记

    错误提示 造成该错误的原因是 pcl库中的函数模板max与Visual C 中的全局的宏max冲突 百度了以下看了一共有两种解决方案 第一种不用修改库里边的源代码 设置项目属性 在预定义处理器中添加定义NOMINMAX来禁止使用Visual
  • Android 旧项目引入 (kotlin)插件简单记录

    1 确认自己 AS 的kotlin 插件 已经安装 2 选择kotlin plugin updates 文件显示 3 选择configure kotlin in project 进入选择 Android gradle 的选项 由于我这边配置
  • JAVA中数组冒泡排序和选择排序

    冒泡排序的思想 两两之间比较大小 小的数在前 大的数在后 共比较i 1次 static void MaoPaoArray int a for int i 0 i lt a length 2 i for int j 0 j lt a leng
  • tortoisegit 常见错误disconnected no supported authentication methods available(server sent: publickey)

    本文转载自 https blog csdn net yym6789 article details 53807640 1 安装好小乌龟git后 用小乌龟的pull 从github上拉取项目 遇到错误 disconnected no supp
  • 7 整数反转 c++

    leetcode7 整数反转 题目描述 给出一个 32 位的有符号整数 你需要将这个整数中每位上的数字进行反转 注意 如果反转后整数溢出那么就返回 0 算法思路 使用一个64位的long long类型来存储结果整数 避免反转后结果溢出报错
  • C++ 装饰器模式

    什么是装饰器模式 装饰器模式是一种结构型设计模式 实现了在不改变现有对象结构的的同时又拓展了新的功能 装饰器本质上是对现有对象的重新包装 同时装饰器又称为封装器 如何理解装饰器模式 以笔记本电脑为例 当我们购买了一台新笔记本电脑 但我们发现
  • 在openwrt使用C语言增加ubus接口(包含C uci操作)

    在openwrt使用C语言增加ubus接口 包含C uci操作 文章目录 在openwrt使用C语言增加ubus接口 包含C uci操作 创建自己的软件包 软件包结构 编写代码和启动脚本 重点 案例大致分析 实现过程 ubus demo i