引言:本文章針對驅動的應用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頁。