Linux进程间通信—共享内存

五.共享内存(shared memory)

共享内存映射为一段可以被其他进程访问的内存。该共享内存由一个进程所创建,然后其他进程可以挂载到该共享内存中。共享内存是最快的IPC机制,但由于linux本身不能实现对其同步控制,需要用户程序进行并发访问控制,因此它一般结合了其他通信机制实现了进程间的通信,例如信号量。

共享内存与多线程共享global data和heap类似。一个进程可以将自己内存空间中的一部分拿出来,允许其它进程读写。当使用共享内存的时候,我们要注意同步的问题。我们可以使用 semaphore同步,也可以在共享内存中建立mutex或其它的线程同步变量来同步。由于共享内存允许多个进程直接对同一个内存区域直接操作,所以它是效率最高的IPC方式。

最为高效的进程间通信方式

进程直接读写内存,不需要任何数据的拷贝

  •为了在多个进程间交换信息,内核专门留出了一块内存区

  •由需要访问的进程将其映射到自己私有地址空间

  •进程直接读写这一内存区而不需要进行数据的拷贝,提高了效率

多个进程共享一段内存,需要依靠某种同步机制,如互斥锁和信号量等

Linux进程间通信—共享内存

l共享内存编程步骤:

  1. 创建共享内存

    •函数shmget()

    •从内存中获得一段共享内存区域

  2. 映射共享内存

    •把这段创建的共享内存映射到具体的进程空间中

    •函数shmat()

  3. 使用这段共享内存

    •可以使用不带缓冲的I/O读写命令对其进行操作

  4. 撤销映射操作: 函数shmdt()

  5. 删除共享内存: 函数shctl()

Linux进程间通信—共享内存

Linux进程间通信—共享内存

Linux进程间通信—共享内存

Linux进程间通信—共享内存

Linux进程间通信—共享内存

eg. 下面这个例子完成:父进程从stdin读取字符串并保存到共享内存中,子进程从共享内存中读出数据并输出到stdout

Linux进程间通信—共享内存

1 #include <stdio.h>

2 #include <stdlib.h>

3 #include <string.h>

4 #include <sys/types.h>

5 #include <sys/ipc.h>

6 #include <sys/shm.h>

8 #define BUFFER_SIZE 2048

10 int main() {

11 pid_t pid;

12 int shmid;

13 char *shm_addr;

14 char flag[]="Parent";

15 char buff[BUFFER_SIZE];

16 // 创建当前进程的私有共享内存

17 if ((shmid=shmget(IPC_PRIVATE,BUFFER_SIZE,0666))<0) {

18 perror("shmget");

19 exit(1);

20 } else

21 printf("Create shared memory: %d.\n",shmid);

23 // ipcs 命令往标准输出写入一些关于活动进程间通信设施的信息

24 // -m 表示共享内存

25 printf("Created shared memory status:\n");

26 system("ipcs -m");

28 if((pid=fork())<0) {

29 perror("fork");

30 exit(1);

31 }else if (pid==0) {

32 // 自动分配共享内存映射地址,为可读可写,映射地址返回给shm_addr

33 if ((shm_addr=shmat(shmid,0,0))==(void*)-1) {

34 perror("Child:shmat");

35 exit(1);

36 }else

37 printf("Child: Attach shared-memory: %p.\n",shm_addr);

39 printf("Child Attach shared memory status:\n");

40 system("ipcs -m");

41 // 比较shm_addr,flag的长度为strlen(flag)的字符

42 // 当其内容相同时,返回0

43 // 否则返回(str1[n]-str2[n])

44 while (strncmp(shm_addr,flag,strlen(flag))) {

45 printf("Child: Waiting for data...\n");

46 sleep(10);

47 }

49 strcpy(buff,shm_addr+strlen(flag));

50 printf("Child: Shared-memory: %s\n",buff);

51 // 删除子进程的共享内存映射地址

52 if (shmdt(shm_addr)<0) {

53 perror("Child:shmdt");

54 exit(1);

55 }else

56 printf("Child: Deattach shared-memory.\n");

58 printf("Child Deattach shared memory status:\n");

59 system("ipcs -m");

61 }else{

62 sleep(1);

63 // 自动分配共享内存映射地址,为可读可写,映射地址返回给shm_addr

64 if ((shm_addr=shmat(shmid,0,0))==(void*)-1) {

65 perror("Parent:shmat");

66 exit(1);

67 }else

68 printf("Parent: Attach shared-memory: %p.\n",shm_addr);

70 printf("Parent Attach shared memory status:\n");

71 system("ipcs -m");

72 // shm_addr为flag+stdin

73 sleep(1);

74 printf("\nInput string:\n");

75 fgets(buff,BUFFER_SIZE-strlen(flag),stdin);

76 strncpy(shm_addr+strlen(flag),buff,strlen(buff));

77 strncpy(shm_addr,flag,strlen(flag));

78 // 删除父进程的共享内存映射地址

79 if (shmdt(shm_addr)<0) {

80 perror("Parent:shmdt");

81 exit(1);

82 }else

83 printf("Parent: Deattach shared-memory.\n");

85 printf("Parent Deattach shared memory status:\n");

86 system("ipcs -m");

87 // 保证父进程在删除共享内存前,子进程能读到共享内存的内容

88 waitpid(pid,NULL,0);

89 // 删除共享内存

90 if (shmctl(shmid,IPC_RMID,NULL)==-1) {

91 perror("shmct:IPC_RMID");

92 exit(1);

93 }else

94 printf("Delete shared-memory.\n");

96 printf("Child Delete shared memory status:\n");

97 system("ipcs -m");

99 printf("Finished!\n");

100 }

101 exit(0);

103 }

Linux进程间通信—共享内存

Linux进程间通信—共享内存

Linux进程间通信—共享内存

  共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的

进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。得

到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额

外的开销,但在现实中并不常用,因为它控制存取的将是实际的物理内存,在Linux系统

下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通

过shmXXX函数族来实现利用共享内存进行存储的。

  首先要用的函数是shmget,它获得一个共享存储标识符。

     #include <sys/types.h>

     #include <sys/ipc.h>

     #include <sys/shm.h>

     int shmget(key_t key, int size, int flag);

  这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共

享内存。Linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队

列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这

个关键字,就是上面第一个函数的 key。数据类型key_t是在头文件sys/types.h中定义的,

它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。

当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。

   void *shmat(int shmid, void *addr, int flag);

   shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确

定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行

读写操作。

  使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程

去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存

取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如

SHM_LOCK、SHM_UNLOCK等来实现。

共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址。

图共享内存映射图

Linux进程间通信—共享内存Linux进程间通信—共享内存

象所有的 System V IPC对象一样,对于共享内存对象的获取是由key控制。内存共享之后,对进程如何使用这块内存就不再做检查。它们必须依赖于其它机制,比如System V的信号灯来同步对于共享内存区域的访问(信号灯如何控制对临界代码的访问另起一篇说话)。

每一个新创建的共享内存对象都用一个shmid_kernel数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中,该向量表的每一个元素都是一个指向shmid_kernel数据结构的指针。

shm_segs向量表的定义如下:

struct shmid_kernel *shm_segs[SHMMNI];

,表示系统中最多可以有128个共享内存对象。

数据结构shmid_kernel的定义如下:

struct shmid_kernel

{

struct shmid_ds u;         /* the following are private */

unsigned long shm_npages;  /* size of segment (pages) */

unsigned long *shm_pages;  /* array of ptrs to frames -> SHMMAX */

struct vm_area_struct *attaches;  /* descriptors for attaches */

};

其中:

shm_pages代表该共享内存对象的所占据的内存页面数组,数组里面的每个元素当然是每个内存页面的起始地址.

shm_npages则是该共享内存对象占用内存页面的个数,以页为单位。这个数量当然涵盖了申请空间的最小整数倍.

(A new shared memory segment,  with size  equal to the value of size rounded up to a multiple of PAGE_SIZE)

shmid_ds是一个数据结构,它描述了这个共享内存区的认证信息,字节大小,最后一次粘附时间、分离时间、改变时间,创建该共享区域的进程,最后一次对它操作的进程,当前有多少个进程在使用它等信息。

其定义如下:

struct shmid_ds {

struct ipc_perm shm_perm;   /* operation perms */

int shm_segsz;              /* size of segment (bytes) */

__kernel_time_t shm_atime;  /* last attach time */

__kernel_time_t shm_dtime;  /* last detach time */

__kernel_time_t shm_ctime;  /* last change time */

__kernel_ipc_pid_t shm_cpid; /* pid of creator */

__kernel_ipc_pid_t shm_lpid; /* pid of last operator */

unsigned short shm_nattch;   /* no. of current attaches */

unsigned short shm_unused;   /* compatibility */

void *shm_unused2;           /* ditto - used by DIPC */

void *shm_unused3;           /* unused */

};

attaches描述被共享的物理内存对象所映射的各进程的虚拟内存区域。每一个希望共享这块内存的进程都必须通过系统调用将其关联(attach)到它的虚拟内存中。这一过程将为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。创建时可以指定共享内存在它的虚拟地址空间的位置,也可以让Linux自己为它选择一块足够的空闲区域。

这个新的vm_area_struct结构是维系共享内存和使用它的进程之间的关系的,所以除了要关联进程信息外,还要指明这个共享内存数据结构shmid_kernel所在位置; 另外,便于管理这些经常变化的vm_area_struct,所以采取了链表形式组织这些数据结构,链表由attaches指向,同时 vm_area_struct数据结构中专门提供了两个指针:vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构。

图 System V IPC 机制 - 共享内存

Linux进程间通信—共享内存Linux进程间通信—共享内存

Linux为共享内存提供了四种操作。

1. 共享内存对象的创建或获得。与其它两种IPC机制一样,进程在使用共享内存区域以前,必须通过系统调用sys_ipc (call值为SHMGET)创建一个键值为key的共享内存对象,或获得已经存在的键值为key的某共享内存对象的引用标识符。以后对共享内存对象的访问都通过该引用标识符进行。对共享内存对象的创建或获得由函数sys_shmget完成,其定义如下:

int sys_shmget (key_t key, int size, int shmflg)

这里key是表示该共享内存对象的键值,size是该共享内存区域的大小(以字节为单位),shmflg是标志(对该共享内存对象的特殊要求)。

它所做的工作如下:

1) 如果key == IPC_PRIVATE,则总是会创建一个新的共享内存对象。

但是  (The name choice IPC_PRIVATE was perhaps unfortunate, IPC_NEW would more clearly show its function)

* 算出size要占用的页数,检查其合法性。

* 申请一块内存用于建立shmid_kernel数据结构,注意这里申请的内存区域大小不包括真正的共享内存区,实际上,要等到第一个进程试图访问它的时候才真正创建共享内存区。

个字节),将页表清0。

* 搜索向量表shm_segs,为新创建的共享内存对象找一个空位置。

* 填写shmid_kernel数据结构,将其加入到向量表shm_segs中为其找到的空位置。

* 返回该共享内存对象的引用标识符。

2) 在向量表shm_segs中查找键值为key的共享内存对象,结果有三:

* 如果没有找到,而且在操作标志shmflg中没有指明要创建新共享内存,则错误返回,否则创建一个新的共享内存对象。

* 如果找到了,但该次操作要求必须创建一个键值为key的新对象,那么错误返回。

* 否则,合法性、认证检查,如有错,则错误返回;否则,返回该内存对象的引用标识符。

共享内存对象的创建者可以控制对于这块内存的访问权限和它的key是公开还是私有。如果有足够的权限,它也可以把共享内存锁定在物理内存中。

参见include/linux/shm.h

2. 关联。在创建或获得某个共享内存区域的引用标识符后,还必须将共享内存区域映射(粘附)到进程的虚拟地址空间,然后才能使用该共享内存区域。系统调用 sys_ipc(call值为SHMAT)用于共享内存区到进程虚拟地址空间的映射,而真正完成粘附动作的是函数sys_shmat,

其定义如下:

#include <sys/types.h>

#include <sys/shm.h>

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

其中:

shmid是shmget返回的共享内存对象的引用标识符;

shmaddr用来指定该共享内存区域在进程的虚拟地址空间对应的虚拟地址;

shmflg是映射标志;

返回的是在进程中的虚拟地址

该函数所做的工作如下:

1) 根据shmid找到共享内存对象。

,即用户没有指定该共享内存区域在它的虚拟空间中的位置,则由系统在进程的虚拟地址空间中为其找一块区域(从1G开始);否则,就用shmaddr作为映射的虚拟地址。

(If  shmaddr  is NULL, the system chooses a suitable (unused) address a他 which to attach the segment)

3) 检查虚拟地址的合法性(不能超过进程的最大虚拟空间大小—3G,不能太接近堆栈栈顶)。

4) 认证检查。

5) 申请一块内存用于建立数据结构vm_area_struct,填写该结构。

6) 检查该内存区域,将其加入到进程的mm结构和该共享内存对象的vm_area_struct队列中。

共享内存的粘附只是创建一个vm_area_struct数据结构,并将其加入到相应的队列中,此时并没有创建真正的共享内存页。

,表示共享页是第一次使用)。如果不存在,它就分配一个物理页,并为它创建一个页表条目。这个条目不但进入当前进程的页表,同时也存到shmid_kernel数据结构的页表shm_pages中。

当下一个进程试图访问这块内存并得到一个page fault的时候,经过同样的路径,也会走到函数shm_nopage。此时,该函数查看shmid_kernel数据结构的页表shm_pages时,发现共享页已经存在,它只需把这里的页表项填到进程页表的相应位置即可,而不需要重新创建物理页。所以,是第一个访问共享内存页的进程使得这一页被创建,而随后访问它的其它进程仅把此页加到它们的虚拟地址空间。

3. 分离。当进程不再需要共享虚拟内存的时候,它们与之分离(detach)。只要仍旧有其它进程在使用这块内存,这种分离就只会影响当前的进程,而不会影响其它进程。当前进程的vm_area_struct数据结构被从shmid_ds中删除,并被释放。当前进程的页表也被更新,共享内存对应的虚拟内存页被标记为无效。当共享这块内存的最后一个进程与之分离时,共享内存页被释放,同时,这块共享内存的shmid_kernel数据结构也被释放。

系统调用sys_ipc (call值为SHMDT) 用于共享内存区与进程虚拟地址空间的分离,而真正完成分离动作的是函数

sys_shmdt,其定义如下:

int sys_shmdt (char *shmaddr)

其中shmaddr是进程要分离的共享页的开始虚拟地址。

该函数搜索进程的内存结构中的所有vm_area_struct数据结构,找到地址shmaddr对应的一个,调用函数do_munmap将其释放。

在函数do_munmap中,将要释放的vm_area_struct数据结构从进程的虚拟内存中摘下,清除它在进程页表中对应的页表项(可能占多个页表项).

章中讨论。

4. 控制。Linux在共享内存上实现的第四种操作是共享内存的控制(call值为SHMCTL的sys_ipc调用),它由函数sys_shmctl实现。控制操作包括获得共享内存对象的状态,设置共享内存对象的参数(如uid、gid、mode、ctime等),将共享内存对象在内存中锁定和释放(在对象的mode上增加或去除SHM_LOCKED标志),释放共享内存对象资源等。

共享内存提供了一种快速灵活的机制,它允许进程之间直接共享大量的数据,而无须使用拷贝或系统调用。共享内存的主要局限性是它不能提供同步,如果两个进程企图修改相同的共享内存区域,由于内核不能串行化这些动作,因此写的数据可能任意地互相混合。所以使用共享内存的进程必须设计它们自己的同步协议,如用信号灯等。

以下是使用共享内存机制进行进程间通信的基本操作:

需要包含的头文件:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

1.创建共享内存:

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

参数说明:

key:用来表示新建或者已经存在的共享内存去的关键字。

size:创建共享内存的大小。

shmflg:可以指定的特殊标志。IPC_CREATE,IPC_EXCL以及低九位的权限。

eg:

int shmid;

shmid=shmget(IPC_PRIVATE,4096,IPC_CREATE|IPC_EXCL|0660);

if(shmid==-1)

perror("shmget()");

2.连接共享内存

char *shmat(int shmid,char *shmaddr,int shmflg);

参数说明

shmid:共享内存的关键字

shmflg:制定特殊的标志位。

eg:

int shmid;

char *shmp;

shmp=shmat(shmid,0,0);

if(shmp==(char *)(-1))

perror("shmat()\n");

3.使用共享内存

在使用共享内存是需要注意的是,为防止内存访问冲突,我们一般与信号量结合使用。

4.分离共享内存:当程序不再需要共享内后,我们需要将共享内存分离以便对其进行释放,分离共享内存的函数原形如下:

int shmdt(char *shmaddr);

5. 释放共享内存

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

共享内存(Shared Memory)

  共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址。

Linux进程间通信—共享内存

图共享内存映射图

  象所有的 System V IPC对象一样,对于共享内存对象的访问由key控制,并要进行访问权限检查。内存共享之后,对进程如何使用这块内存就不再做检查。它们必须依赖于其它机制,比如System V的信号灯来同步对于共享内存区域的访问。

  每一个新创建的共享内存对象都用一个shmid_kernel数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中,该向量表的每一个元素都是一个指向shmid_kernel数据结构的指针。shm_segs向量表的定义如下:

  struct shmid_kernel *shm_segs[SHMMNI];

,表示系统中最多可以有128个共享内存对象。

  数据结构shmid_kernel的定义如下:

struct shmid_kernel

{

struct shmid_ds u;         /* the following are private */

unsigned long shm_npages;  /* size of segment (pages) */

unsigned long *shm_pages;  /* array of ptrs to frames -> SHMMAX */

struct vm_area_struct *attaches;  /* descriptors for attaches */

};

  其中:shm_pages是该共享内存对象的页表,每个共享内存对象一个,它描述了如何把该共享内存区域映射到进程的地址空间的信息。

  shm_npages是该共享内存区域的大小,以页为单位。

  shmid_ds是一个数据结构,它描述了这个共享内存区的认证信息,字节大小,最后一次粘附时间、分离时间、改变时间,创建该共享区域的进程,最后一次对它操作的进程,当前有多少个进程在使用它等信息。其定义如下:

struct shmid_ds {

struct ipc_perm shm_perm;   /* operation perms */

int shm_segsz;              /* size of segment (bytes) */

__kernel_time_t shm_atime;  /* last attach time */

__kernel_time_t shm_dtime;  /* last detach time */

__kernel_time_t shm_ctime;  /* last change time */

__kernel_ipc_pid_t shm_cpid; /* pid of creator */

__kernel_ipc_pid_t shm_lpid; /* pid of last operator */

unsigned short shm_nattch;   /* no. of current attaches */

unsigned short shm_unused;   /* compatibility */

void *shm_unused2;           /* ditto - used by DIPC */

void *shm_unused3;           /* unused */

};

  attaches描述被共享的物理内存对象所映射的各进程的虚拟内存区域。每一个希望共享这块内存的进程都必须通过系统调用将其粘附(attach)到它的虚拟内存中。这一过程将为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。进程可以选择共享内存在它的虚拟地址空间的位置,也可以让Linux为它选择一块足够的空闲区域。

  这个新的vm_area_struct结构除了要连接到进程的内存结构mm中以外,还被连接到共享内存数据结构shmid_kernel的一个链表中,该结构中的attaches指针指向该链表。vm_area_struct数据结构中专门提供了两个指针:vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构。其实,虚拟内存并没有在粘附的时候创建,而要等到第一个进程试图访问它的时候才创建。

Linux进程间通信—共享内存

图 System V IPC 机制 - 共享内存

1. 共享内存对象的创建或获得。与其它两种IPC机制一样,进程在使用共享内存区域以前,必须通过系统调用sys_ipc (call值为SHMGET)创建一个键值为key的共享内存对象,或获得已经存在的键值为key的某共享内存对象的引用标识符。以后对共享内存对象的访问都通过该引用标识符进行。对共享内存对象的创建或获得由函数sys_shmget完成,其定义如下:

  int sys_shmget (key_t key, int size, int shmflg)

  这里key是表示该共享内存对象的键值,size是该共享内存区域的大小(以字节为单位),shmflg是标志(对该共享内存对象的特殊要求)。

  它所做的工作如下:

  1) 如果key == IPC_PRIVATE,则创建一个新的共享内存对象。

  * 算出size对应的页数,检查其合法性。

  * 搜索向量表shm_segs,为新创建的共享内存对象找一个空位置。

  * 申请一块内存用于建立shmid_kernel数据结构,注意这里申请的内存区域大小不包括真正的共享内存区,实际上,要等到第一个进程试图访问它的时候才真正创建共享内存区。

个字节),将页表清0。

  * 填写shmid_kernel数据结构,将其加入到向量表shm_segs中为其找到的空位置。

  * 返回该共享内存对象的引用标识符。

  2) 在向量表shm_segs中查找键值为key的共享内存对象,结果有三:

  * 如果没有找到,而且在操作标志shmflg中没有指明要创建新共享内存,则错误返回,否则创建一个新的共享内存对象。

  * 如果找到了,但该次操作要求必须创建一个键值为key的新对象,那么错误返回。

  * 否则,合法性、认证检查,如有错,则错误返回;否则,返回该内存对象的引用标识符。

  共享内存对象的创建者可以控制对于这块内存的访问权限和它的key是公开还是私有。如果有足够的权限,它也可以把共享内存锁定在物理内存中。

  参见include/linux/shm.h

  2. 粘附。在创建或获得某个共享内存区域的引用标识符后,还必须将共享内存区域映射(粘附)到进程的虚拟地址空间,然后才能使用该共享内存区域。系统调用 sys_ipc(call值为SHMAT)用于共享内存区到进程虚拟地址空间的映射,而真正完成粘附动作的是函数sys_shmat,其定义如下:

  int sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr)

  其中:

  shmid是共享内存对象的引用标识符;

  shmaddr该共享内存区域在进程的虚拟地址空间对应的虚拟地址;

  shmflg是映射标志;

  raddr是实际映射的虚拟空间地址。

  该函数所做的工作如下:

  1) 根据shmid找到共享内存对象。

,即用户没有指定该共享内存区域在它的虚拟空间中的位置,则由系统在进程的虚拟地址空间中为其找一块区域(从1G开始);否则,就用shmaddr作为映射的虚拟地址。

  3) 检查虚拟地址的合法性(不能超过进程的最大虚拟空间大小—3G,不能太接近堆栈栈顶)。

  4) 认证检查。

  5) 申请一块内存用于建立数据结构vm_area_struct,填写该结构。

  6) 检查该内存区域,将其加入到进程的mm结构和该共享内存对象的vm_area_struct队列中。

  共享内存的粘附只是创建一个vm_area_struct数据结构,并将其加入到相应的队列中,此时并没有创建真正的共享内存页。

,表示共享页是第一次使用)。如果不存在,它就分配一个物理页,并为它创建一个页表条目。这个条目不但进入当前进程的页表,同时也存到shmid_kernel数据结构的页表shm_pages中。

  当下一个进程试图访问这块内存并得到一个page fault的时候,经过同样的路径,也会走到函数shm_nopage。此时,该函数查看shmid_kernel数据结构的页表shm_pages时,发现共享页已经存在,它只需把这里的页表项填到进程页表的相应位置即可,而不需要重新创建物理页。所以,是第一个访问共享内存页的进程使得这一页被创建,而随后访问它的其它进程仅把此页加到它们的虚拟地址空间。

  3. 分离。当进程不再需要共享虚拟内存的时候,它们与之分离(detach)。只要仍旧有其它进程在使用这块内存,这种分离就只会影响当前的进程,而不会影响其它进程。当前进程的vm_area_struct数据结构被从shmid_ds中删除,并被释放。当前进程的页表也被更新,共享内存对应的虚拟内存页被标记为无效。当共享这块内存的最后一个进程与之分离时,共享内存页被释放,同时,这块共享内存的shmid_kernel数据结构也被释放。

  系统调用sys_ipc(call值为SHMDT)用于共享内存区与进程虚拟地址空间的分离,而真正完成分离动作的是函数sys_shmdt,其定义如下:

  int sys_shmdt (char *shmaddr)

  其中shmaddr是进程要分离的共享页的开始虚拟地址。

  该函数搜索进程的内存结构中的所有vm_area_struct数据结构,找到地址shmaddr对应的一个,调用函数do_munmap将其释放。

  在函数do_munmap中,将要释放的vm_area_struct数据结构从进程的虚拟内存中摘下,清除它在进程页表中对应的页表项(可能占多个页表项),调用该共享内存数据结构vm_area_struct的操作例程中的close操作(此处为shm_close)做进一步的处理。

,则释放共享内存页,释放共享内存页表,释放该对象的shmid_kernel数据结构,将向量表shm_segs中该共享内存对象所占用的项改为IPC_UNUSED。

章中讨论。

  4. 控制。Linux在共享内存上实现的第四种操作是共享内存的控制(call值为SHMCTL的sys_ipc调用),它由函数sys_shmctl实现。控制操作包括获得共享内存对象的状态,设置共享内存对象的参数(如uid、gid、mode、ctime等),将共享内存对象在内存中锁定和释放(在对象的mode上增加或去除SHM_LOCKED标志),释放共享内存对象资源等。

  共享内存提供了一种快速灵活的机制,它允许进程之间直接共享大量的数据,而无须使用拷贝或系统调用。共享内存的主要局限性是它不能提供同步,如果两个进程企图修改相同的共享内存区域,由于内核不能串行化这些动作,因此写的数据可能任意地互相混合。所以使用共享内存的进程必须设计它们自己的同步协议,如用信号灯等。

  以下是使用共享内存机制进行进程间通信的基本操作:

  需要包含的头文件:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

  1.创建共享内存:

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

  参数说明:

  • key:用来表示新建或者已经存在的共享内存去的关键字。
  • size:创建共享内存的大小。
  • shmflg:可以指定的特殊标志。IPC_CREATE,IPC_EXCL以及低九位的权限。

      eg:

    int shmid;

    shmid=shmget(IPC_PRIVATE,4096,IPC_CREATE|IPC_EXCL|0660);

    if(shmid==-1)

    perror("shmget()");

      2.连接共享内存

      char *shmat(int shmid,char *shmaddr,int shmflg);

      参数说明:

  • shmid:共享内存的关键字
  • shmflg:制定特殊的标志位。

      eg:

    int shmid;

    char *shmp;

    shmp=shmat(shmid,0,0);

    if(shmp==(char *)(-1))

    perror("shmat()\n");

      3.使用共享内存

      在使用共享内存是需要注意的是,为防止内存访问冲突,我们一般与信号量结合使用。

      4.分离共享内存

      当程序不再需要共享内后,我们需要将共享内存分离以便对其进行释放,分离共享内存的函数原形如下:

      int shmdt(char *shmaddr);

      5.释放共享内存

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

上一篇:[转] Freemarker的常用技巧总结


下一篇:Oracle日期的加减运算