引言:本文章针对驱动的应用app,例如sensor data内容的获取,显示到QT的一种办法,共享内存。举例子,这是一个常见需求,比如摄像头采集进程与 GUI 显示进程分离,通过共享内存传输图像,避免 socket、pipe 等冗余复制。
进程A(数据采集者):采集图像数据(如 OpenCV),写入共享内存。
进程B(Qt GUI):读取共享内存内容并展示图像,避免拷贝。
使用 共享内存(QSharedMemory) 实现 高效通信。
实现同步策略,如 信号量 / 标志位 / 双缓冲机制。
那么笔者这边用的案例就是下述两个应用app,利用此demo获取数据流,实现题目目标要求,加上共享内存等机制,供读者观看,如下所示:
/*
*
* 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案例,如下所示:
#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;}//初始化 MPU6050if (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, ®, 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_data
和 dht11_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页。