执行应用共享内存空间 同步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页。

相关推荐
黑牛先生1 分钟前
【Qt】信号与槽
开发语言·qt
橙子199110161 小时前
Kotlin 中的 Object
android·开发语言·kotlin
callJJ1 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
java·开发语言·spring boot·后端·spring·restful·ioc di
Python开发吖2 小时前
【已解决】python的kafka-python包连接kafka报认证失败
开发语言·python·kafka
@老蝴5 小时前
C语言 — 通讯录模拟实现
c语言·开发语言·算法
♚卜卦7 小时前
面向对象 设计模式简述(1.创建型模式)
开发语言·设计模式
安全系统学习7 小时前
网络安全之RCE简单分析
开发语言·python·算法·安全·web安全
Swift社区8 小时前
Swift 解法详解:如何在二叉树中寻找最长连续序列
开发语言·ios·swift
yutian06068 小时前
C# 支持 ToolTip 功能的控件,鼠标悬停弹提示框
开发语言·microsoft·c#
byte轻骑兵8 小时前
【C++特殊工具与技术】优化内存分配(四):定位new表达式、类特定的new、delete表达式
开发语言·c++