代码模板之-C语言如何优雅的定义事件和字符串解析?(index < ARRAY_SIZE(str_event) ? str_event[index] : “unknown“)

背景

在平时项目开发中,经常需要将事件或者ID转化为字符串。本文根据Mellanox驱动代码中的ib_event介绍介绍一种比较优雅的定义,判断的方法以及可以未来直接使用的代码。它的核心有点在于可以定义跳空的事件,以及直接使用字符串数组下标判空和越界的方法。

一般方法是定义事件类型和字符串,然后解析。
一般判断方法是 if event < EVENT_START && event > EVENT_END, return “unknown”
并且定义enum也需要指定start和end类似这种:

enum ib_event_type {
	IB_EVENT_START,
	IB_EVENT_CQ_ERR = IB_EVENT_START,
	IB_EVENT_QP_FATAL, //最后一行也有,
	IB_EVENT_END = IB_EVENT_QP_FATAL,
}

该方法需要定义start和end,或者需要动态的用第一个和最后一个,如果第一个和最后一个变化,还需要修改enum,是否有更优雅的一点方法?避免频繁修改,避免if判断?以及做到事件ID可以不连续?

事件字符串解析三部曲

答案是可以用三目运算符来判断evnet是否存在?甚至不用将event从0开始定义,最大event用事件字符串大小来确定(注意不是evnet大小)。并且判断用三目判断是否在其中,在其中用字符串数组取值,否则用指定的字符串。
当然三目运算的本质和if判断一样,最后编译到汇编几乎相同,都是条件判断。但是三目更加优雅。所以在以后很多场景可以用三目来替代if。尤其是双分支场景。
事件ID不连续的解析方法,是根据array的结构化初始化,不连续的跳空位置会自动初始化为0

三部曲定义:

# .h中定义事件和函数
enum ib_event_type {
	IB_EVENT_CQ_ERR,
	IB_EVENT_QP_FATAL, //最后一行也有,
}
const char *__attribute_const__ ib_event_msg(enum ib_event_type event);

# .c中定义事件字符串
static const char * const str_ib_events[] = {
	[IB_EVENT_CQ_ERR]		= "CQ error",
	[IB_EVENT_QP_FATAL]		= "QP fatal error",
}

# .c中定义事件解析函数,入参是事件,如果事件不在范围内直接用三目
const char *__attribute_const__ ib_event_msg(enum ib_event_type event)
{
	size_t index = event;
	return (index < ARRAY_SIZE(str_ib_events) && str_ib_events[index]) ?
			ib_events[index] : "unrecognized event";
}
//注释:注意这里先判断index是否在array的范围内,然后如果在其中,可以直接return str,但是还加一个判断这里为空,这样就非常有效,并且这里的event可以不用连续,编译期间会根据结构化初始化str字符串来空出没有初始化的数据,非常优雅。缺点就是event不能太大,不然str字符串数组占用空间较大。

//注意这里的array_size是内核的标准接口,可以自己定义,也可以包含头文件
// #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

# 使用地方直接调用
printk("Event: %s\n", ib_event_msg(event);

注意这里未知事件并不定义到enum和string中,而是在函数中判断。

Mellanox驱动中的例子

参考文件:include/rdma/ib_verbs.h
https://github.com/torvalds/linux/blob/v6.11/include/rdma/ib_verbs.h
https://github.com/linux-rdma/rdma-core/blob/master/libibverbs/verbs.c

事件定义

enum ib_event_type {
	IB_EVENT_CQ_ERR,
	IB_EVENT_QP_FATAL,
	IB_EVENT_QP_REQ_ERR,
	IB_EVENT_QP_ACCESS_ERR,
	IB_EVENT_COMM_EST,
	IB_EVENT_SQ_DRAINED,
	IB_EVENT_PATH_MIG,
	IB_EVENT_PATH_MIG_ERR,
	IB_EVENT_DEVICE_FATAL,
	IB_EVENT_PORT_ACTIVE,
	IB_EVENT_PORT_ERR,
	IB_EVENT_LID_CHANGE,
	IB_EVENT_PKEY_CHANGE,
	IB_EVENT_SM_CHANGE,
	IB_EVENT_SRQ_ERR,
	IB_EVENT_SRQ_LIMIT_REACHED,
	IB_EVENT_QP_LAST_WQE_REACHED,
	IB_EVENT_CLIENT_REREGISTER,
	IB_EVENT_GID_CHANGE,
	IB_EVENT_WQ_FATAL,
	IB_EXP_EVENT_XRQ_QP_ERR,
	IB_EVENT_XRQ_NVMF_BACKEND_CTRL_PCI_ERR,
	IB_EVENT_XRQ_NVMF_BACKEND_CTRL_TO_ERR,
};

事件字符串

static const char * const ib_events[] = {
	[IB_EVENT_CQ_ERR]		= "CQ error",
	[IB_EVENT_QP_FATAL]		= "QP fatal error",
	[IB_EVENT_QP_REQ_ERR]		= "QP request error",
	[IB_EVENT_QP_ACCESS_ERR]	= "QP access error",
	[IB_EVENT_COMM_EST]		= "communication established",
	[IB_EVENT_SQ_DRAINED]		= "send queue drained",
	[IB_EVENT_PATH_MIG]		= "path migration successful",
	[IB_EVENT_PATH_MIG_ERR]		= "path migration error",
	[IB_EVENT_DEVICE_FATAL]		= "device fatal error",
	[IB_EVENT_PORT_ACTIVE]		= "port active",
	[IB_EVENT_PORT_ERR]		= "port error",
	[IB_EVENT_LID_CHANGE]		= "LID change",
	[IB_EVENT_PKEY_CHANGE]		= "P_key change",
	[IB_EVENT_SM_CHANGE]		= "SM change",
	[IB_EVENT_SRQ_ERR]		= "SRQ error",
	[IB_EVENT_SRQ_LIMIT_REACHED]	= "SRQ limit reached",
	[IB_EVENT_QP_LAST_WQE_REACHED]	= "last WQE reached",
	[IB_EVENT_CLIENT_REREGISTER]	= "client reregister",
	[IB_EVENT_GID_CHANGE]		= "GID changed",
	[IB_EXP_EVENT_XRQ_QP_ERR]	= "XRQ QP error",
	[IB_EVENT_XRQ_NVMF_BACKEND_CTRL_PCI_ERR] = "XRQ NVMF backend ctrl PCI error",
	[IB_EVENT_XRQ_NVMF_BACKEND_CTRL_TO_ERR] = "XRQ NVMF backend ctrl timeout error",
};

事件字符串解析

const char *__attribute_const__ ib_event_msg(enum ib_event_type event)
{
	size_t index = event;

	return (index < ARRAY_SIZE(ib_events) && ib_events[index]) ?
			ib_events[index] : "unrecognized event";
}
EXPORT_SYMBOL(ib_event_msg);

综述

本文根据Mellanox驱动代码中的ib_event介绍介绍一种比较优雅的事件定义和解析。判断的方法以及可以未来直接使用的代码。它的核心有点在于可以定义跳空的事件,以及直接使用字符串数组下标判空和越界的方法。
用三目运算符来判断evnet是否存在?甚至不用将event从0开始定义,最大event用事件字符串大小来确定,利用C语言结构化初始化定义自动扩展未定义的ID。并且判断用三目判断是否在其中,在其中用字符串数组取值,否则用指定的字符串。

// 包含标准库头文件 #include <unistd.h> #include <signal.h> // 包含uboxubus库头文件 #include <libubox/blobmsg_json.h> #include "libubus.h" #include "count.h" // 自定义头文件,包含count_to_number函数声明 // 全局变量声明 static struct ubus_context *ctx; // UBus上下文 static struct ubus_subscriber test_event; // 订阅者对象 static struct blob_buf b; // 用于构造响应数据的缓冲区 // 定义hello方法的参数ID enum { HELLO_ID, HELLO_MSG, __HELLO_MAX }; // 定义hello方法的参数解析策略 static const struct blobmsg_policy hello_policy[] = { [HELLO_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, // id字段为32位整数 [HELLO_MSG] = { .name = "msg", .type = BLOBMSG_TYPE_STRING }, // msg字段为字符串 }; // 自定义结构体用于异步响应 struct hello_request { struct ubus_request_data req; // UBus请求数据 struct uloop_timeout timeout; // 超时定时器 int fd; // 管道文件描述符 int idx; // 消息计数器 char data[]; // 可变长度数据 }; // 定时器回调函数:发送文件描述符数据 static void test_hello_fd_reply(struct uloop_timeout *t) { struct hello_request *req = container_of(t, struct hello_request, timeout); char *data; // 构造消息 data = alloca(strlen(req->data) + 32); sprintf(data, "msg%d: %s\n", ++req->idx, req->data); // 写入管道 if (write(req->fd, data, strlen(data)) < 0) { close(req->fd); free(req); return; } // 重新设置定时器 uloop_timeout_set(&req->timeout, 1000); } // 定时器回调函数:发送UBus响应 static void test_hello_reply(struct uloop_timeout *t) { struct hello_request *req = container_of(t, struct hello_request, timeout); int fds[2]; // 构造JSON响应 blob_buf_init(&b, 0); blobmsg_add_string(&b, "message", req->data); ubus_send_reply(ctx, &req->req, b.head); // 创建管道用于异步通信 if (pipe(fds) == -1) { fprintf(stderr, "Failed to create pipe\n"); return; } // 设置文件描述符并完成延迟请求 ubus_request_set_fd(ctx, &req->req, fds[0]); ubus_complete_deferred_request(ctx, &req->req, 0); req->fd = fds[1]; // 切换定时器回调为文件描述符模式 req->timeout.cb = test_hello_fd_reply; test_hello_fd_reply(t); } // hello方法处理函数 static int test_hello(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct hello_request *hreq; struct blob_attr *tb[__HELLO_MAX]; const char format[] = "%s received a message: %s"; const char *msgstr = "(unknown)"; // 解析客户端发送的参数 blobmsg_parse(hello_policy, ARRAY_SIZE(hello_policy), tb, blob_data(msg), blob_len(msg)); // 获取msg参数 if (tb[HELLO_MSG]) msgstr = blobmsg_data(tb[HELLO_MSG]); // 分配内存 size_t len = sizeof(*hreq) + sizeof(format) + strlen(obj->name) + strlen(msgstr) + 1; hreq = calloc(1, len); if (!hreq) return UBUS_STATUS_UNKNOWN_ERROR; // 构造响应消息 snprintf(hreq->data, len, format, obj->name, msgstr); // 延迟处理请求 ubus_defer_request(ctx, req, &hreq->req); hreq->timeout.cb = test_hello_reply; // 设置定时器回调 uloop_timeout_set(&hreq->timeout, 1000); // 1秒后触发 return 0; } // 定义watch方法的参数ID enum { WATCH_ID, WATCH_COUNTER, __WATCH_MAX }; // watch方法的参数解析策略 static const struct blobmsg_policy watch_policy[__WATCH_MAX] = { [WATCH_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, [WATCH_COUNTER] = { .name = "counter", .type = BLOBMSG_TYPE_INT32 }, }; // 对象移除回调函数 static void test_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s, uint32_t id) { fprintf(stderr, "Object %08x went away\n", id); } static int test_notify(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { #if 0 // JSON格式化调试输出 char *str; str = blobmsg_format_json(msg, true); fprintf(stderr, "Received notification '%s': %s\n", method, str); free(str); #endif return 0; } // watch方法处理函数 static int test_watch(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__WATCH_MAX]; int ret; // 解析参数 blobmsg_parse(watch_policy, __WATCH_MAX, tb, blob_data(msg), blob_len(msg)); if (!tb[WATCH_ID]) return UBUS_STATUS_INVALID_ARGUMENT; // 设置订阅者回调 test_event.remove_cb = test_handle_remove; test_event.cb = test_notify; // 订阅指定对象 ret = ubus_subscribe(ctx, &test_event, blobmsg_get_u32(tb[WATCH_ID])); fprintf(stderr, "Watching object %08x: %s\n", blobmsg_get_u32(tb[WATCH_ID]), ubus_strerror(ret)); return ret; } // count方法参数ID enum { COUNT_TO, COUNT_STRING, __COUNT_MAX }; // count方法参数解析策略 static const struct blobmsg_policy count_policy[__COUNT_MAX] = { [COUNT_TO] = { .name = "to", .type = BLOBMSG_TYPE_INT32 }, [COUNT_STRING] = { .name = "string", .type = BLOBMSG_TYPE_STRING }, }; // count方法处理函数 static int test_count(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__COUNT_MAX]; char *s1, *s2; uint32_t num; // 解析参数 blobmsg_parse(count_policy, __COUNT_MAX, tb, blob_data(msg), blob_len(msg)); if (!tb[COUNT_TO] || !tb[COUNT_STRING]) return UBUS_STATUS_INVALID_ARGUMENT; // 获取参数值 num = blobmsg_get_u32(tb[COUNT_TO]); s1 = blobmsg_get_string(tb[COUNT_STRING]); // 执行计数操作 s2 = count_to_number(num); if (!s1 || !s2) { free(s2); return UBUS_STATUS_UNKNOWN_ERROR; } // 构造响应 blob_buf_init(&b, 0); blobmsg_add_u32(&b, "rc", strcmp(s1, s2)); ubus_send_reply(ctx, req, b.head); free(s2); return 0; } // 注册UBus方法 static const struct ubus_method test_methods[] = { UBUS_METHOD("hello", test_hello, hello_policy), UBUS_METHOD("watch", test_watch, watch_policy), UBUS_METHOD("count", test_count, count_policy), }; // 定义对象类型 static struct ubus_object_type test_object_type = UBUS_OBJECT_TYPE("test", test_methods); // 定义UBus对象 static struct ubus_object test_object = { .name = "test", .type = &test_object_type, .methods = test_methods, .n_methods = ARRAY_SIZE(test_methods), }; // 服务主函数 static void server_main(void) { int ret; // 注册UBus对象 ret = ubus_add_object(ctx, &test_object); if (ret) fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); // 注册订阅者 ret = ubus_register_subscriber(ctx, &test_event); if (ret) fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret)); // 进入事件循环 uloop_run(); } int main(int argc, char **argv) { const char *ubus_socket = NULL; int ch; // 解析命令行参数 while ((ch = getopt(argc, argv, "cs:")) != -1) { switch (ch) { case 's': ubus_socket = optarg; break; default: break; } } // 初始化事件循环 uloop_init(); signal(SIGPIPE, SIG_IGN); // 连接UBus ctx = ubus_connect(ubus_socket); if (!ctx) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } // 将UBus上下文绑定到事件循环 ubus_add_uloop(ctx); // 启动服务 server_main(); // 清理资源 ubus_free(ctx); uloop_done(); return 0; } 帮我解释下这段代码
09-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值