进程间通信共享内存使用总结

什么是共享内存?

共享内存就是允许两个或多个进程共享一定的存储区。就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。

注:共享内存没有任何的同步与互斥机制,所以要使用信号量来实现对共享内存的存取的同步。

 

共享内存特点和优势

当中共享内存的大致原理相信我们可以看明白了,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信,而且共享内存是IPC通信当中传输速度最快的通信方式没有之,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,数据不需要在两进程间复制,没有什么操作比这简单了。再者用共享内存进行数据通信,它对数据也没啥限制。

最后就是共享内存的生命周期随内核。即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象),在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留;简单地说,共享内存区域对象的生命周期跟系统内核的生命周期是一致的,而且共享内存区域对象的作用域范围就是在整个系统内核的生命周期之内。

缺陷

但是,共享内存也并不完美,共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。这明显还达不到我们想要的,我们不单是在两进程间交互数据,还想实现多个进程对共享内存的同步访问,这也正是使用共享内存的窍门所在。基于此,我们通常会用平时常谈到和用到 信号量来实现对共享内存同步访问控制。

 

与共享内存有关的函数

1.shmget()函数

函数头文件:#include   #include   #include

函数原型:int shmget(key_t key, size_t size, int shmflg)
函数功能: 创建或者获取共享内存

参数说明:

第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。 key_t ftok(char* fname, int id),文件名和项目编号确定唯一的键值,一个IPC对象对应一个键值,键值在创建IPC对象之前就存在了,文件描述符在创建独享之后存在。

 第二个参数size指定共享内存的大小,它的值一般为一页大小的整数倍(未到一页,操作系统向上对齐到一页,但是用户实际能使用只有自己所申请的大小)。

第三个参数shmflg是一组标志,创建一个新的共享内存,将shmflg 设置了IPC_CREAT标志后,共享内存存在就打开。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的共享内存,如果共享内存已存在,返回一个错误。一般我们会还或上一个文件权限

函数返回值:成功:返回创建或获取到的共享内存的描述符,失败返回-1

 

2.  shmctl()函数

函数头文件:#include   #include   

函数原型:int shmctl(int shm_id, int cmd, struct shmid_ds *buf); 

函数功能:操作共享内存,比如删除共享内存

函数参数:

(1)第一个参数,shm_id是shmget函数返回的共享内存标识符。

(2)第二个参数,cmd是要采取的操作,它可以取下面的三个值 :    

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。    

IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值    

IPC_RMID:删除共享内存段

(3)第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构,不使用设为0即可。 shmid_ds结构至少包括以下成员 

1

2

3

4

5

6

struct shmid_ds 

    uid_t shm_perm.uid; 

    uid_t shm_perm.gid; 

    mode_t shm_perm.mode; 

};

函数返回值:

成功:根据不同的操作返回不同值,失败返回-1

 

3.shmat()函数

函数头文件:#include   #include   

函数原型:void *shmat(int shm_id, const void *shm_addr, int shmflg);

函数功能:shmid指定的共享内存映射到进程的地址空间中

函数参数:shm_id是由shmget函数返回的共享内存标识,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址,shm_flg是一组标志位,通常为0

函数返回值:成功返回映射到进程空间的内存地址,出错返回-1 

 

4 shmdt()函数

函数头文件:#include   #include   

函数原型:int shmdt(const void *shmaddr);

函数功能:在进程内存地址空间中,断掉与共享内存的关系,该操作不从系统中删除标识符和其数据结构,要显示调用shmctl(带命令IPC_RMID)才能删除它

函数参数:shmaddr为要断开的共享内存的映射地址,shmat的返回值

函数返回值:成功返回0,出错返回-1 

 

共享内存进程间的通信实例

说了这么多,又到了实战的时候了。下面就以两个不相关的进程来说明进程间如何通过共享内存来进行通信。其中一个文件shmread.c创建共享内存,并读取其中的信息,另一个文件shmwrite.c向共享内存中写入数据。为了方便操作和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文件shmdata.c中。结构shared_use_st中的written作为一个可读或可写的标志,非0:表示可读,0表示可写,text则是内存中的文件。

shmdata.h的源代码如下:

1

2

3

4

5

6

7

8

9

#ifndef _SHMDATA_H_HEADER

#define _SHMDATA_H_HEADER

#define TEXT_SZ 2048

struct shared_use_st

{  

    int written;//作为一个标志,非0:表示可读,0表示可写 

    char text[TEXT_SZ];//记录写入和读取的文本

};

#endif

  源文件shmread.c的源代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

#include

#include

#include

#include

#include "shmdata.h"

int main()

{  

    int running = 1;//程序是否继续运行的标志  

    void *shm = NULL;//分配的共享内存的原始首地址   

    struct shared_use_st *shared;//指向shm   

    int shmid;//共享内存标识符 //创建共享内存   

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);

    if(shmid == -1)

    {      

        fprintf(stderr, "shmget failed\n");

        exit(EXIT_FAILURE);

    }   //将共享内存连接到当前进程的地址空间

    shm = shmat(shmid, NULL, 0);

    if(shm == (void*)-1)   

    {  

        fprintf(stderr, "shmat failed\n"); 

        exit(EXIT_FAILURE);

    }  

    printf("\nMemory attached at %X\n", (int)shm);  //设置共享内存   

    shared = (struct shared_use_st*)shm;   

    shared->written = 0;

    while(running)//读取共享内存中的数据 

    {       //没有进程向共享内存定数据有数据可读取       

        if(shared->written != 0)

        {      

            printf("You wrote: %s", shared->text);      

            sleep(rand() % 3);          //读取完数据,设置written使共享内存段可写

            shared->written = 0;         //输入了end,退出循环(程序)  

            if(strncmp(shared->text, "end", 3) == 0)    

                running = 0;       

        }      

        else//有其他进程在写数据,不能读取数据     

            sleep(1);  

    }   //把共享内存从当前进程中分离

    if(shmdt(shm) == -1)   

    {      

        fprintf(stderr, "shmdt failed\n");     

        exit(EXIT_FAILURE);

    }   //删除共享内存   

    if(shmctl(shmid, IPC_RMID, 0) == -1)   

    {  

        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  

        exit(EXIT_FAILURE);

    }  

    exit(EXIT_SUCCESS);

}

  源文件shmwrite.c的源代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#include

#include

#include

#include

#include

#include "shmdata.h"

int main()

{  

    int running = 1;   

    void *shm = NULL;  

    struct shared_use_st *shared = NULL;

    char buffer[BUFSIZ + 1];//用于保存输入的文本

    int shmid;  //创建共享内存

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);

    if(shmid == -1)

    {  

        fprintf(stderr, "shmget failed\n");

        exit(EXIT_FAILURE);

    }   //将共享内存连接到当前进程的地址空间

    shm = shmat(shmid, (void*)0, 0);   

    if(shm == (void*)-1)

    {  

        fprintf(stderr, "shmat failed\n");     

        exit(EXIT_FAILURE);

    }  

    printf("Memory attached at %X\n", (int)shm);    //设置共享内存   

    shared = (struct shared_use_st*)shm;   

    while(running)//向共享内存中写数据  

    {       //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本       

        while(shared->written == 1)     

        {          

            sleep(1);      

            printf("Waiting...\n");

        }       //向共享内存中写入数据       

        printf("Enter some text: ");       

        fgets(buffer, BUFSIZ, stdin); //从指定的流 sdin读取一行,并把它存储在buffer所指向的字符串内     

        strncpy(shared->text, buffer, TEXT_SZ);      //写完数据,设置written使共享内存段可读       

        shared->written = 1;     //输入了end,退出循环(程序)  

        if(strncmp(buffer, "end", 3) == 0)         

            running = 0;   

    }   //把共享内存从当前进程中分离

    if(shmdt(shm) == -1)   

    {      

        fprintf(stderr, "shmdt failed\n");     

        exit(EXIT_FAILURE);

    }  

    sleep(2);  

    exit(EXIT_SUCCESS);

}

  结果截图如下:

 

分析:

1、程序shmread创建共享内存,然后将它连接到自己的地址空间。在共享内存的开始处使用了一个结构struct_use_st。该结构中有个标志written,当共享内存中有其他进程向它写入数据时,共享内存中的written被设置为0,程序等待。当它不为0时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,然后重置设置共享内存中的written为0,即让其可被shmwrite进程写入数据。

2、程序shmwrite取得共享内存并连接到自己的地址空间中。检查共享内存中的written,是否为0,若不是,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。若共享内存的written为0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的written为1,表示写完成,其他进程可对共享内存进行读操作。

 

总结:

1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

 

参考资料:

https://www.cnblogs.com/wuyepeng/p/9748889.html

 

 

你可能感兴趣的:(linux系统编程)