Linux 进程间通信 共享内存

共享内存可以说是最有用的进程间通信方式,也是最快的 IPC 形式。两个不同进程 A、B 共享内存的意思是,同一块物理内存被映射到进程 A、B 各自的进程地址空间。进程 A 可以即时看到进程 B 对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。

实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。


mmap()系统调用

1:两个进程通过映射普通文件实现共享内存通信

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int age;
}people;

int main(int argc,char **argv){
    int fd,i;
    people *p_map;
    char temp;

    fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
    lseek(fd,sizeof(people)*5-1,SEEK_SET);
    write(fd,"",1);

    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    
    temp='a';
    for(i=0;i<10;i++){
        temp+=1;
        memcpy((*(p_map+i)).name,&temp,2);
        (*(p_map+i)).age=20+i;
    }

    printf("initialize over\n");
    sleep(10);
    munmap(p_map,sizeof(people)*10);
    printf("umap ok\n");
    return 0;
}

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct{
    char name[4];
    int age;
}people;

int main(int argc,char**argv){
    int fd,i;
    people *p_map;
    fd=open(argv[1],O_CREAT|O_RDWR,00777);
    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    for(i=0;i<10;i++){
        printf("%d,name:%s,age:%d\n",i+1,(*(p_map+i)).name,(*(p_map+i)).age);
    }

    munmap(p_map,sizeof(people)*10);
    return 0;
}

2:父子进程通过匿名映射实现共享内存

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct {
    char name[4];
    int age;
}people;

int main(){
    int i;
    people *p_map;
    char temp;
    pid_t pid;
    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);

    if((pid=fork())<0){
        printf("fork error\n");
    }else if(pid>0){
        temp='a';
        for(i=0;i<5;i++){
            temp+=1;
            memcpy((*(p_map+i)).name,&temp,2);
            (*(p_map+i)).age=20+i;
        }
        sleep(5);
        printf("P read:%d\n",(*p_map).age);
        printf("umap\n");
        munmap(p_map,sizeof(people)*10);
        printf("umap ok\n");
        exit(0);
    }else{
        sleep(2);
        for(i=0;i<5;i++){
            printf("c read %d,name:%s,age:%d\n",i+1,(*(p_map+i)).name,(*(p_map+i)).age);            
        }
        (*p_map).age=100;
        munmap(p_map,sizeof(people)*10);
        exit(0);
    }
    return 0;
}

System V 共享内存


system V 共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存
放共享数据的物理内存页面。
系统调用 mmap()通过映射一个普通文件实现共享内存。
system V 则是通过映射特殊文件系统 shm 中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm 中的一个文件。

进程间需要共享的数据被放在一个叫做 IPC 共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统 V 共享内存通过 shmget 获得或创建一个 IPC 共享内存区域,并返回相应的标识符。
内核在保证 shmget 获得或创建一个共享内存区,初始化该共享内存区相应的 shmid_kernel 结构注同时,还将在特殊文件系统 shm 中,创建并打开一个同名文件,并在内存中建立起该文件的相应 dentry 及 inode 结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget 完成的。

system_v_shm_w.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct {
    char name[4];
    int age;
}people;

int main(){
    int shm_id,i;
    key_t key;
    char temp;
    people* p_map;
    char* name="/home/archermind/Desktop/ritter";
    char name2[40];
    strcpy(name2,"/dev/shm/ritter");
    if((key=ftok("/dev/shm/myshm",1))==-1){
        printf("ftok error\n");
        exit(0);
    }

    if((shm_id=shmget(key,4096,IPC_CREAT))<0){
        printf("shmget error\n");
        exit(0);
    }
    
    p_map=(people*)shmat(shm_id,NULL,0);
    temp='a';
    for(i=0;i<5;i++){
        temp+=1;
        memcpy((*(p_map+i)).name,&temp,1);
        (*(p_map+i)).age=20+i;
    }

    if(shmdt(p_map)<0){
        printf("detach error\n");
        exit(0);
    }
    return 0;
}

system_v_shm_r.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct{
    char name[4];
    int age;
}people;

int main(){
    int shm_id,i;
    key_t key;
    people* p_map;
    char *name="/dev/shm/ritter";
    if((key=ftok(name,0))<0){
        printf("ftok error\n");
        exit(0);
    }

    if((shm_id=shmget(key,4096,IPC_CREAT))<0){
        printf("shmget error\n");
        exit(0);
    }

    p_map=(people*)shmat(shm_id,NULL,0);

    for(i=0;i<5;i++){
        printf("%d,name:%s,age:%d\n",i+1,(*(p_map+i)).name,(*(p_map+i)).age);
    }
    
    if(shmdt(p_map)<0){
        printf("detach error\n");
    }

    return 0;
}
参考郑彦兴《Linux进程通信》


消息队列和管道基本上都是4次拷贝,而共享内存(mmap, shmget)只有两次。

4次:1,由用户空间的buf中将数据拷贝到内核中。2,内核将数据拷贝到内存中。3,内存到内核。4,内核到用户空间的buf.

2次: 1,用户空间到内存。 2,内存到用户空间。

消息队列和管道都是内核对象,所执行的操作也都是系统调用,而这些数据最终是要存储在内存中执行的。因此不可避免的要经过4次数据的拷贝。但是共享内存不同,当执行mmap或者shmget时,会在内存中开辟空间,然后再将这块空间映射到用户进程的虚拟地址空间中,即返回值为一个指向一个内存地址的指针。当用户使用这个指针时,例如赋值操作,会引起一个从虚拟地址到物理地址的转化,会将数据直接写入对应的物理内存中,省去了拷贝到内核中的过程。当读取数据时,也是类似的过程,因此总共有两次数据拷贝。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值