Linux 进程通讯 - 共享内存机制

共享内存机制,就是在物理内存中划分一段内存,多个进程间可以共同访问这段内存,也可对这段内存做修改操作,从而达到进程通讯的效果!

共享内存机制是允许两个或多个进程(不相关或有亲缘关系)访问同一个逻辑内存的机制。它是共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。

两种常用共享内存方式

  • System V版本的共享内存 shm

        1. 多个进程直接共享内存

  • 文件映射 mmap        

        1. 文件进行频繁读写,将一个普通文件映射到内存中

        2. 将特殊文件进行匿名内存映射,为关联进程提供共享内存空间

        3. 为无关联的进程提供共享内存空间,将一个普通文件映射到内存中


目录

一、System V版本

1. ftok

2. shmid

3. shmat

4. shmdt

5. shmctl

6. 示例

1). 例一

2). 例二

二、mmap文件映射

1. mmap

2. munmap

3. msync

4. mremap

5. 示例

1). 例一

2). 例二

3). 例三,实际意义的项目用法

三、总结


一、System V版本

原理:利用共享内存完成进程通讯,两个进程都通过虚拟地址空间到用户页表,然后通过用户级页表映射到物理内存的相同一块内存区域。

1. ftok

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

key_t ftok(const char *pathname, int proj_id);

描述:生成key标识符返回;

参数:

        pathname

                文件路径名;

        proj_id

                id是子序号;虽然是int类型,但是只使用8bits(1-255);

返回值:

        成功:返回key标识符;

        失败:返回-1,并设置错误标志errno;

例:

key_t key = ftok("a.txt", 1);
if ((key_t)-1 == key) {
    fprintf(stderr, "ftok failed. reason: %s\n", strerror(errno));
    exit(1);
}

2. shmid

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

int shmget(key_t key, size_t size, int shmflg);

描述:创建一个共享内存块,返回这个共享内存块的标识符shmid;

参数:

        key

                共享内存的key,可随机取;但互相通信的进程需一致;

        size

                申请的共享内存的大小,为4k(4096)的整数倍;

        shmflg

                IPC_CREAT,创建新的共享内存,存在则返回共享内存标识符;

                IPC_EXCL,不存在就创建,存在则报错;

返回值:

        成功:返回标识符;

        失败:返回-1,并设置错误标志errno;

例:

int shmid = shmget((key_t)1234, 4096, 0666|IPC_CREAT);
if (-1 == shmid) {
    fprintf(stderr, "shmget failed!\n");
    exit(1);
}

3. shmat

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

void *shmat(int shmid, const void *shmaddr, int shmflg);

描述:挂接共享内存(将进程地址空间挂接到物理空间,可以有多个挂接);

参数:

        shmid

                挂接的共享内存id;

        shmatddr

                一般为0,表示连接到由内核选择的第一个可用地址上;

        shmflg

                一般为0;

返回值:

        成功:返回共享内存段的地址;

        失败:返回(void *)-1,并设置错误标志errno;

例:

void *shm = shmat(shmid, (void*)0, 0);
if ((void*)-1 == shm) {
    fprintf(stderr, "shmat failed!\n");
    exit(1);
}

4. shmdt

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

int shmdt(const void *shmaddr);

描述:取消共享内存映射

参数:

        shmaddr

                共享内存段的地址;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

例:

int ret = shmdt(shm);
if (-1 == ret) {
    fprintf(stderr, "shmdt failed!\n");
    exit(1);
}

5. shmctl

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

描述:控制共享内存,可用于删除内存共享等操作;

参数:

        shmid

                由shmget返回的共享内存标识码;

        cmd

                将要采取的动作(可取值:IPC_STAT、IPC_SET、IPC_RMID);

        buf

                指向一个保存着共享内存的模式状态和访问权限的数据结构;   

返回值:

        成功:IPC_INFO、SHM_INFO、SHM_STAT 返回其他一些东东;其他操作返回0;

        失败:返回-1,并设置错误标志errno;

例:

int ret = shmctl(shmid, IPC_RMID, 0);
if (-1 == ret) {
    fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
    exit(1);
}

6. 示例

1). 例一

shmwrite.cpp

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>


typedef struct MSG {
    char name[32];
    int age;
    char sex[32];
}Msg;

int main(int argc, char **argv) {
    
    void *shm = NULL;    
    int shmid = 0;                   // 共享内存标识符
    const key_t key = (key_t)2023;   // 共享内存的key
    
    Msg msg;
    sprintf(msg.name, "%s", "老王");
    msg.age = 25;
    sprintf(msg.sex, "%s", "男");
    
    // 创建共享内存
    shmid = shmget(key, sizeof(Msg), 0666|IPC_CREAT);
    if (-1 == shmid) {
        fprintf(stderr, "shmget failed!\n");
        exit(1);
    }
    
    // 将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, (void*)0, 0);
    if ((void*)-1 == shm) {
        fprintf(stderr, "shmat failed!\n");
        exit(2);
    }
    
    printf("Memory attached at %p \n", shm);
    
    // 设置共享内存
    Msg *p = (Msg*)shm;
    memcpy(p, &msg, sizeof(Msg));
    
    
    sleep(3);
    
    printf("name = %s\n", p->name);
    printf("age = %d\n", p->age);
    printf("sex = %s\n", p->sex);
    
    
    // 把共享内存从当前进程中分离
    /*int ret = shmdt(shm);
    if (-1 == ret) {
        fprintf(stderr, "shmdt failed!\n");
        exit(3);
    }*/
    
    
    return 0;
}

shmread.cpp

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <errno.h>


typedef struct MSG {
    char name[32];
    int age;
    char sex[32];
}Msg;

int main(int argc, char **argv) {
    
    void *shm = NULL;    
    int shmid = 0;                   // 共享内存标识符
    const key_t key = (key_t)2023;   // 共享内存的key
    
    Msg msg;    // 指向shm
    
    
    // 创建共享内存
    shmid = shmget(key, sizeof(Msg), 0666|IPC_CREAT);
    if (-1 == shmid) {
        fprintf(stderr, "shmget failed!\n");
        exit(1);
    }
    
    // 将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, (void*)0, 0);
    if ((void*)-1 == shm) {
        fprintf(stderr, "shmat failed!\n");
        exit(2);
    }
    
    printf("Memory attached at %p \n", shm);
    
    // 设置共享内存
    memcpy(&msg, shm, sizeof(Msg));
    
    printf("name = %s\n", msg.name);
    printf("age = %d\n", msg.age);
    printf("sex = %s\n", msg.sex);
    

    sprintf(msg.name, "%s", "小红");
    msg.age = 24;
    sprintf(msg.sex, "%s", "女");
    
    memcpy(shm, &msg, sizeof(Msg));
    

    
    // 把共享内存从当前进程中分离
    int ret = shmdt(shm);
    if (-1 == ret) {
        fprintf(stderr, "shmdt failed!\n");
        exit(3);
    }
    
    
    // 删除共享内存
    ret = shmctl(shmid, IPC_RMID, 0);
    if (-1 == ret) {
        fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
        exit(4);
    }
    
    
    return 0;
}

2). 例二

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>


int main(int argc, char **argv) {
    
    void *shm = NULL;    
    int shmid = 0;                   // 共享内存标识符
    const key_t key = (key_t)2023;   // 共享内存的key
    pid_t fpid;     // 进程唯一标识符
    
    char msg[1024] = { '\0' };
    
    // 创建共享内存
    shmid = shmget(key, sizeof(msg), 0666|IPC_CREAT|IPC_EXCL);
    if (-1 == shmid) {
        fprintf(stderr, "shmget failed!\n");
        exit(1);
    }
    
    // 将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, (void*)0, 0);
    if ((void*)-1 == shm) {
        fprintf(stderr, "shmat failed!\n");
        exit(2);
    }
    
    printf("Memory attached at %p \n", shm);
    
    
    // 设置共享内存
    char *p = (char*)shm;
    memcpy(p, &msg, sizeof(shm));
    
    
    // 创建进程
    fpid = fork();
    if (fpid < 0) {
        printf("error in fork!\n");
        exit(3);
    
    } else if (0 == fpid) { // 子进程的操作
        printf("Child:p = %s\n", p);
        
        sprintf(p, "%s", "Hello World!");

        sleep(1);
    
    } else {    // 父进程的操作
        sprintf(p, "%s", "this is message!");   // 修改内存中的值
        
        sleep (1);
        
        printf("Parent:p = %s\n", p);
        
        wait(NULL);
    
        // 把共享内存从当前进程中分离
        int ret = shmdt(shm);
        if (-1 == ret) {
            fprintf(stderr, "shmdt failed!\n");
            exit(3);
        }
        
        
        // 删除共享内存
        ret = shmctl(shmid, IPC_RMID, 0);
        if (-1 == ret) {
            fprintf(stderr, "shmctl(IPC_RMID) failed, reason:%s\n", strerror(errno));
            exit(4);
        }
    }
      
    
    return 0;
}


二、mmap文件映射

原理:将一个文件或者其他对象映射进内存;

        1. 使用普通文件提供的内存映射;

        2. 使用特殊文件提供匿名内存映射。

1. mmap

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

描述:在调用的虚拟地址空间中创建一个新的映射;

参数:

        addr

                指向欲映射的内存起始地址,通常设为NULL,让系统自动选定地址,映射成功后返回该地址;

        length

                代表将文件中多大的部分映射到内存;(4k的整数倍)

        prot

                映射区域的保护方式,可以是以下几种反射光hi的组合:

                        PROT_EXEC        执行

                        PROT_READ        读取

                        PROT_WRITE        写入

                        PROT_NONE        不能存取

        flags

                影响映射区域的各种特性;必须要指定MAP_SHARED 或 MAP_PRIVATE;

                        MAP_SHARED        映射区域数据与文件对应,允许其他进程共享;

                        MAP_PRIVATE        映射区域生成文件的copy,修改不同步文件;

                        MAP_ANONYMOUS        建立匿名映射;此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享;

                        MAP_DENYWRITE        允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝;

                        MAP_LOCKED            将映射区域锁定住,这表示该区域不会被设置swap;

        fd

                要映射到内存中的文件描述符;如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设置为-1;有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero 文件,然后对该文件进行映射,可以同样达到匿名映射的效果;

        offset

                文件映射的偏移量,通常设置为0,代表从文件最前方开始映射,offset必须是分页大小的整数倍,即4k的整数倍;

返回值:

        成功:返回一个指向映射区域的指针;

        失败:返回MAP_FAILED(即(void *) -1),并设置错误标志errno;

例:

void *mem = mmap(NULL, FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 映射失败
if (MAP_FAILED == mem) {
    fprintf(stderr, "map file failed: %s", strerror(errno));      
    exit(1);
}

2. munmap

#include <sys/mman.h>

int munmap(void *addr, size_t length);

描述:解除映射;

参数:

        addr

                文件映射到进程空间的地址;

        length

                映射空间的大小;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

例:

int ret = munmap(p, sizeof(p));
if(ret < 0) {
    perror("munmap error. reason:");
    exit(1);
}

3. msync

#include <sys/mman.h>

int msync(void *addr, size_t length, int flags);

描述:实现磁盘文件内容与共享内存区中的内容一致,即同步操作;

参数:

        addr

                文件映射到进程空间的地址;

        length

                映射空间的大小;

        flags

                MS_ASYNC        异步,调用会立即返回,不等更新完成;

                MS_SYNC           同步,调用后会等到更新完成之后才返回;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

例:

int ret = msync(p, FILE_SIZE, MS_ASYNC);
if (MAP_FAILED == p) {
    fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
    exit(1);
}

4. mremap

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <sys/mman.h>

void *mremap(void *old_address, size_t old_size, size_t new_size, int flags);

描述:扩大(或缩小)现有的内存映射;

参数:

        old_address

                旧的文件映射到进程空间的地址;

        old_size

                旧的映射空间的大小;

        new_size

                重新映射指定的新空间大小;

        flags

                取值0或者MREMAP_MAYMOVE,0代表不允许内核移动映射区域,MREMAP_MAYMOVE则表示内核可以根据实际情况移动映射区域以找到一个符合new_size大小要求的内存区域;

返回值:

        成功:返回0;

        失败:返回MAP_FAILED即(void*)-1,并设置错误标志errno;

例:

p = (char*)mremap(p, FILE_SIZE, 2*FILE_SIZE, MREMAP_MAYMOVE);
if (MAP_FAILED == p) {
    fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
    exit(1);
}

5. 示例

1). 例一

mmap_write.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>

typedef struct MSG {
    char name[32];
    int age;
    char sex[32];
}Msg;


int main(int argc, char **argv) {
    if (2 > argc) {
        printf("usage: %s file.\n", argv[0]);
        exit(1);
    }
    
    Msg msg;
    sprintf(msg.name, "%s", "小明");
    msg.age = 25;
    sprintf(msg.sex, "%s", "男");
    
    // 打开一个文件
    int fd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0664);
    if (fd < 0) {
        perror("open error. reason:");
        exit(2);
    }
    
    // 修改文件的大小
    ftruncate(fd, sizeof(Msg));
    
    // 将文件映射到内存
    void *mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // 映射失败
    if (MAP_FAILED == mem) {
        fprintf(stderr, "map file failed: %s", strerror(errno));      
        exit(3);
    }
    close(fd); //关闭不用的文件描述符
    Msg *p = (Msg*)mem;
    memcpy(p, &msg, sizeof(Msg));   // 将内容拷贝映射的内存
    
    
    sleep(3);
    
    printf("name = %s\n", p->name);
    printf("age = %d\n", p->age);
    printf("sex = %s\n", p->sex);
    
    
    // 结束映射
    int ret = munmap(p, sizeof(Msg));
	if(ret < 0)
	{
		perror("munmap error. reason:");
		exit(4);
	}

	return 0;

}

mmap_read.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>

typedef struct MSG {
    char name[32];
    int age;
    char sex[32];
}Msg;


int main(int argc, char **argv) {
    if (2 > argc) {
        printf("usage: %s file.\n", argv[0]);
        exit(1);
    }
    
    Msg msg;

    
    int fd = open(argv[1], O_RDWR, 0664);
    if (fd < 0) {
        perror("open error. reason:");
        exit(2);
    }
    
    
    // 将文件映射到内存
    void *mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // 映射失败
    if (MAP_FAILED == mem) {
        fprintf(stderr, "map file failed: %s", strerror(errno));      
        exit(3);
    }
    close(fd); //关闭不用的文件描述符
    Msg *p = (Msg*)mem;
    memcpy(&msg, p, sizeof(Msg));   // 将内容拷贝映射的内存
    
    
    printf("name = %s\n", msg.name);
    printf("age = %d\n", msg.age);
    printf("sex = %s\n", msg.sex);
    
       
    sprintf(p->name, "%s", "小红");
    p->age = 24;
    sprintf(p->sex, "%s", "女");
    
    
    
    // 结束映射
    int ret = munmap(mem, sizeof(Msg));
	if(ret < 0)
	{
		perror("munmap error. reason:");
		exit(4);
	}

	return 0;

}

2). 例二

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>


#define FILE_SIZE   32

int main(int argc, char **argv) {
    
    void *mem = NULL;    
    pid_t fpid;     // 进程唯一标识符
    int ret = -1;
    
    if (2 > argc) {
        printf("usage: %s file.\n", argv[0]);
        exit(1);
    }
    
    // open file
    int fd = open(argv[1], O_RDWR|O_CREAT, 0664);
    if (fd < 0) {
        perror("open error. reason:");
        exit(2);
    }
    
    // 修改文件的大小
    ftruncate(fd, FILE_SIZE);
    
    // 将文件映射到内存
    mem = mmap(NULL, FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // 映射失败
    if (MAP_FAILED == mem) {
        fprintf(stderr, "map file failed: %s", strerror(errno));      
        exit(3);
    }

    close(fd); //关闭不用的文件描述符
    char *p = (char*)mem;
    
    
    // 创建进程
    fpid = fork();
    if (fpid < 0) {
        printf("error in fork!\n");
        exit(3);
    
    } else if (0 == fpid) { // 子进程的操作
        printf("Child:p = %s, strlen(p) = %ld\n", p, strlen(p));
        
        // 扩大现有的内存映射
        p = (char*)mremap(p, strlen(p), 2*FILE_SIZE, MREMAP_MAYMOVE);
        if (MAP_FAILED == p) {
            fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
            exit(4);
        }
        ftruncate(fd, 2*FILE_SIZE);
        
        
        sprintf(p, "%s", "Hello World!");
    
    } else {    // 父进程的操作
        printf("Parent:p = %s\n", p);
        
        sprintf(p, "%s", "this is message!this is message!");   // 修改内存中的值
        
        sleep (1);
        
        printf("Parent:p = %s\n", p);
        
        wait(NULL);
        
        // 同步内存中的数据到文件
        ret = msync(p, 2*FILE_SIZE, MS_SYNC);
        if (MAP_FAILED == p) {
            fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
            exit(4);
        }
        
        // 结束映射
        int ret = munmap(p, sizeof(p));
        if(ret < 0) {
            perror("munmap error. reason:");
            exit(5);
        }
    }
      
    
    return 0;
}

3). 例三,实际意义的项目用法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>


typedef struct MSG {
    char name[32];
    int age;
    char sex[32];
}Msg;

int main(int argc, char **argv) {
    
    void *mem = NULL;    
    pid_t fpid;     // 进程唯一标识符
    int ret = -1;
    int dataCount = 0;  // 记录结构体数据在文件中的个数
    
    
    if (2 > argc) {
        printf("usage: %s file.\n", argv[0]);
        exit(1);
    }
    
    // open file
    int fd = open(argv[1], O_RDWR|O_CREAT, 0664);
    if (fd < 0) {
        perror("open error. reason:");
        exit(2);
    }
    
    
    // 将文件映射到内存
    mem = mmap(NULL, sizeof(Msg), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // 映射失败
    if (MAP_FAILED == mem) {
        fprintf(stderr, "map file failed: %s", strerror(errno));      
        exit(3);
    }

    // 修改文件的大小
    ftruncate(fd, sizeof(Msg));
    //close(fd); //关闭不用的文件描述符

  
    
    // 创建进程
    fpid = fork();
    if (fpid < 0) {
        printf("error in fork!\n");
        exit(3);
    
    } else if (0 == fpid) { // 子进程的操作
        
        printf("子进程读取共享内存中的数据打印:\n");
        
        // 循环读取内存中的所有数据打印出来
        int i = 0;
        for (; i <= 10; i++) {  // 共享内存中一个共有是一个结构体数据,依次读取出来
            Msg m;
            
            // 读取
            memcpy(&m, (Msg*)((char*)mem + (sizeof(Msg) * i)), sizeof(Msg));
            printf("name = %s\n", m.name);
            printf("age = %d\n", m.age);
            printf("sex = %s\n", m.sex);
        }  
        
    
    } else {    // 父进程的操作
            
        int i = 0;
        for (; i <= 10; i++) {
            Msg msg;
            sprintf(msg.name, "%s%d", "张某", i);
            msg.age = 20 + i;
            sprintf(msg.sex, "%s%d", "男", i);
            
            dataCount++;    // 统计个数
            
            
            // 扩大现有的内存映射
            mem = mremap(mem, sizeof(Msg) * i, sizeof(Msg) * dataCount, MREMAP_MAYMOVE);
            if (MAP_FAILED == mem) {
                fprintf(stderr, "mremap failed. reason:%s\n", strerror(errno));
                exit(4);
            }
            
            // 将数据依次拷贝到共享内存中
            memcpy((Msg*)((char*)mem + (sizeof(Msg) * i)), &msg, sizeof(Msg));
        }
        
        wait(NULL);
        printf("end---\n");
        printf("sizeof(Msg) = %ld\n", sizeof(Msg));
        
        // 修改文件的大小
        ftruncate(fd, sizeof(Msg) * dataCount);
        
        // 同步内存中的数据到文件
        ret = msync(mem, sizeof(Msg) * dataCount, MS_SYNC);
        if (MAP_FAILED == mem) {
            fprintf(stderr, "msync failed. reason:%s\n", strerror(errno));
            exit(5);
        }
        
        // 结束映射
        int ret = munmap(mem, sizeof(Msg) * dataCount);
        if(ret < 0) {
            perror("munmap error. reason:");
            exit(6);
        }
    }
      
    
    return 0;
}


三、总结

共享内存机制在项目中是常用的,且读取数据也是非常高效的,学好它也许日后对做项目会有实在用处!

淘宝分布式文件系统,用的就是mmap!

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/129012826