进程、线程和进程间通信课程 Day7学习要点总结

一、内存映射(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 整数倍)

(四)使用注意事项

  1. 映射创建时隐含一次文件读操作,将数据载入内存
  2. MAP_SHARED 模式下,映射权限需≤文件打开权限
  3. 映射区释放与文件关闭无关,文件可提前关闭
  4. 映射文件大小必须 > 0,否则访问会报错
  5. 偏移量需为 0 或 4K 倍数,否则报非法参数错误
  6. 映射大小可大于文件,但仅能访问文件对应内存区域
  7. 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:创建后生成的唯一标识符
  • 特性:创建后持续存在,需显式删除

(二)共享内存使用步骤

  1. 生成 key:通过ftok函数基于文件和项目 ID 生成唯一键
  2. 创建共享内存shmget函数创建指定大小的共享内存
  3. 映射到进程shmat函数将共享内存映射到进程地址空间
  4. 数据操作:直接通过指针读写共享内存
  5. 撤销映射shmdt函数取消进程对共享内存的映射
  6. 删除共享内存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

(六)共享内存特性

  • 进程间最高效的通信方式,直接读写内存
  • 在内核空间创建,可映射到用户空间
  • 需要配合同步机制(如信号量)避免竞争
  • 创建后持续存在,需显式删除

三、关键示意图说明

  1. 内存映射原理示意图:展示进程地址空间与磁盘文件的映射关系,包括文本段、数据段、堆、栈与映射区的位置分布
  2. 映射区访问错误示意图:说明超出文件大小或非法偏移量访问时引发的 SIGSEGV(段错误)和 SIGBUS(总线错误)
  3. 共享内存命令输出示例:展示ipcs命令输出的共享内存列表,包括键、ID、拥有者、权限等信息
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值