执行应用共享内存空间 同步QT进行快速捕获数据流

引言:本文章针对驱动的应用app,例如sensor data内容的获取,显示到QT的一种办法,共享内存。举例子,这是一个常见需求,比如摄像头采集进程与 GUI 显示进程分离,通过共享内存传输图像,避免 socket、pipe 等冗余复制。

进程A(数据采集者) :采集图像数据(如 OpenCV),写入共享内存。
进程B(Qt GUI) :读取共享内存内容并展示图像,避免拷贝。

使用 共享内存(QSharedMemory) 实现 高效通信

实现同步策略,如 信号量 / 标志位 / 双缓冲机制

那么笔者这边用的案例就是下述两个应用app,利用此demo获取数据流,实现题目目标要求,加上共享内存等机制,供读者观看,如下所示:

cpp 复制代码
/*
*
*   file: dht11.c
*   date: 2024-08-06
*   usage: 
*       sudo gcc -o dht11 dht11.c -lrt
*       sudo ./dht11
*
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
#include <sys/mman.h>
#include <errno.h>

#define DEV_NAME "/dev/dht11"

// 定义共享内存结构
struct sensor_data {
    float temperature;
    float humidity;
    int is_valid;
};

int main(int argc, char **argv)
{
	int fd;
	int ret;
    uint8_t data[6];
    int shm_fd;
    struct sensor_data *shm_data;
    
    /* 创建共享内存 */
    shm_fd = shm_open("/dht11_data", O_CREAT | O_RDWR, 0666);
    if (shm_fd < 0) {
        printf("创建共享内存失败: %s\n", strerror(errno));
        printf("错误代码: %d\n", errno);
        return -1;
    }
    printf("成功创建共享内存,fd: %d\n", shm_fd);
    
    /* 设置共享内存大小 */
    if (ftruncate(shm_fd, sizeof(struct sensor_data)) < 0) {
        printf("设置共享内存大小失败: %s\n", strerror(errno));
        printf("错误代码: %d\n", errno);
        close(shm_fd);
        return -1;
    }
    printf("成功设置共享内存大小: %ld\n", sizeof(struct sensor_data));
    
    /* 映射共享内存 */
    shm_data = (struct sensor_data *)mmap(NULL, sizeof(struct sensor_data),
                                        PROT_READ | PROT_WRITE, MAP_SHARED,
                                        shm_fd, 0);
    if (shm_data == MAP_FAILED) {
        printf("映射共享内存失败: %s\n", strerror(errno));
        printf("错误代码: %d\n", errno);
        close(shm_fd);
        return -1;
    }
    printf("成功映射共享内存,地址: %p\n", shm_data);
    
    /* 初始化共享内存数据 */
    shm_data->is_valid = 0;
	
	/* open dev */
	fd = open(DEV_NAME, O_RDWR);
    if (fd < 0) {
		printf("can not open file %s, %d\n", DEV_NAME, fd);
        munmap(shm_data, sizeof(struct sensor_data));
        close(shm_fd);
		return -1;
	}

    while(1) {
		/* read date from dht11 */
		ret = read(fd, &data, sizeof(data));	
        if(ret) {
		    printf("Temperature=%d.%d Humidity=%d.%d\n", data[2], data[3], data[0], data[1]);
            /* 更新共享内存数据 */
            shm_data->temperature = (float)data[2] + (float)data[3]/10.0;
            shm_data->humidity = (float)data[0] + (float)data[1]/10.0;
            shm_data->is_valid = 1;
        } else {
            printf("error reading!\n");
            shm_data->is_valid = 0;
        }
		
		sleep(1);
	}
	
    /* 清理资源 */
	close(fd);
    munmap(shm_data, sizeof(struct sensor_data));
    close(shm_fd);
	
	return 0;
}

这段 dht11.c 是一个 Linux 下使用共享内存同步 DHT11 温湿度传感器数据 的完整示例,涵盖设备读取、共享内存映射与进程间通信的核心流程。

// 定义共享内存结构

sensor_data 是共享给其他进程的数据结构;

is_valid 表示当前温湿度值是否有效;

/dev/dht11 是 DHT11 驱动的设备文件节点。

shm_open 创建/打开名为 /dht11_data 的共享内存对象,权限为 0666(所有用户可读写)

.设置共享内存大小,以及简单的error报错判断了

设置共享内存大小等于 sensor_data 结构体大小(通常为 12字节),将共享内存映射到当前进程地址空间;之后即可通过 shm_data->temperature 等成员直接访问。

把读取结果写入共享内存,供其他程序访问。

总结app案例一:

"共享内存"是多个进程之间最快的数据通信机制。通过 shm_open 创建内核中的一个匿名对象,并映射(mmap)进用户空间,多个进程即可在不同地址空间访问同一块物理内存。"

DHT11芯片 通过 /dev/dht11 驱动 到 应用程序 A (写入) 再到 共享内存段 再到 应用程序 B (如 Qt GUI)

创建共享内存对象(shm_open)

int shm_fd = shm_open("/dht11_data", O_CREAT | O_RDWR, 0666);

设置共享内存大小(ftruncate)

ftruncate(shm_fd, sizeof(struct sensor_data));

映射共享内存到本进程(mmap)

shm_data = (struct sensor_data *)mmap(NULL, sizeof(...),

PROT_READ | PROT_WRITE, MAP_SHARED,

shm_fd, 0);

写入数据(数据生产者)

shm_data->temperature = ...;

shm_data->humidity = ...;

shm_data->is_valid = 1;

另一个进程读取(数据消费者)

int shm_fd = shm_open("/dht11_data", O_RDONLY, 0666);
struct sensor_data* data = mmap(NULL, sizeof(...), PROT_READ, MAP_SHARED, shm_fd, 0);

if (data->is_valid) {
printf("温度: %.1f°C, 湿度: %.1f%%\n", data->temperature, data->humidity);
}

再来一个应用app案例,如下所示:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>

/* 寄存器地址 */
#define SMPLRT_DIV 0x19
#define PWR_MGMT_1 0x6B
#define CONFIG 0x1A
#define ACCEL_CONFIG 0x1C
#define GYRO_CONFIG 0x1B

#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48

//从机地址 MPU6050 地址
#define Address 0x68

// 定义共享内存结构
struct mpu_data {
    float accel_x;
    float accel_y;
    float accel_z;
    float gyro_x;
    float gyro_y;
    float gyro_z;
    int is_valid;
};

//MPU6050 操作相关函数
static int mpu6050_init(int fd,uint8_t addr);
static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val);
static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val);
 static short GetData(int fd,uint8_t addr,unsigned char REG_Address);

 int main(int argc, char *argv[])
 {
 int fd;
 int shm_fd;
 struct mpu_data *shm_data;
 char i2c_dev[20];

 if(argc < 2){
 printf("使用错误!\n");
 printf("用法: %s [设备]\n", argv[0]);
 return -1;
 }

 strcpy(i2c_dev, argv[1]);

 /* 创建共享内存 */
 shm_fd = shm_open("/mpu6050_data", O_CREAT | O_RDWR, 0666);
 if (shm_fd < 0) {
     printf("创建共享内存失败: %s\n", strerror(errno));
     printf("错误代码: %d\n", errno);
     return -1;
 }
 
 printf("成功创建共享内存,fd: %d\n", shm_fd);
 
 /* 设置共享内存大小 */
 if (ftruncate(shm_fd, sizeof(struct mpu_data)) < 0) {
     printf("设置共享内存大小失败: %s\n", strerror(errno));
     printf("错误代码: %d\n", errno);
     close(shm_fd);
     return -1;
 }
 
 printf("成功设置共享内存大小: %ld\n", sizeof(struct mpu_data));
 
 /* 映射共享内存 */
 shm_data = (struct mpu_data *)mmap(NULL, sizeof(struct mpu_data),
                                  PROT_READ | PROT_WRITE, MAP_SHARED,
                                  shm_fd, 0);
 if (shm_data == MAP_FAILED) {
     printf("映射共享内存失败: %s\n", strerror(errno));
     printf("错误代码: %d\n", errno);
     close(shm_fd);
     return -1;
 }
 
 printf("成功映射共享内存,地址: %p\n", shm_data);
 
 /* 初始化共享内存数据 */
 shm_data->is_valid = 0;

 fd = open(argv[1], O_RDWR);
 if (fd < 0) {
     printf("无法打开设备 %s: %s\n", argv[1], strerror(errno));
     munmap(shm_data, sizeof(struct mpu_data));
     close(shm_fd);
     return -1;
 }

 //初始化 MPU6050
 if (mpu6050_init(fd,Address) < 0) {
     printf("MPU6050初始化失败\n");
     close(fd);
     munmap(shm_data, sizeof(struct mpu_data));
     close(shm_fd);
     return -1;
 }

 while(1){
 usleep(1000 * 10);
 shm_data->accel_x = GetData(fd,Address,ACCEL_XOUT_H);
 usleep(1000 * 10);
 shm_data->accel_y = GetData(fd,Address,ACCEL_YOUT_H);
 usleep(1000 * 10);
 shm_data->accel_z = GetData(fd,Address,ACCEL_ZOUT_H);
 usleep(1000 * 10);
 shm_data->gyro_x = GetData(fd,Address,GYRO_XOUT_H);
 usleep(1000 * 10);
 shm_data->gyro_y = GetData(fd,Address,GYRO_YOUT_H);
 usleep(1000 * 10);
 shm_data->gyro_z = GetData(fd,Address,GYRO_ZOUT_H);
 
 shm_data->is_valid = 1;
 
 printf("加速度 X:%6d Y:%6d Z:%6d\n", 
        shm_data->accel_x, shm_data->accel_y, shm_data->accel_z);
 printf("陀螺仪 X:%6d Y:%6d Z:%6d\n\n", 
        shm_data->gyro_x, shm_data->gyro_y, shm_data->gyro_z);
 
 sleep(1);
 }

 close(fd);
 munmap(shm_data, sizeof(struct mpu_data));
 close(shm_fd);

 return 0;
 }

 static int mpu6050_init(int fd,uint8_t addr)
 {
 i2c_write(fd, addr,PWR_MGMT_1,0x00); //配置电源管理,0x00, 正常启动
 i2c_write(fd, addr,SMPLRT_DIV,0x07); //设置 MPU6050 的输出分频既设置采样
 i2c_write(fd, addr,CONFIG,0x06); //配置数字低通滤波器和帧同步引脚
 i2c_write(fd, addr,ACCEL_CONFIG,0x01); //设置量程和 X、Y、Z 轴加速度自检

 return 0;
 }

 static int i2c_write(int fd, uint8_t addr,uint8_t reg,uint8_t val)
 {
 int retries;
 uint8_t data[2];

 data[0] = reg;
 data[1] = val;

 //设置地址长度:0 为 7 位地址
 ioctl(fd,I2C_TENBIT,0);
//设置从机地址
 if (ioctl(fd,I2C_SLAVE,addr) < 0){
 printf("fail to set i2c device slave address!\n");
 close(fd);
 return -1;
 }

 //设置收不到 ACK 时的重试次数
 ioctl(fd,I2C_RETRIES,5);

 if (write(fd, data, 2) == 2){
 return 0;
 }
 else{
 return -1;
 }

 }

 static int i2c_read(int fd, uint8_t addr,uint8_t reg,uint8_t * val)
 {
 int retries;

 //设置地址长度:0 为 7 位地址
 ioctl(fd,I2C_TENBIT,0);

 //设置从机地址
 if (ioctl(fd,I2C_SLAVE,addr) < 0){
 printf("fail to set i2c device slave address!\n");
 close(fd);
 return -1;
 }
//设置收不到 ACK 时的重试次数
 ioctl(fd,I2C_RETRIES,5);

 if (write(fd, &reg, 1) == 1){
 if (read(fd, val, 1) == 1){
 return 0;
 }
 }
 else{
 return -1;
 }
 }

 static short GetData(int fd,uint8_t addr,unsigned char REG_Address)
 {
 char H, L;

 i2c_read(fd, addr,REG_Address, &H);
 usleep(1000);
 i2c_read(fd, addr,REG_Address + 1, &L);
 return (H << 8) +L;
}

简单的IIC应用,不过多提了,重点也是数据落达的问题,内存共享,

加速度 (X/Y/Z)

陀螺仪 (X/Y/Z)

is_valid 是一个标志,说明该结构数据是否有效(是否已经写入)。

这里也是创建共享内存的对象,文件描述符。

设置共享内存的大小

映射

赋值

跟刚刚的流程一摸一样,因此现在数据落达内存区域的链路通了,现在就是QT等gui应用去读取捕获这些数据内容的过程了,如下所示:

初始化,你想数据获取就先创建共享内存空间索引,如下所示:

打开或创建共享内存对象

设置共享内存大小

映射共享内存到用户空间

初始化结构体有效标志

之后就是定时器反复读取了。

共享内存中的数据是通过两个指针 mpu_datadht11_data 来访问的,而这两个指针指向的是映射到共享内存的结构体。

Producer进程

└─> shm_open("/mpu6050_data") + ftruncate()

└─> mmap() 得到指针

└─> 周期性更新 mpu_data 的内容 + is_valid=1

Qt消费者

└─> shm_open("/mpu6050_data")

└─> mmap() 得到 mpu_data 指针

└─> 周期性读取 mpu_data,更新 QLabel

最后笔者再通过应用补充一个知识点。

共享内存(POSIX shm_open + mmap)的底层原理其实是:

1. 内核的内存管理子系统里

共享内存对象 /mpu6050_data/dht11_data 并不是存在某个具体的物理文件上;

它们是内核维护的一段物理内存页(page frames),被映射进进程虚拟地址空间;

这段物理内存区域驻留在 RAM(内存) 中,不在磁盘上。

2. 具体来说,RK3566这类基于Linux的SoC:

RK3566运行的是Linux内核,POSIX共享内存由内核虚拟文件系统(通常是 tmpfs 类型)支持;

共享内存对象表现为 /dev/shm 下的文件(你可以用 ls /dev/shm 查看);

这个目录本身挂载在 tmpfs 上,tmpfs 是基于内存(RAM)的文件系统;

数据实际就在RAM里,并不占用实际磁盘空间

层次 说明
进程虚拟内存 你程序中通过 mmap 得到的指针
内核内存管理 内核分配的物理内存页
tmpfs文件系统 /dev/shm/ 里的文件对应的内存
物理设备 RK3566上的物理RAM

3. 如何验证?

运行 mount | grep shm,一般会看到 /dev/shm 挂载的 tmpfs

ls /dev/shm 可以看到共享内存名字(如果进程打开过);

这些文件大小对应你的 ftruncate 设置大小;

数据写入这些"文件",实质是写入内核分配的RAM页。

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript