进程间通信 (IPC) 方法总结(二)

进程间通信 (IPC) 方法总结(二)
强烈推介IDEA2021.1.3破解激活,IntelliJ IDEA 注册码,2021.1.3IDEA 激活码  

大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说进程间通信 (IPC) 方法总结(二),希望能够帮助大家进步!!!

进程间通信 (IPC) 方法总结(二)

共享内存(SHARE MEMORY)

  • 使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
  • 由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥。

共享内存

Linux的内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及System V共享内存。

mmap

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或System V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

mmap示意图

mmap的使用
#include <sys/mman.h>
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 
  • 返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
  • 参数:
    • addr:建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
    • length:欲创建映射区的大小
    • prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
    • flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
      • MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
      • MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
    • fd:用来建立映射区的文件描述符
    • offset:映射文件的偏移(4k的整数倍)

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。

int munmap(void *addr, size_t length);  //成功:0; 失败:-1

eg.

// 写共享内存
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

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

int main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    char temp = 'a';
    
    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    ftruncate(fd, sizeof(people)*10);
    p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( (*(p_map+i)).name, &temp, 1);
        (*(p_map+i)).name[1] = 0;
        (*(p_map+i)).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
        
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

// 读共享内存
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

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

int main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    struct stat filestat;
    
    int fd = open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    fstat(fd, &filestat);
    p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
    }
    
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

运行结果:

执行./mmap_w people.txt

mmap_w

执行./mmap_r people.txt

mmap_w

POSIX共享内存

POSIX共享内存使用方法有以下两个步骤:

  1. 通过shm_open创建或打开一个POSIX共享内存对象
  2. 调用mmap将它映射到当前进程的地址空间

和通过内存映射文件进行通信的使用上差别在于mmap描述符参数获取方式不一样:通过open或shm_open。POSIX共享内存和POSIX消息队列,有名信号量一样都是具有随内核持续性的特点。

POSIX共享内存的使用
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

// 打开一个共享内存的文件句柄
int shm_open(const char *name, int oflag, mode_t mode);
//注意 这里的名字具有形式 /somename,即必须以 / 为开头,因为POSIX共享内存对应的文件是位于/dev/shm这个特殊的文件系统内。

// 删除一个共享内存的名字,但只有所有程序都关闭,才会真的删除
int shm_unlink(const char *name);

eg.

写共享内存

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

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    char temp = 'a';
    
    int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    ftruncate(fd, sizeof(people)*10);
    p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
        
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

读共享内存

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

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    struct stat filestat;
    
    int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    fstat(fd, &filestat);
    p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
    }
    
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

删除共享内存

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    int ret = shm_unlink(argv[1]);
    if (-1 == ret)
    {
        printf("unlink shm error = %s\n", strerror(errno));
        return -1;
    }
    
    printf("unlink ok \n");
    return 0;
}

运行结果:

[root@rocket ipc]# g++ -g -o ipc_posix_mmap_writer ipc_posix_mmap_writer.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_writer /shm_from_mem.txt
initialize over
umap ok
[root@rocket ipc]# g++ -g -o ipc_posix_mmap_reader ipc_posix_mmap_reader.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_reader /shm_from_mem.txt
name = a, age = 20
name = b, age = 21
name = c, age = 22
name = d, age = 23
name = e, age = 24
name = f, age = 25
name = g, age = 26
name = h, age = 27
name = i, age = 28
name = j, age = 29
umap ok
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink ok
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink shm error = No such file or directory
[root@rocket ipc]# ll /dev/shm/|grep mem
[root@rocket ipc]#

System V共享内存

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

每一个共享内存区都有一个控制结构struct shmid_kernel,shmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁,定义如下:

struct shmid_kernel /* private to the kernel */
{       
    struct kern_ipc_perm shm_perm;
    struct file* shm_file;
    int id;
    unsigned long shm_nattch;
    unsigned long shm_segsz;
    time_t shm_atim;
    time_t shm_dtim;
    time_t shm_ctim;
    pid_t shm_cprid;
    pid_t shm_lprid;
};

该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。
System V共享内存内核结构
内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了,对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构,shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时,在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

在创建了一个共享内存区域后,还要将它映射到进程地址空间,系统调用shmat()完成此项功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程,原理与mmap()大同小异。

System V共享内存的使用
#include <sys/ipc.h>
#include <sys/shm.h>

// 获取共享内存区域
int shmget(key_t key, size_t size, int shmflg);

// 连接共享内存区域
void *shmat(int shmid, const void *shmaddr, int shmflg);

// 断开共享内存区域
int shmdt(const void *shmaddr);

// 对共享内存区域进行控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// 将path和proj_id转换成System V IPC key
key_t ftok(const char *pathname, int proj_id);

eg.

写共享内存

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

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

int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people* p_map;
    char temp = 'a';
    
    const char* name = "/dev/shm/my_systemv_shm1";
    key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    shm_id=shmget(key, 4096, IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
    
    if(shmdt(p_map) == -1)
    {
        perror(" detach error ");
        return -1;
    }
    
    return 0;
}

读共享内存

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

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

int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people* p_map;
    
    const char* name = "/dev/shm/my_systemv_shm1";
    key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    shm_id=shmget(key, 4096, IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    
    for(int i = 0; i < 10; i++)
    {
        printf( "name:%s, ",(*(p_map+i)).name );
        printf( "age %d\n",(*(p_map+i)).age );
    }
    
    if(shmdt(p_map) == -1)
    {
        perror(" detach error ");
        return -1;
    }
    
    return 0;
}

运行结果:

[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_writer ipc_systemv_mmap_writer.cpp
[root@rocket ipc]# touch /dev/shm/my_systemv_shm1
[root@rocket ipc]# ./ipc_systemv_mmap_writer
initialize over
[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_reader ipc_systemv_mmap_reader.cpp
[root@rocket ipc]# ./ipc_systemv_mmap_reader
name:a, age 20
name:b, age 21
name:c, age 22
name:d, age 23
name:e, age 24
name:f, age 25
name:g, age 26
name:h, age 27
name:i, age 28
name:j, age 29

观察一下共享内存:
[root@rocket ipc]# ./get_ipc_key /dev/shm/my_systemv_shm1
key = 1084739
[root@rocket ipc]# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     
0x00000000 0          gdm        600        393216     2          dest        
0x00000000 32769      gdm        600        393216     2          dest        
0x00000000 65538      gdm        600        393216     2          dest        
0x00000000 98307      gdm        600        393216     2          dest        
0x00108d43 131076     root       0          4096       0   
 
看到我们新建的共享内存了吧?删除也很简单:
[root@rocket ipc]# ipcrm -m 131076
[root@rocket ipc]# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     
0x00000000 0          gdm        600        393216     2          dest        
0x00000000 32769      gdm        600        393216     2          dest        
0x00000000 65538      gdm        600        393216     2          dest        
0x00000000 98307      gdm        600        393216     2          dest

转载于:https://www.cnblogs.com/joker-wz/p/11006414.html

本文来源weixin_30595035,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/29758

发表评论