利用 mmap 实现进程之间的通信

1.mmap 使用的注意事项

(1)当 open 一个文件时,如果指定了 O_CREAT 标志并且文件不存在,就会新创建一个文件作为映射文件,此时必须调用 ftruncate 或者 lseek+write 设置文件长度,否则任然可以调用 mmap,但是对存储映射区的引用会产生 SIGBUS。另外,如果映射的长度超过了文件长度,访问超过文件长度的映射区也会出错。

(2)munmap 释放映射区时传入的指针必须指向最初分配的位置,否则将会出错(中途可移动映射区指针,但是必须从最开始分配的位置开始释放)。

(3)mmap 指定对映射区的访问权限时不能超过 open 对文件指定的访问权限。

(4)mmap 建立映射区时隐含一次对文件的读,因此打开文件时读权限是必须有的,即使你只进行写操作。

2.使用存储映射区实现父子进程间通信

注意:父子进程的内存空间遵循 读时共享、写时复制,但打开的文件和 mmap 建立的存储映射区在父子进程之间是一直共享的,因此可通过 mmap 建立存储映射区实现父子进程之间的通信。通过以下代码可说明:

#include <stdio.h>                                                                      
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <sys/mman.h>                                                                   
                                                                                        
int var = 10;                                                                           
                                                                                                                                                                                            
int main() {                                                                            
    int* p;                                                                             
    int fd;                                                                             
    pid_t pid;                                                                          
                                                                                        
    if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {                 
        perror("open tmpfile");                                                         
        exit(1);                                                                        
    }                                                                                           
    unlink("tmpfile");      //解除硬链接,即删除了临时文件唯一的目录项,使之具备被删除的条件,但此时并未删除
    ftruncate(fd, 4);       //创建文件大小                                              
                                                                                        
    p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);                 
               
    if (p == MAP_FAILED) {                                                              
        perror("mmap failed");                                                          
        exit(1);                                                                        
    }                                                                                   
    close(fd);                                                                          
                                                                                        
    if ((pid = fork()) == 0) {      //child                                             
        sleep(1);                                                                       
        printf("In child: *p = %d, var = %d\n", *p, var);                               
    }                                                                                   
    else if (pid > 0) {     //parent                                                    
        *p = 2000;          //修改映射区                                                
        var = 1000;         //修改全局变量                                              
                                                                                        
        printf("In parent: *p = %d, var = %d\n", *p, var);                              
        wait(NULL);         //等待子进程执行完成                                        
                                                                                        
        if (munmap(p, 4) < 0) {     //释放映射区                                        
            perror("munmap failed");                                                    
            exit (1);                                                                   
        }                                                                               
    }                                                                                   
                                                                                        
    return 0;                                                                           
}                  

执行结果:

In parent: *p = 2000, var = 1000
In child: *p = 2000, var = 10

3.创建匿名存储映射区

以上方法虽然实现了父子进程之间的通信,但是每次都要依赖一个文件,如果是一个临时文件,打开后马上进行了 unlink 使文件具备了被释放的条件,在进程结束后文件就被释放,因此这个文件根本就没有存在的必要,可通过匿名映射区避免这种情况。但是匿名映射区只能实现有血缘关系的进程间的通信。

注意:宏 MAP_ANON 是 linux 所独有的,所有类 Unix 系统可以借助文件 /dev/zero 实现匿名映射区

#include <stdio.h>                                                                                                                                                                                                                                   
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <sys/mman.h>                                                                   
                                                                                        
int var = 10;                                                                           
                                                                                        
                                                                                        
int main() {                                                                            
    int* p;                                                                             
    pid_t pid;                                                                          
                                                                                        
                                                                                        
    //注意这里加上 MAP_ANON 参数并将文件描述符指定为 -1                                 
    p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);   
    if (p == MAP_FAILED) {                                                              
        perror("mmap failed");                                                          
        exit(1);                                                                        
    }                                                                                   
                                                                                        
    if ((pid = fork()) == 0) {      //child                                             
        sleep(1);                                                                       
        printf("In child: *p = %d, var = %d\n", *p, var);                               
    }                                                                                   
    else if (pid > 0) {     //parent                                                    
        *p = 2000;          //修改映射区                                                
        var = 1000;         //修改全局变量                                              
                                                                                        
        printf("In parent: *p = %d, var = %d\n", *p, var);                              
        wait(NULL);         //等待子进程执行完成                                        
                                                                                        
        if (munmap(p, 4) < 0) {     //释放映射区                                        
            perror("munmap failed");                                                    
            exit (1);                                                                   
        }                                                                               
    }                                                                                   
                                                                                        
    return 0;                                                                           
}        

4.通过存储映射区实现非血缘关系进程间的通信

注意非血缘关系的进程间不能通过匿名映射区实现

写进程:

#include <stdio.h>                                                                      
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <string.h>                                                                     
#include <sys/mman.h>                                                                   
                                                                                        
//这里换一种数据结构进行通信                                                            
struct STU {                                                                            
    int id;                                                                             
    char name[20];                                                                      
    char sex;                                                                           
};                                                                                      
                                                                                        
void sys_err(char* s) {                                                                 
    perror(s);                                                                          
    exit(1);                                                                            
}                                                                                       
                                                                                        
int main() {                                                                            
    int fd;                                                                             
    struct STU stu = {10, "xiaoming", 'm'};                                             
    struct STU* mm;                                                                     
                                                                                        
    if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)                   
        sys_err("open tmpfile");                                                        
    //unlink("tmpfile");            //写进程不能立即进行 unlink,因为他要保证写进程能找到这个目录项                       
    ftruncate(fd, sizeof(stu));     //创建文件大小                                                                        
                                                                                     
    mm = mmap(NULL, sizeof(stu), PROT_WRITE, MAP_SHARED, fd, 0);                        
    if (mm == MAP_FAILED)                                                               
        sys_err("mmap failed");                                                         
    close(fd);                                                                          
                                                                                        
    while (stu.id++ < 100) {                                                            
        memcpy(mm, &stu, sizeof(stu));                                                  
        sleep(1);                                                                       
    }                                                                                   
                                                                                        
    unlink("tmpfile");                                                                                                                                                                                                                               
    munmap(mm, sizeof(stu));                                                            
                                                                                        
    return 0;                                                                        
}           

读进程:

#include <stdio.h>                                                                      
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <sys/mman.h>                                                                   
                                                                                        
//这里换一种数据结构进行通信                                                            
struct STU {                                                                            
    int id;                                                                             
    char name[20];                                                                      
    char sex;                                                                           
};                                                                                      
                                                                                        
void sys_err(char* s) {                                                                 
    perror(s);                                                                          
    exit(1);                                                                            
}                                                                                       
                                                                                        
int main() {                                                                            
    int fd;                                                                             
    struct STU stu;                                                                     
    struct STU* mm;                                                                     
                                                                                        
    if ((fd = open("tmpfile", O_RDONLY)) < 0)                                           
        sys_err("open tmpfile");                                                        
    unlink("tmpfile");          //读进程可以立即进行 unlink                             
    ftruncate(fd, sizeof(stu));                                                         
                                                                                        
    mm = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);                                                                                                                                                                                      
    if (mm == MAP_FAILED)                                                               
        sys_err("mmap failed");                                                         
    close(fd);                                                                          
                                                                                        
    while (mm->id < 100) {                                                              
        printf("id = %d, name = %s, sex = %c \n", mm->id, mm->name, mm->sex);           
        sleep(1);                                                                       
    }                                                                                   
                                                                                        
    munmap(mm, sizeof(stu));                                                            
                                                                                        
    return 0;                                                                           
}           

运行结果:

存储映射通信实质:

程序将文件映射到一块内存,这块内存类似于管道连接了a、b两个进程,a b进程都可以对文件进行读写操作。

5.解析打开文件的实质

对于以下代码:

#include <stdio.h>                                                                                                                                                                                                                                   
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <string.h>                                                                     
#include <fcntl.h>                                                                      
                                                                                        
                                                                                        
int main() {                                                                            
    int fd;                                                                             
    char* s = "hello world\n";                                                          
                                                                                        
    if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {                 
        perror("open tmpfile");                                                         
        exit(1);                                                                        
    }                                                                                   
                                                                                        
    write(fd, s, strlen(s));                                                            
    close(fd);                                                                          
                                                                                        
    return 0;                                                                           
}         

通过命令 strace ./a.out 查看进程执行过程中系统调用过程如下:

从中可以看出,系统在文件描述符3上打开了文件,并且调用 mmap 为描述符3创建了存储映射区,之后也多次调用 mmap 创建了存储映射区。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
***存储映射区来实现进程间的通信。存储映射区是指将一个文件或者匿名映射关联到进程的地址空间中,使得进程可以通过访问内存的方式来读写文件或者进行进程间的通信。 在父子进程之间,可以通过打开的文件和使用mmap建立的存储映射区来实现通信。父子进程的内存空间遵循读时共享、写时复制的原则,所以打开的文件和mmap建立的存储映射区在父子进程之间是一直共享的。可以通过修改存储映射区中的数据来进行进程间的通信。 另外,在非血缘关系的进程间,也可以通过mmap实现通信。使用宏MAP_ANON(仅linux独有)或者指定文件描述符为-1,可以创建匿名映射区,无需依赖文件。这种方式可以实现非血缘关系进程间的通信,但仅限于同一台机器上的进程。 综上所述,mmap可以通过建立存储映射区来实现进程间的通信,无论是父子进程还是非血缘关系的进程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [利用 mmap 实现进程之间通信](https://blog.csdn.net/qq_40843865/article/details/89442987)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值