# 20. 共享内存与mmap ## 一、概述 共享内存是最快的进程间通信(IPC)方式,允许多个进程直接访问同一块物理内存。 ## 二、共享内存概念 ### 2.1 优势 - **零拷贝**:进程直接读写共享内存,无需内核中转 - **高效**:比管道、消息队列快一个数量级 - **随机访问**:不像管道那样只能顺序读写 ### 2.2 挑战 - **同步问题**:需要配合信号量等机制保证数据一致性 - **缓存一致性**:多CPU缓存同步问题 ## 三、System V共享内存 ### 3.1 API详解 ```c #include #include // 创建共享内存段 int shmget(key_t key, size_t size, int shmflg); // key: 共享内存标识符(通常用ftok生成) // size: 共享内存大小 // shmflg: 标志(IPC_CREAT|权限) // 返回值:共享内存ID(shmid),失败返回-1 // 将共享内存映射到进程地址空间 void *shmat(int shmid, const void *shmaddr, int shmflg); // shmid: 共享内存ID // shmaddr: 映射地址(通常为NULL,由系统选择) // shmflg: 标志(SHM_RDONLY只读) // 返回值:映射后的地址,失败返回(void*)-1 // 分离共享内存 int shmdt(const void *shmaddr); // 返回值:成功返回0,失败返回-1 // 控制共享内存 int shmctl(int shmid, int cmd, struct shmid_ds *buf); // cmd: IPC_STAT(获取状态), IPC_SET(设置状态), IPC_RMID(删除) ``` ### 3.2 代码示例:进程间共享数据 ```c #include #include #include #include #include #include #include #include int main() { key_t key = ftok("/tmp", 'a'); int shmid = shmget(key, 1024, IPC_CREAT | 0666); if (shmid == -1) { perror("shmget"); exit(1); } pid_t pid = fork(); if (pid == 0) { // 子进程:写入数据 char *shm = shmat(shmid, NULL, 0); if (shm == (void*)-1) { perror("shmat"); exit(1); } strcpy(shm, "Hello from child process!"); shmdt(shm); exit(0); } else { // 父进程:等待子进程写入后读取 wait(NULL); char *shm = shmat(shmid, NULL, SHM_RDONLY); if (shm == (void*)-1) { perror("shmat"); exit(1); } printf("Parent received: %s\n", shm); shmdt(shm); shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 } return 0; } ``` **编译命令:** ```bash gcc -o shm_demo shm_demo.c ./shm_demo ``` ### 3.3 ftok函数 ```c #include key_t ftok(const char *pathname, int proj_id); // pathname: 存在的文件路径 // proj_id: 项目ID(低8位有效) // 返回值:key_t,失败返回-1 ``` ## 四、mmap内存映射 ### 4.1 mmap/munmap API ```c #include void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // addr: 映射起始地址(通常NULL,系统自动选择) // length: 映射长度 // prot: 保护标志 // flags: 映射标志 // fd: 文件描述符 // offset: 文件偏移量 // 返回值:映射后的地址,失败返回MAP_FAILED int munmap(void *addr, size_t length); // addr: 映射地址 // length: 映射长度 // 返回值:成功返回0,失败返回-1 ``` ### 4.2 保护标志(prot) ```c PROT_READ // 可读 PROT_WRITE // 可写 PROT_EXEC // 可执行 PROT_NONE // 不可访问 ``` ### 4.3 映射标志(flags) ```c MAP_SHARED // 共享映射:修改会写回文件,其他进程可见 MAP_PRIVATE // 私有映射:写时复制(COW) MAP_FIXED // 精确指定地址(危险) MAP_ANONYMOUS // 匿名映射(不关联文件) MAP_ANON // MAP_ANONYMOUS别名 ``` ### 4.4 代码示例:文件映射 ```c #include #include #include #include #include #include #include int main() { int fd = open("test.txt", O_RDWR | O_CREAT, 0666); if (fd == -1) { perror("open"); exit(1); } write(fd, "Hello mmap!", 11); struct stat st; fstat(fd, &st); size_t length = st.st_size; char *mapped = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mapped == MAP_FAILED) { perror("mmap"); close(fd); exit(1); } close(fd); printf("Original: %s\n", mapped); // 修改映射内容(会同步到文件) strcpy(mapped, "Modified mmap!"); printf("Modified: %s\n", mapped); // 同步到磁盘 msync(mapped, length, MS_SYNC); munmap(mapped, length); return 0; } ``` **编译命令:** ```bash gcc -o mmap_file mmap_file.c ./mmap_file cat test.txt # 验证文件内容已修改 ``` ### 4.5 代码示例:匿名共享映射 ```c #include #include #include #include #include #include int main() { size_t length = 1024; int flags = MAP_SHARED | MAP_ANONYMOUS; char *shared = mmap(NULL, length, PROT_READ | PROT_WRITE, flags, -1, 0); if (shared == MAP_FAILED) { perror("mmap"); exit(1); } pid_t pid = fork(); if (pid == 0) { // 子进程写入 strcpy(shared, "Hello from child!"); munmap(shared, length); exit(0); } else { // 父进程等待并读取 wait(NULL); printf("Parent received: %s\n", shared); munmap(shared, length); } return 0; } ``` **编译命令:** ```bash gcc -o mmap_anon mmap_anon.c ./mmap_anon ``` ## 五、mmap设备映射 ### 5.1 嵌入式设备内存映射 在嵌入式系统中,常用mmap映射设备寄存器或framebuffer。 ### 5.2 代码示例:Framebuffer映射 ```c #include #include #include #include #include #include #include int main() { int fb_fd = open("/dev/fb0", O_RDWR); if (fb_fd == -1) { perror("open framebuffer"); exit(1); } struct fb_var_screeninfo vinfo; if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) == -1) { perror("ioctl"); close(fb_fd); exit(1); } size_t screensize = vinfo.yres_virtual * vinfo.xres_virtual * vinfo.bits_per_pixel / 8; char *fb = mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0); if (fb == MAP_FAILED) { perror("mmap framebuffer"); close(fb_fd); exit(1); } // 在屏幕中心画一个白点 size_t x = vinfo.xres / 2; size_t y = vinfo.yres / 2; size_t location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) + (y + vinfo.yoffset) * vinfo.line_length; // 假设32位色深 if (vinfo.bits_per_pixel == 32) { *(uint32_t*)(fb + location) = 0xFFFFFFFF; // 白色 } munmap(fb, screensize); close(fb_fd); return 0; } ``` **编译命令:** ```bash gcc -o fb_demo fb_demo.c ./fb_demo ``` ### 5.3 GPIO设备寄存器映射 ```c #include #include #include #include #include #define GPIO_BASE 0x3F200000 // Raspberry Pi GPIO基地址 #define GPIO_SIZE 0x1000 int main() { int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd == -1) { perror("open /dev/mem"); exit(1); } void *gpio = mmap(NULL, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE); if (gpio == MAP_FAILED) { perror("mmap GPIO"); close(mem_fd); exit(1); } // 读取GPIO状态寄角器(偏移0x34) volatile uint32_t *gplev0 = (volatile uint32_t*)((char*)gpio + 0x34); printf("GPIO State: 0x%08X\n", *gplev0); munmap(gpio, GPIO_SIZE); close(mem_fd); return 0; } ``` **编译命令:** ```bash gcc -o gpio_demo gpio_demo.c sudo ./gpio_demo # 需要root权限访问/dev/mem ``` ## 六、共享内存的同步问题 ### 6.1 为什么需要同步 - 多进程同时读写共享内存会导致数据竞争 - 需要信号量或其他同步机制保证互斥 ### 6.2 信号量配合使用 ```c #include #include #include #include #include #include #include #include #define SHM_NAME "/my_shared_mem" #define SEM_NAME "/my_semaphore" typedef struct { int value; sem_t sem; } shared_data_t; int main() { // 创建共享内存 int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); ftruncate(shm_fd, sizeof(shared_data_t)); shared_data_t *shared = mmap(NULL, sizeof(shared_data_t), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); // 初始化信号量 sem_init(&shared->sem, 1, 1); // 1: 进程间共享 shared->value = 0; pid_t pid = fork(); if (pid == 0) { // 子进程:递增共享变量 for (int i = 0; i < 100000; i++) { sem_wait(&shared->sem); shared->value++; sem_post(&shared->sem); } munmap(shared, sizeof(shared_data_t)); exit(0); } else { // 父进程:递增共享变量 for (int i = 0; i < 100000; i++) { sem_wait(&shared->sem); shared->value++; sem_post(&shared->sem); } wait(NULL); printf("Final value: %d\n", shared->value); sem_destroy(&shared->sem); munmap(shared, sizeof(shared_data_t)); shm_unlink(SHM_NAME); } return 0; } ``` **编译命令:** ```bash gcc -o shm_sync shm_sync.c -lrt -lpthread ./shm_sync ``` ## 七、嵌入式场景 ### 7.1 Framebuffer映射 - 直接映射显存,高效图形渲染 - 支持硬件加速和DMA操作 ### 7.2 多进程共享数据 - 数据采集进程与处理进程共享缓冲区 - 实时数据处理流水线 ### 7.3 设备寄存器映射 - 直接操作硬件寄存器 - 避免系统调用开销 - 适用于GPIO、UART、SPI等外设控制 ### 7.4 DMA缓冲区映射 - 与DMA控制器共享内存 - 高速数据传输(如网络包、视频流) ## 八、注意事项 1. **权限管理**:/dev/mem需要root权限 2. **缓存一致性**:多核CPU需考虑缓存同步 3. **内存对齐**:某些硬件要求特定对齐方式 4. **错误处理**:mmap失败返回MAP_FAILED(不是NULL) 5. **资源清理**:使用后munmap,删除shm_open创建的对象 ## 九、面试要点 1. **mmap与read/write的区别**:mmap零拷贝,减少内核态/用户态切换 2. **MAP_SHARED与MAP_PRIVATE**:共享映射会同步文件,私有映射写时复制 3. **共享内存的同步机制**:信号量、互斥锁、条件变量 4. **mmap在嵌入式中的应用**:framebuffer、设备寄存器、DMA缓冲区 5. **共享内存的优缺点**:高效但需要同步,适用于大数据量IPC