• 共享内存


      共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
      采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

      默认情况下通过fork派生的子进程并不与父进程共享内存区。通过一个程序来验证,程序功能是让父子进程都给一个名为count的全局变量加1操作,程序如下:

    复制代码
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <sys/types.h>
     5 #include <semaphore.h>
     6 #include <fcntl.h>
     7 
     8 #define   SEM_NAME         "mysem"
     9 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
    10 int   count = 0;
    11 
    12 int main(int argc,char* argv[])
    13 {
    14     int  i,nloop;
    15     sem_t *mutex;
    16     if(argc != 2)
    17     {
    18         printf("usage: incrl <#loops>");
    19         exit(0);
    20     }
    21     nloop = atoi(argv[1]);
    22         //创建有名信号量
    23     mutex = sem_open(SEM_NAME,O_RDWR|O_CREAT|O_EXCL,FILE_MODE,1);
    24     sem_unlink(SEM_NAME);
    25         //将stdout设置为非缓冲的
    26     setbuf(stdout,NULL);
    27        //子进程开始执行增加1
    28     if(fork() == 0)
    29     {
    30         for(i = 0;i<nloop;++i)
    31         {
    32             sem_wait(mutex);
    33             printf("child: %d
    ",count++);
    34             sem_post(mutex);
    35         }
    36         exit(0);
    37     }
    38        //父进程执行增加1操作
    39     for(i = 0;i<nloop;++i)
    40     {
    41         sem_wait(mutex);
    42         printf("parent: %d
    ",count++);
    43         sem_post(mutex);
    44     }
    45         //等待子进程退出
    46     wait(NULL); 
    47     exit(0);
    48 }
    复制代码

    程序执行结果如下所示:

    从结果可以看出父子进程都有各自的全局变量count的副本,每个进程都从该变量为0的初始值开始的,每次增加的对象是各自的变量的副本。

    共享内存操作函数:

    1、系统调用mmap()
       void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )

       mamap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。

    2、系统调用munmap()
       int munmap( void * addr, size_t len )

       munmap函数从某个进程的地址空间中删除一个映射关系。addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

    3、系统调用msync()
       int msync ( void * addr , size_t len, int flags)

      一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

     现使用共享内存实现在内存映射文件中个计数器持续加1,程序如下所示:

    复制代码
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <sys/types.h>
     5 #include <semaphore.h>
     6 #include <fcntl.h>
     7 #include <sys/mman.h>
     8 #include <errno.h>
     9 
    10 #define   SEM_NAME         "mysem"
    11 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
    12 
    13 int main(int argc,char* argv[])
    14 {
    15     int     fd,i,nloop,zero = 0;
    16     int      *ptr;
    17     sem_t   *mutex;
    18     if(argc != 3)
    19     {
    20         printf("usage: incrl <#loops>");
    21         exit(0);
    22     }
    23     nloop = atoi(argv[2]);
    24     //打开文件
    25     fd = open(argv[1],O_RDWR|O_CREAT,FILE_MODE);
    26     //向文件中写入0值
    27     write(fd,&zero,sizeof(int));
    28     //将文件映射到进程地址空间,返回被映射区的起始地址
    29     ptr = mmap(NULL,sizeof(int),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
    30     if(ptr == MAP_FAILED)
    31     {
    32         perror("mmap() error");
    33         exit(0);
    34     }
    35     close(fd);
    36     mutex = sem_open(SEM_NAME,O_RDWR|O_CREAT|O_EXCL,FILE_MODE,1);
    37     sem_unlink(SEM_NAME);
    38     setbuf(stdout,NULL);
    39     if(fork() == 0)
    40     {
    41         for(i = -0;i<nloop;++i)
    42         {
    43             sem_wait(mutex);
    44             printf("child: %d
    ",(*ptr)++);
    45             sem_post(mutex);
    46         }
    47         exit(0);
    48     }
    49     for(i = 0;i<nloop;++i)
    50     {
    51         sem_wait(mutex);
    52         printf("parent: %d
    ",(*ptr)++);
    53         sem_post(mutex);
    54     }
    55     wait(NULL);
    56     exit(0);
    57 }
    复制代码

    程序执行结果如下所示:

    从结果可以看出父子进程共享内存区。可以将这个程序改成使用[osix基于内存的信号量,而不是一个Posix有名信号量,并把该信号量存放在共享内存中。程序如下所示:

    复制代码
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <sys/types.h>
     5 #include <semaphore.h>
     6 #include <fcntl.h>
     7 #include <sys/mman.h>
     8 #include <errno.h>
     9 
    10 #define   SEM_NAME         "mysem"
    11 #define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
    12 
    13 //共享内存结构
    14 struct shared
    15 {
    16     sem_t mutex;       //信号量
    17     int count;         //计数器
    18 }shared;
    19 
    20 int main(int argc,char* argv[])
    21 {
    22     int     fd,i,nloop;
    23     struct  shared *ptr;
    24     if(argc != 3)
    25     {
    26         printf("usage: incrl <#loops>");
    27         exit(0);
    28     }
    29     nloop = atoi(argv[2]);
    30     fd = open(argv[1],O_RDWR|O_CREAT,FILE_MODE);
    31     write(fd,&shared,sizeof(struct shared));
    32     ptr = mmap(NULL,sizeof(struct shared),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
    33     if(ptr == MAP_FAILED)
    34     {
    35         perror("mmap() error");
    36         exit(0);
    37     }
    38     close(fd);
    39     sem_init(&ptr->mutex,1,1);
    40     setbuf(stdout,NULL);
    41     if(fork() == 0)
    42     {
    43         for(i = -0;i<nloop;++i)
    44         {
    45             sem_wait(&ptr->mutex);
    46             printf("child: %d
    ",ptr->count++);
    47             sem_post(&ptr->mutex);
    48         }
    49         exit(0);
    50     }
    51     for(i = 0;i<nloop;++i)
    52     {
    53         sem_wait(&ptr->mutex);
    54         printf("parent: %d
    ",ptr->count++);
    55         sem_post(&ptr->mutex);
    56     }
    57     wait(NULL);
    58     exit(0);
    59 }
    复制代码

    程序执行结果与上面的一致。从上面的程序发现,我们在进行文件映射的时候,当文件不存在的时候需要在文件系统中创建一个文件然后打开。4.4BSD提供匿名内存映射,避免了文件的创建和打开。其解决办法是将mmap的flags的参数指定为MAP_SHARED | MAP_ANON,把fd参数指定为-1,offset参数则被忽略。这样的内存区初始化为0。实现如下所示:

    int *ptr;
    ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANON,-1,0);

    SVR4提供了/dev/zero设备文件,从该设备读是返回的字节全为0,写往该设备的任何字节被丢弃。实现如下所示:

    int *ptr;
    fd = open("dev/zero",O_RDWR);
    ptr = mmap(NULL,sizeof(int),PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
  • 相关阅读:
    java命名规范:注重细节
    撒旦法撒旦法三阿斯顿发暗室逢灯
    369绿色浏览器开发记录
    时间过得好快
    C++进程间通信(常用理解例子)-买票
    MFC常用 控制对话框透明属性函数
    DedeCms 建站随笔(一)
    个人作业收官——软件工程实践总结
    第三次作业——个人作业——软件产品案例分析
    UML用例图
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8468621.html
Copyright © 2020-2023  润新知