硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2566|回复: 26
收起左侧

[客户分享] X-track提供了一种任务间通讯的框架。

  [复制链接]

96

主题

535

回帖

838

积分

金牌会员

积分
838
发表于 2024-6-7 10:20:02 | 显示全部楼层 |阅读模式
data_center

它抽象出了一个data center用于任务间通讯。

任务可以在data center里面注册account,然后提供subscribe,在订阅了相关的账号之后,可以通过publish推送,pull下拉。



这样子任务间的通讯就可以解耦合了。


评分

参与人数 2金币 +70 收起 理由
OldGerman + 20 很给力!
eric2013 + 50 很给力!

查看全部评分

共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-7-31 11:48:19 | 显示全部楼层
之前这个是用c++写的,我把它改成c语言了。然后写了一个测试的demo。


原来c++的动态数组,我用链表实现了。然后一些重载的api改成了不同的名字。
不过由于本人水平有限,可能改后的版本会存在各种内存泄漏的bug...
目前没经过严格的测试,谨慎使用。

改后的代码如下:
datacenter.7z (363.37 KB, 下载次数: 37)






共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 10:34:42 | 显示全部楼层
来看一下account的数据结构全文如下:

[C] 纯文本查看 复制代码
class Account
{
public:

    /* Event type enumeration */
    typedef enum
    {
        EVENT_NONE,
        EVENT_PUB_PUBLISH, // Publisher posted information
        EVENT_SUB_PULL,    // Subscriber data pull request
        EVENT_NOTIFY,      // Subscribers send notifications to publishers
        EVENT_TIMER,       // Timed event
        _EVENT_LAST
    } EventCode_t;

    /* Error type enumeration */
    typedef enum
    {
        RES_OK                  =  0,
        RES_UNKNOW              = -1,
        RES_SIZE_MISMATCH       = -2,
        RES_UNSUPPORTED_REQUEST = -3,
        RES_NO_CALLBACK         = -4,
        RES_NO_CACHE            = -5,
        RES_NO_COMMITED         = -6,
        RES_NOT_FOUND           = -7,
        RES_PARAM_ERROR         = -8
    } ResCode_t;

    /* Event parameter structure */
    typedef struct
    {
        EventCode_t event; // Event type
        Account* tran;     // Pointer to sender
        Account* recv;     // Pointer to receiver
        void* data_p;      // Pointer to data
        uint32_t size;     // The length of the data
    } EventParam_t;

    /* Event callback function pointer */
    typedef int(*EventCallback_t)(Account* account, EventParam_t* param);

    typedef std::vector<Account*> AccountVector_t;

public:
    Account(
        const char* id,
        DataCenter* center,
        uint32_t bufSize = 0,
        void* userData = nullptr
    );
    ~Account();

    Account* Subscribe(const char* pubID);
    bool Unsubscribe(const char* pubID);
    bool Commit(const void* data_p, uint32_t size);
    int Publish();
    int Pull(const char* pubID, void* data_p, uint32_t size);
    int Pull(Account* pub, void* data_p, uint32_t size);
    int Notify(const char* pubID, const void* data_p, uint32_t size);
    int Notify(Account* pub, const void* data_p, uint32_t size);
    void SetEventCallback(EventCallback_t callback);
    void SetTimerPeriod(uint32_t period);
    void SetTimerEnable(bool en);
    size_t GetPublishersSize();
    size_t GetSubscribersSize();

public:
    const char* ID;      /* Unique account ID */
    DataCenter* Center;  /* Pointer to the data center */
    void* UserData;

    AccountVector_t publishers;  /* Followed publishers */
    AccountVector_t subscribers; /* Followed subscribers */

    struct
    {
        EventCallback_t eventCallback;
        lv_timer_t* timer;
        PingPongBuffer_t BufferManager;
        uint32_t BufferSize;
    } priv;

private:
    static void TimerCallbackHandler(lv_timer_t* task);
};




共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 10:49:25 | 显示全部楼层
本帖最后由 会飞的猪_2020 于 2024-6-7 10:54 编辑

Snipaste_2024-06-07_10-35-16.png

先来看这部分内容,这里可以看到每个account有一个publishers和subscribers。
这两个貌似是c++的语法里的动态数组,我不太熟悉。


如果拿b站来举例子,这两个应该就是“关注”和“粉丝”了。
一个是你关注的账号的列表,一个是关注你账号的列表。

subscribe的流程如下:
Snipaste_2024-06-07_10-48-49.png

用图片表示

Snipaste_2024-06-07_10-54-05.png






共产主义一定胜利!
回复

使用道具 举报

14

主题

246

回帖

288

积分

高级会员

积分
288
发表于 2024-6-7 11:00:11 | 显示全部楼层
这么牛逼,研究一下,确实需要这么个东西,一直没有成熟的方案
回复

使用道具 举报

48

主题

376

回帖

520

积分

金牌会员

积分
520
发表于 2024-6-7 11:18:59 | 显示全部楼层
呃,订阅发布,MQTT ?
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 11:41:45 | 显示全部楼层
那么订阅好了之后,要如何实现发送呢消息,下拉消息等操作呢?

我们肯定就需要一个调度的机制,在不停的轮询,然后才可以把对应的事件通知给对应的账号。

它这里是这样实现的:
Snipaste_2024-06-07_11-35-16.png
Snipaste_2024-06-07_11-35-20.png

利用SetEventCallback这个函数来注册回调函数,然后使用SetTimerPeriod来启动这个定时器。(所以需要OS提供软件定时器功能才可以使用)
Snipaste_2024-06-07_11-40-48.png
Snipaste_2024-06-07_11-40-45.png
共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 11:50:58 | 显示全部楼层
以这个作者工程里的实际为例,可以看它的GPS任务:

Snipaste_2024-06-07_11-48-30.png



另外他创建这些任务通讯机制用了宏定义,这些宏定义也很值得分析。(感觉类似于export机制)
用了宏定义之后,就可以不用再显示的调用函数,只需要在对应文件下面:DATA_PROC_INIT_DEF(name){}
就可以自动在外面初始化。
不用担心忘记初始化了。
共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 11:55:35 | 显示全部楼层
我们来看一下它的初始化机制,主要是DataProc.cpp,DataProc.这两个文件,全部的代码如下:


[C] 纯文本查看 复制代码
#include "DataProc.h"
#include "../HAL/HAL.h"

static DataCenter center("CENTER");

DataCenter* DataProc::Center()
{
    return &#162;er;
}

void DataProc_Init()
{
#define DP_DEF(NODE_NAME, BUFFER_SIZE)\
    Account* act##NODE_NAME = new Account(#NODE_NAME, &#162;er, BUFFER_SIZE);
#  include "DP_LIST.inc"
#undef DP_DEF

#define DP_DEF(NODE_NAME, BUFFER_SIZE)\
do{\
    DATA_PROC_INIT_DEF(NODE_NAME);\
    _DP_##NODE_NAME##_Init(act##NODE_NAME);\
}while(0)
#  include "DP_LIST.inc"
#undef DP_DEF

}


[C] 纯文本查看 复制代码
#ifndef __DATA_PROC_H
#define __DATA_PROC_H

#include "Utils/DataCenter/DataCenter.h"
#include "../HAL/HAL_Def.h"
#include "DataProc_Def.h"

#define DATA_PROC_INIT_DEF(name)   void _DP_##name##_Init(Account* account)
#define DATA_PROC_INIT_STRUCT(sct) memset(&sct, 0, sizeof(sct))

void DataProc_Init();

namespace DataProc
{

DataCenter* Center();
uint32_t    GetTick();
uint32_t    GetTickElaps(uint32_t prevTick);
const char* MakeTimeString(uint64_t ms, char* buf, uint16_t len);

}

#endif




只需要在主函数中调用DataProc_Init,之后就可以使用DATA_PROC_INIT_DEF(name)来初始化了。
不用一个个把函数加入到主函数中去了。
共产主义一定胜利!
回复

使用道具 举报

0

主题

13

回帖

13

积分

新手上路

积分
13
发表于 2024-6-7 14:33:41 | 显示全部楼层
就是太浪费内存了。
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-6-7 16:14:43 | 显示全部楼层
本帖最后由 会飞的猪_2020 于 2024-6-7 16:16 编辑
inkfish321 发表于 2024-6-7 14:33
就是太浪费内存了。

不会呀,你要传输多大的信息,申请多大的内存呀。
结构体本身不怎么占用内存的,然后你创建一个账号的时候,要传输多大的数据,申请多大的内存*2(因为是pingpong buffer)。

Snipaste_2024-06-07_16-13-17.png

Snipaste_2024-06-07_16-13-21.png

共产主义一定胜利!
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
115892
QQ
发表于 2024-6-8 01:21:03 | 显示全部楼层
谢谢楼主分享
回复

使用道具 举报

6

主题

681

回帖

699

积分

金牌会员

积分
699
QQ
发表于 2024-6-8 18:25:27 来自手机 | 显示全部楼层
感谢楼主分享,
回复

使用道具 举报

4

主题

178

回帖

190

积分

初级会员

积分
190
发表于 2024-7-31 14:23:12 | 显示全部楼层
和QPC很像
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-4 15:18:44 | 显示全部楼层
会飞的猪_2020 发表于 2024-7-31 11:48
之前这个是用c++写的,我把它改成c语言了。然后写了一个测试的demo。

这个文件里的代码是有bug的。。
之前在STM32F103的时候没测出来。。后来在bl602上面测出来了。
共产主义一定胜利!
回复

使用道具 举报

6

主题

82

回帖

100

积分

初级会员

积分
100
发表于 2024-11-4 15:55:47 | 显示全部楼层
会飞的猪_2020 发表于 2024-11-4 15:18
这个文件里的代码是有bug的。。
之前在STM32F103的时候没测出来。。后来在bl602上面测出来了。

细说啥问题
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-4 16:05:58 | 显示全部楼层

有一个地方的malloc我改写的时候,size忘记乘2了。但是后面meset的时候是szie是乘2的。
导致会把动态内存外的数据也给清零。下面这个buffer_size在malloc的时候需要乘二
[C] 纯文本查看 复制代码
account_t *account_init(const char *id, data_center_t *center, unsigned int buffer_size, void *user_data)
{
    account_t *new = __malloc(sizeof(account_t));
    bool error = false;
    do {
        if (new == NULL) {
            DATA_CENTER_TRACE("malloc new account failed.\n");
            break;
        }

        error = true;
        memset(&new->priv, 0, sizeof(new->priv));
        new->id = id;
        new->center = center;
        new->user_data = user_data;
        INIT_LIST_HEAD(&new->fans_list);
        INIT_LIST_HEAD(&new->followers_list);
        if (buffer_size != 0) {
            unsigned char *buffer = __malloc(buffer_size * 2);
            if (buffer == NULL) {
                DATA_CENTER_TRACE("account[%s] buffer malloc failed\n", id);
                break;
            }
            memset(buffer, 0, buffer_size * 2);
            unsigned char *buf0 = buffer;
            unsigned char *buf1 = buffer + buffer_size;
            pingpong_buffer_init(&new->priv.buffer_manager, buf0, buf1);
            new->priv.buffer_size = buffer_size;
            DATA_CENTER_TRACE("account[%s] cached %dx2 bytes\n", id, buffer_size);
        }
        if (datacenter_add_account(center, new) == false) {
            DATA_CENTER_TRACE("account[%s] add to center[%s] failed\n", id, center->name);
            break;
        }
        error = false;
    } while (0);

    if (error == true) {
        __free(new);
        new = NULL;
    }
    return new;
}
共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-4 16:09:41 | 显示全部楼层

是我写的有bug。原作者的应该没有bug。
我抄的时候抄漏了。
(原作者的是C++动态数组实现的,C语言没这个特性,我把它改成了链表)
但是我抄的时候malloc里的size大小抄漏了。
共产主义一定胜利!
回复

使用道具 举报

1

主题

13

回帖

16

积分

新手上路

积分
16
发表于 2024-11-5 22:22:47 | 显示全部楼层
这跟普通的消息队列比起来有啥优点?那个“动态数组”是C++ STL 里的标准容器,emmm,能用STL 的话确实会很方便,但是默认的动态内存管理总觉得会有隐患。
回复

使用道具 举报

2

主题

9

回帖

15

积分

新手上路

积分
15
发表于 2024-11-7 15:02:34 | 显示全部楼层
能发个最新版本的代码吗
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-7 16:48:04 | 显示全部楼层
etberzin 发表于 2024-11-5 22:22
这跟普通的消息队列比起来有啥优点?那个“动态数组”是C++ STL 里的标准容器,emmm,能用STL 的话确实会很 ...

消息队列没办法广播。
比如说你有5个任务需要获取蓝牙配网成功这个信息。

用消息队列的话就要创建5个消息队列,然后一个个去发送。
共产主义一定胜利!
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-7 16:51:34 | 显示全部楼层
本帖最后由 会飞的猪_2020 于 2024-11-7 16:53 编辑
Piii 发表于 2024-11-7 15:02
能发个最新版本的代码吗
我再多试试。目前所有代码还在重构中。
共产主义一定胜利!
回复

使用道具 举报

2

主题

9

回帖

15

积分

新手上路

积分
15
发表于 2024-11-7 17:36:26 | 显示全部楼层
会飞的猪_2020 发表于 2024-11-7 16:48
消息队列没办法广播。
比如说你有5个任务需要获取蓝牙配网成功这个信息。

这部分感觉可以用Freertos的xQueuePeek和xQueueOverwrite来实现,就是感觉耦合还是比较大,至少全局得知道个队列句柄
回复

使用道具 举报

1

主题

13

回帖

16

积分

新手上路

积分
16
发表于 2024-11-7 22:30:47 | 显示全部楼层
会飞的猪_2020 发表于 2024-11-7 16:48
消息队列没办法广播。
比如说你有5个任务需要获取蓝牙配网成功这个信息。

不知道能不能基于环形队列来实现消息队列,不用链表。每个队列只有一个发送,但是可以有多个接收,每个接收方自己持有一个read_index,用来从队列里取数据,所有接收方相互独立,互不影响。发送发持有一个write_index,只往队列写数据。这样一来写入和读取都不需要加锁。只要接受方在新消息产生后及时读取,read_index 和write_index 的距离比较近,不超过环形队列的长度,读取数据时就不会遇到数据覆盖的问题。
回复

使用道具 举报

96

主题

535

回帖

838

积分

金牌会员

积分
838
 楼主| 发表于 2024-11-18 11:49:31 | 显示全部楼层
Piii 发表于 2024-11-7 15:02
能发个最新版本的代码吗

fp-sdk-stm32f103.7z (16.74 MB, 下载次数: 28)
共产主义一定胜利!
回复

使用道具 举报

2

主题

9

回帖

15

积分

新手上路

积分
15
发表于 2024-11-21 17:31:05 | 显示全部楼层
回复

使用道具 举报

4

主题

18

回帖

30

积分

新手上路

积分
30
发表于 2024-11-23 08:17:50 | 显示全部楼层
我之前一直想将整个X-TRAKE改成用C,一直没能很好地完成,没想到楼主也弄了这个,我也觉得这个通信方式挺好的,这个通信方式应该是源自MQTT协议。最后谢谢楼主分享!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-5-25 12:47 , Processed in 0.411513 second(s), 31 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表