一、内存映射(mmap)技术
(一)内存映射概念
将磁盘文件与内存中的缓冲区建立映射关系,使进程可直接访问内存如同访问文件,无需调用传统的 read/write 函数。
(二)mmap () 优点
- 实现用户空间与内核空间的高效数据交互
- 减少数据拷贝次数,提升 I/O 效率
- 支持多进程共享文件数据
(三)mmap () 函数定义与参数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- addr:指定映射内存地址(通常设为 NULL,由系统自动分配)
- length:映射区域字节数(必须 > 0)
- prot:内存访问权限(PROT_READ/WRITE/EXEC/NONE)
- flags:映射标志(MAP_SHARED/PRIVATE/FIXED/ANONYMOUS)
- fd:映射文件句柄(匿名映射时为 - 1)
- offset:文件映射偏移量(需为 0 或 4K 整数倍)
(四)使用注意事项
- 映射创建时隐含一次文件读操作,将数据载入内存
- MAP_SHARED 模式下,映射权限需≤文件打开权限
- 映射区释放与文件关闭无关,文件可提前关闭
- 映射文件大小必须 > 0,否则访问会报错
- 偏移量需为 0 或 4K 倍数,否则报非法参数错误
- 映射大小可大于文件,但仅能访问文件对应内存区域
- mmap 出错概率高,必须检查返回值(五)映射种类与代码示例
1. 基于文件的映射
- 基于文件的内存映射写操作示例
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr; // 定义映射区首地址指针
int fd; // 定义文件描述符
// 以读写模式打开文件"test"
fd = open("test", O_RDWR);
if(fd < 0){
perror("open"); // 打开失败时打印错误信息
return 0;
}
// 获取文件长度
int len = lseek(fd, 0, SEEK_END);
// 创建内存映射区
addr = mmap(NULL, 2048, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap"); // 映射失败时打印错误信息
return 0;
}
close(fd); // 关闭文件,映射区仍可使用
// 向映射区写入数据,每个字节写入'a'
int i = 0;
while(i < 2048){
memcpy((addr + i), "a", 1);
i++;
sleep(1); // 每秒写入一个字节
}
}
- 基于文件的内存映射读操作示例
#include <sys/mman.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(){ void *addr; int fd; fd = open("test", O_RDWR); if(fd < 0){ perror("open"); return 0; } int len = lseek(fd, 0, SEEK_END); // 创建与写操作相同的映射区 addr = mmap(NULL, 2048, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(addr == MAP_FAILED){ perror("mmap"); return 0; } close(fd); // 循环读取映射区数据 while(1){ printf("read=%s\n", (char*)(addr)); sleep(1); // 每秒读取一次 } }2. 匿名映射 - 父子进程通信示例
#include <sys/mman.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> int main(){ void *addr; // 创建匿名映射区,不依赖文件 addr = mmap(NULL, 2048, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if(addr == MAP_FAILED){ perror("mmap"); return 0; } pid_t pid; pid = fork(); // 创建子进程 if(pid < 0){ perror("fork"); return 0; } else if(pid > 0){ // 父进程向映射区写入数据 memcpy(addr, "1234567890", 10); wait(NULL); // 等待子进程结束 } else { // 子进程延迟1秒后读取数据 sleep(1); printf("read father val=%s\n", (char *)addr); } munmap(addr, 2048); // 释放映射区 }(六)内存映射释放函数 munmap
int munmap(void *addr, size_t length); - 成功返回 0,失败返回 - 1
- 需传入 mmap 返回的首地址和映射长度
二、System V 共享内存
(一)IPC 基础概念
- IPC 对象:包括共享内存、消息队列、信号灯集
- Key:唯一标识 IPC 对象,通过 ftok 生成
- ID:创建后生成的唯一标识符
- 特性:创建后持续存在,需显式删除
(二)共享内存使用步骤
- 生成 key:通过
ftok函数基于文件和项目 ID 生成唯一键 - 创建共享内存:
shmget函数创建指定大小的共享内存 - 映射到进程:
shmat函数将共享内存映射到进程地址空间 - 数据操作:直接通过指针读写共享内存
- 撤销映射:
shmdt函数取消进程对共享内存的映射 - 删除共享内存:
shmctl函数删除内核中的共享内存对象
(三)核心函数详解
1. ftok 函数(生成 Key)
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
- 参数:path 为存在的文件路径,proj_id 为 1-255 的数字
- 返回:成功返回 key,失败返回 - 1
2. shmget 函数(创建 / 打开共享内存)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
- 参数:key 为关联键,size 为内存大小,shmflg 为标志位(如 IPC_CREAT|0666)
- 返回:成功返回共享内存 ID,失败返回 - 1
3. shmat 函数(映射共享内存)
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数:shmid 为共享内存 ID,shmaddr 为映射地址(NULL 自动映射),shmflg 为标志位
- 返回:成功返回映射地址,失败返回 (void *)-1
4. shmdt 函数(撤销映射)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
- 参数:shmaddr 为映射地址
- 返回:成功返回 0,失败返回 - 1
5. shmctl 函数(控制共享内存)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数:shmid 为 ID,cmd 为操作类型(IPC_STAT/SET/RMID),buf 为属性指针
- 返回:成功返回 0,失败返回 - 1
(四)代码示例集
1. 生成 Key 并创建共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
// 通过ftok生成key
key = ftok("keytest", 100);
if(key < 0){
perror("ftok");
return 0;
}
printf("key=%x\n", key); // 打印生成的key值
// 创建共享内存
shmid = shmget(key, 512, IPC_CREAT | 0666);
if(shmid < 0){
perror("shmget");
return 0;
}
printf("shmid=%d\n", shmid); // 打印共享内存ID
}
2. 向共享内存写入数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf; // 指向共享内存的指针
key = ftok("keytest", 100);
if(key < 0){
perror("ftok");
return 0;
}
shmid = shmget(key, 512, IPC_CREAT | 0666);
if(shmid < 0){
perror("shmget");
return 0;
}
// 映射共享内存到进程地址空间
buf = shmat(shmid, NULL, 0);
if(buf < 0){
perror("shmat");
return 0;
}
// 向共享内存写入字符串
strcpy(buf, "hello world");
}
3. 从共享内存读取数据
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest", 100);
if(key < 0){
perror("ftok");
return 0;
}
// 打开已存在的共享内存(不使用IPC_CREAT)
shmid = shmget(key, 512, 0666);
if(shmid < 0){
perror("shmget");
return 0;
}
buf = shmat(shmid, NULL, 0);
if(buf < 0){
perror("shmat");
return 0;
}
// 读取共享内存数据并打印
printf("share mem=%s\n", buf);
shmdt(buf); // 撤销映射
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
}
(五)命令行操作
- 查看共享内存:
ipcs -m - 删除共享内存:
ipcrm -m shmid
(六)共享内存特性
- 进程间最高效的通信方式,直接读写内存
- 在内核空间创建,可映射到用户空间
- 需要配合同步机制(如信号量)避免竞争
- 创建后持续存在,需显式删除
三、关键示意图说明
- 内存映射原理示意图:展示进程地址空间与磁盘文件的映射关系,包括文本段、数据段、堆、栈与映射区的位置分布
- 映射区访问错误示意图:说明超出文件大小或非法偏移量访问时引发的 SIGSEGV(段错误)和 SIGBUS(总线错误)
- 共享内存命令输出示例:展示
ipcs命令输出的共享内存列表,包括键、ID、拥有者、权限等信息
6628

被折叠的 条评论
为什么被折叠?



