当前位置: 首页 > news >正文

[QNX] C++编程: 外部硬件加速器与SOC共享内存中使用NOCACHE的必要性与优化策略

文章目录

    • 0. 引言
    • 1. 问题描述
    • 2. 解决方案
      • 2.1 默认使用缓存并在每次读取前invalidate缓存
      • 2.2 默认使用NOCACHE并在需要时切换缓存状态
      • 2.3 代码示例
    • 3 测试结果与分析
      • 3.1 分析
      • 3.2 优化策略
    • 4 结论
    • 5 附完整测试C++源码

0. 引言

在嵌入式Linux-ARM系统编程中,外部硬件加速器(如FPGA)和SOC(System on Chip)经常需要共享内存进行数据交换。SOC通过共享内存读取FPGA处理的数据,而FPGA则直接写入内存。这种架构在性能和实时性上有着明显的优势,特别是在需要高速数据传输的场景中,如视频处理、网络通信等。

然而,这种内存共享架构也带来了一些挑战。最主要的问题在于数据一致性:由于FPGA直接操作内存,而不经过CPU的控制,这导致CPU的缓存无法感知到内存中的数据变化。如果SOC继续使用缓存中的旧数据,将可能导致数据的不一致性,进而影响系统的稳定性和可靠性。

1. 问题描述

当SOC读取共享内存时,如果没有指定PROT_NOCACHE(禁止缓存),CPU可能会直接从缓存中读取数据。然而,缓存中的数据可能是上一次读取时缓存下来的旧数据,这将导致SOC获取的不是FPGA最新写入的内容,从而引发一系列问题。例如,在图像处理应用中,可能导致处理的图像帧出现错乱或滞后。

QNX系统对这种问题的描述是:

你应该在共享内存区域使用PROT_NOCACHE,以便在访问可能由硬件(如视频帧缓冲区或内存映射的网络或通信板)修改的双端口内存时,确保读取到最新的数据。如果不使用该标志,处理器可能会返回之前缓存的“过期”数据。

因此,在涉及FPGA与SOC共享内存的场景中,如何正确管理缓存,确保数据一致性,成为了关键。

2. 解决方案

为了解决这个问题,我们可以考虑以下两种方案:

2.1 默认使用缓存并在每次读取前invalidate缓存

在每次SOC读取共享内存数据之前,先使用msync接口将缓存失效,使得CPU在读取时必须从内存中重新获取数据。这种方法能够确保读取的数据是最新的,避免了数据不一致的问题。

然而,尽管这种方法在理论上可行,实际测试中发现其效果并不稳定。在某些情况下,invalidate操作并未成功,使得CPU仍然从缓存中获取了旧数据。

2.2 默认使用NOCACHE并在需要时切换缓存状态

另一种更为可靠的方法是,默认情况下将共享内存映射为NOCACHE,这样SOC每次读取的都是最新的数据。在需要提高读取速度时,可以临时打开缓存,在读取完数据后再将其恢复为NOCACHE状态。

这种方法可以通过mprotect接口实现。具体步骤如下:

  1. 在映射内存时,使用PROT_NOCACHE标志,确保SOC读取的总是最新的数据。
  2. 在需要加速读取时,使用mprotect接口临时打开缓存。
  3. 读取完数据后,再次使用mprotect接口将缓存状态恢复为NOCACHE。

更多请查看之前的文章: QNX 平台下 mmap 缓存与非缓存模式的 memcpy 性能分析

2.3 代码示例

以下是使用mprotect动态切换缓存状态的代码示例:

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {int fd = open("/dev/mem", O_RDWR | O_SYNC);if (fd < 0) {perror("open");return -1;}size_t size = 4096;void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_NOCACHE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap");close(fd);return -1;}// 打开缓存if (mprotect(ptr, size, PROT_READ | PROT_WRITE) != 0) {perror("mprotect");}// 读取数据// ... your code ...// 恢复为NOCACHEif (mprotect(ptr, size, PROT_READ | PROT_WRITE | PROT_NOCACHE) != 0) {perror("mprotect");}munmap(ptr, size);close(fd);return 0;
}

通过这种方法,你可以在需要高速读取数据时临时打开缓存,同时在其他时间保持NOCACHE,以确保数据的一致性。

3 测试结果与分析

为评估上述两种方案的性能,我们进行了多组测试,测试数据如下:

  • memcpy cached 速度:2133.333333 MB/s
  • memcpy nocached 速度:116.363636 MB/s
  • invalidate memcpy cached 速度:1333.333333 MB/s
  • invalidate memcpy nocached 速度:112.280702 MB/s
  • asm memcpy cached 速度:2133.333333 MB/s
  • asm memcpy nocached 速度:225.352113 MB/s

测试方法见之前的文章: QNX 平台下 mmap 缓存与非缓存模式的 memcpy 性能分析

3.1 分析

从测试结果可以看出,使用缓存可以显著提高数据读取的速度,例如memcpy cached的速度达到了2133.333333 MB/s,而在不使用缓存的情况下,速度则大幅下降,仅为116.363636 MB/s。

对于数据一致性要求较高的场景,使用PROT_NOCACHE虽然能够确保数据的一致性,但在读取速度上有明显的劣势。然而,通过动态切换缓存状态的方法,例如使用mprotect接口,我们能够在一定程度上弥补这一缺陷。在默认NOCACHE的情况下,asm memcpy nocached的速度为225.352113 MB/s,虽然比缓存情况下慢,但相比于直接使用memcpy nocached已有明显改善。

3.2 优化策略

综合以上分析,对于FPGA与SOC共享内存的场景,可以采用以下优化策略:

  1. 数据一致性优先:对于需要确保数据一致性的场景,建议默认使用NOCACHE,并在读取数据时临时打开缓存,读取完毕后再恢复为NOCACHE。

  2. 性能优先:对于性能要求更高的场景,可以默认使用缓存,但需在每次读取前使用msync等接口invalidate缓存,以确保数据的一致性。

4 结论

在FPGA与SOC共享内存的场景中,数据一致性和读取速度之间存在一定的权衡。使用PROT_NOCACHE可以确保SOC读取到的总是最新的数据,但会牺牲一定的性能。通过动态切换缓存状态的方法,可以在保证数据一致性的前提下,适当提高数据读取速度。这种方法为处理数据一致性和性能之间的矛盾提供了有效的解决方案。

在实际应用中,根据具体需求选择适当的缓存管理策略,将能够更好地平衡数据一致性和系统性能。

5 附完整测试C++源码

// mmap_memcpy.c
#include <errno.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>#ifndef _QNX_
#define PROT_NOCACHE 0
#endifinline void aarch64_fast_memcpy(void *dst, const void *src, size_t size) {
#ifdef _QNX_void *ss = (void *)src, *dd = (void *)dst;size_t sz = size;asm volatile("loop_start: ""ldp q3, q4,[%0,#0x0]\n""ldp q5, q6,  [%0,#0x20]\n""ldp q7, q8,  [%0,#0x40]\n""ldp q9, q10, [%0,#0x60]\n""stp q3, q4,  [%1,#0x0]\n""stp q5, q6,  [%1,#0x20]\n""stp q7, q8, [%1,#0x40]\n""stp q9, q10, [%1,#0x60]\n""add %0, %0, #0x80\n""add %1, %1, #0x80\n""subs %2, %2, #0x80\n""b.ne loop_start\n""dsb sy\n": /* no output */: "r"(ss), "r"(dd), "r"(sz));
#endif
}off_t offset(unsigned int bytes) {static off_t base_offset = 0x1E000000;off_t return_base_offset = base_offset;base_offset += bytes;return return_base_offset;
}void *mmap_memory(unsigned int bytes, int flag) {int fd = open("/dev/mem", O_RDWR | O_SYNC);if (fd < 0) {printf("open /dev/mem failed: %s\n", strerror(errno));}void *ptr = mmap(NULL, bytes, flag, MAP_SHARED, fd, offset(bytes));close(fd);if (MAP_FAILED == ptr) {printf("mmap failed: %s\n", strerror(errno));}return ptr;
}void *mmap_memory_cached(unsigned int bytes) {return mmap_memory(bytes, PROT_READ | PROT_WRITE);
}void *mmap_memory_nocached(unsigned int bytes) {return mmap_memory(bytes, PROT_READ | PROT_WRITE | PROT_NOCACHE);
}// if C++
#ifdef __cplusplus
#include <chrono>
using namespace std::chrono;
#define start() auto start_ = high_resolution_clock::now();
#define end()                                                                  \auto end_ = high_resolution_clock::now();                                  \double bytes_mb = bytes * count / 1024.0 / 1024.0;                         \double cost_ns = duration_cast<nanoseconds>(end_ - start_).count();        \double mps = bytes_mb / cost_ns * 1e9;#else
#include <time.h>
#include <unistd.h>
double time_diff_ns(struct timespec start, struct timespec end) {return (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec);
}
#define start()                                                                \struct timespec start_, end_;                                              \clock_gettime(CLOCK_REALTIME, &start_);#define end()                                                                  \clock_gettime(CLOCK_REALTIME, &end_);                                      \double bytes_mb = bytes * count / 1024.0 / 1024.0;                         \double cost_ns = time_diff_ns(start_, end_);                               \double mps = bytes_mb / cost_ns * 1e9;#endifdouble memcpy_speed(void *dst, void **src, unsigned int bytes,unsigned int count) {start();for (size_t i = 0; i < count; i++) {memcpy(dst, src[i], bytes);}end();return mps;
}double invalidate_memcpy_speed(void *dst, void **src, unsigned int bytes,unsigned int count) {start();for (size_t i = 0; i < count; i++) {msync(src[i], bytes, MS_INVALIDATE);memcpy(dst, src[i], bytes);}end();return mps;
}double asm_memcpy_speed(void *dst, void **src, unsigned int bytes,unsigned int count) {start();for (size_t i = 0; i < count; i++) {aarch64_fast_memcpy(dst, src[i], bytes);}end();return mps;
}int main(int argc, char *argv[]) {const unsigned int count = 2;unsigned int bytes = 128 * 1024; // 128 KBif (argc > 1) {bytes = atoi(argv[1]) * 1024;}printf("bytes: %d\n", bytes);printf("count: %d\n", count);void *mmap_cached_src[count];void *mmap_nocached_src[count];for (size_t i = 0; i < count; i++) {mmap_cached_src[i] = mmap_memory_cached(bytes);mmap_nocached_src[i] = mmap_memory_nocached(bytes);}void *dst = malloc(bytes);printf("memcpy cached speed: %f MB/s\n",memcpy_speed(dst, mmap_cached_src, bytes, count));printf("memcpy nocached speed: %f MB/s\n",memcpy_speed(dst, mmap_nocached_src, bytes, count));printf("invalidate memcpy cached speed: %f MB/s\n",invalidate_memcpy_speed(dst, mmap_cached_src, bytes, count));printf("invalidate memcpy nocached speed: %f MB/s\n",invalidate_memcpy_speed(dst, mmap_nocached_src, bytes, count));printf("asm memcpy cached speed: %f MB/s dst[0]=%d\n",asm_memcpy_speed(dst, mmap_cached_src, bytes, count), ((char *)dst)[0]);printf("asm memcpy nocached speed: %f MB/s\n",asm_memcpy_speed(dst, mmap_nocached_src, bytes, count));free(dst);for (size_t i = 0; i < count; i++) {munmap(mmap_cached_src[i], bytes);munmap(mmap_nocached_src[i], bytes);}return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • jQuery实现图片轮播效果
  • Redis相关面试题(二)
  • Go框架选战:Gin、Echo、Fiber的终极较量
  • 力扣 | 递增子序列 | 动态规划 | 最长递增子序列、最长递增子序列的个数、及其变式
  • Python-调用pymysql库,执行插入语句
  • 3个月,从Web前端到鸿蒙应用高手
  • 67、ceph
  • Go语言+Vue3开发前后端后台管理系统实战 用户管理的前端界面和表结构分析
  • MySQl 中对数据表的增删改查(基础)
  • 软件测试下的AI之路(6)
  • Python万字长文基础教程第四章:函数
  • 用openssl 创建自签名证书用于内网HTTPS
  • 云原生与微服务
  • 【CS.DB】数据库-关系型数据库-MySQL-3.3.创建和管理表
  • 【NPM】使用教程
  • CentOS 7 防火墙操作
  • HTML5新特性总结
  • javascript从右向左截取指定位数字符的3种方法
  • Yii源码解读-服务定位器(Service Locator)
  • 解决iview多表头动态更改列元素发生的错误
  • 你真的知道 == 和 equals 的区别吗?
  • 扑朔迷离的属性和特性【彻底弄清】
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 使用 Xcode 的 Target 区分开发和生产环境
  • Linux权限管理(week1_day5)--技术流ken
  • NLPIR智能语义技术让大数据挖掘更简单
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (11)MSP430F5529 定时器B
  • (2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少
  • (3)(3.5) 遥测无线电区域条例
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (八)Flask之app.route装饰器函数的参数
  • (编译到47%失败)to be deleted
  • (分享)自己整理的一些简单awk实用语句
  • (十七)Flink 容错机制
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (四)进入MySQL 【事务】
  • (杂交版)植物大战僵尸
  • (转)ObjectiveC 深浅拷贝学习
  • (自用)网络编程
  • *算法训练(leetcode)第四十天 | 647. 回文子串、516. 最长回文子序列
  • .NET : 在VS2008中计算代码度量值
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .Net mvc总结
  • .NET 某和OA办公系统全局绕过漏洞分析
  • .NET/C# 使用反射注册事件
  • .NET_WebForm_layui控件使用及与webform联合使用
  • .net流程开发平台的一些难点(1)
  • .Net语言中的StringBuilder:入门到精通
  • .Net中间语言BeforeFieldInit