共享内存

享内存区是指有一个内存区可以映射到共享它的进程的地址空间中,这样这些进程间数据的传递就不再涉及内核了。个进程中的多个线程之间之所以能够进行良好的通信交流,是因为线程可以访问进程的内存数据而就是说一个进程中的多个线程共享了进程的内存共享了进程所提供的资源。但是用线程来实现数据交互通信是满足不了需求的。因此我们现在需要考虑的是进程间交互。当然,管道和消息队列就可以实现进程间的通信。但由于这些IPC通信方式都在内核中也就是说,进程从IPC通道中读出这些数据通常都是从内核复制到进程。明显的,有了共享内存区后,进程想要从通道中读取数据,就无需在到内核中索取。因此,共享内存区是可用IPC形式中最快的。

      首先我们需要知道的是fork函数派生出来的子进程并没有跟其父进程共享了一块内存区。在fork时,子进程是复制了一份与父进程相似的内存区。因此,在fork以后,实质上这两个父子进程都是在各自的数据段,代码段等进行工作的。
        我们需要一个叫mmap的函数将一个文件或者一个Posix共享内存区对象映射到调用进程的地址空间。
         #include<sys/mman.h>
        void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t offset);
      addr可以指定将描述符fd映射到的进程内空间的起始地址。一般情况下设置为空指针,这样就是告诉内核自己去选择起始地址。最后该函数返回的是将fd映射到进程内存区的起始位置。len是映射到进程地址空间中的字节数,它从文件开头起第offset个自己处开始。
       内存映射区的保护是有prot指定。它的常值有:1.PROT_READ数据可读2. PROT_WRITE数据可写3.PROT_EXEC数据可执行4.PROT_NONE数据不可访问。而实际上一般使用该函数的目的是在于将该文件映射到进程内存区后,进程对该内存区的操作就是对该文件的操作。如果两个进程都将同一个文件映射到自己的进程空间中。那么看似进程只是对自己的进程空间的内容进行操作,可实际上他们都对了同一个文件进行了操作。这也就实现了进程间的通信了。因此为了能够实现这样的目的。flags参数指定了常值:MAP_SHARED变动是共享的即对进程地址区的操作就是对实际文件的操作;MAP_PRIVATE变动是私有的那么进程对进程地址区的操作只对该进程可见而不改变文件或者共享内存区对象;MAP_FIXED准确地解释addr参数

       fork函数虽然不能使子进程直接共享到父进程的内存区,但是它却可以将父进程在调用fork之前创建的内存映射关系由子进程共享了。Posix基于内存的信号量也因为存放在共享内存区中而能够使得在进程之间共享使用了。下面通过一个例子来看看具体的信号量和共享内存区如何使用:
struct shared
{
    sem_t mutex;
    int count;
}shared;    //将来提供给父子进程共享的数据,注意如果没有使用映射,那么父子进程是各有一份,而不是共享
int main(int argc,char **argv)
{
    int fd,i,nloop,zero=0;
    struct shared *ptr;
    if(argc != 3)
    {
        printf("命令行参数格式出错!\n");
        exit(0);
    }
    nloop = atoi(argv[2]);   // 用户设置次数
    fd = open(argv[1],O_RDWR|O_CREAT,FILE_MODE);  //实际创建一个文件以提供共享
    write(fd,&shared,sizeof(struct shared));   //向文件中写入共享的数据
    ptr = mmap(NULL,sizeof(struct shared),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    sem_init(&ptr->mutex,1,1);    // 创建信号量以协助父子进程同步
    setbuf(stdout,NULL);
    if(fork() == 0)  //在创建子进程之前就映射内存区,是方便子进程直接共享映射关系
    {
        for( i = 0; i < nloop; i++)
        {
            sem_wait(&ptr->mutex);
            printf("child:%d \n",(ptr->count)++);
            sem_post(&ptr->mutex);
        }
        exit(0);
    }
    for(i = 0; i< nloop; i++)    //父进程
    {
        sem_wait(&ptr->mutex);
        printf("parent:%d\n",(ptr->count)++);
        sem_post(&ptr->mutex);
    }
    exit(0);
}
   执行过程 :zd@zhengdan:~/3+1学习/共享内存$ ./incr /home/zd/test.txt 5
parent:0
parent:1
parent:2
parent:3
parent:4
child:5
child:6
child:7
child:8
child:9
我们也可以使用命令来查看test.txt是否发生改变:
zd@zhengdan:~/3+1学习/共享内存$ od -D /home/zd/test.txt
0000000          1          0          0          0
0000020         10
0000024

上面介绍的共享内存是大多是指内存映射文件方法:由open函数打开,由mmap函数映射。下面介绍另外一个方法,共享内存区对象:由shm_open打开一个posixIPC名字,所返回的描述符由mmap函数映射。这两种的差别只是在于描述符fd的获取手段不同。posix共享内存区指定一个名字参数调用shm_open,以创建一个新的共享内存对象或者打开一个已经存在的共享内存区对象。
   #include<sys/mman.h>
     int shm_open(const char *name,int oflag,mode_t mode);
     int shm_unlink(const char *name);
我们利用这种方法来实现生产者-消费者,这里是指多个生产者和单个消费者:
消费者程序:
#define FILE_MODE 0644
#define MESGSIZE 256
#define NMESG 16
struct shmstruct{
    sem_t mutex;
    sem_t nempty;
    sem_t nstored;
    int nput;
    long noverflow;
    sem_t noverflowmutex;
    long msgoff[NMESG];
    char msgdata[NMESG*MESGSIZE];
};
   
int main(int argc,char **argv)
{
    int fd,index,lastnoverflow,temp;
    long offset;
    struct shmstruct *ptr;

    if(argc != 2)
    {
        printf("命令行参数格式出错!\n");
        exit(0);
    }
    shm_unlink(argv[1]);
    fd = shm_open(argv[1],O_RDWR|O_CREAT| O_EXCL,FILE_MODE);
    ftruncate(fd,sizeof(struct shmstruct));
    ptr = mmap(NULL,sizeof(struct shmstruct),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
   
    for(index = 0; index< NMESG;index++)
        ptr->msgoff[index] = index *MESGSIZE;
    sem_init(&ptr->mutex,1,1);
    sem_init(&ptr->nempty,1,NMESG);
    sem_init(&ptr->nstored,1,0);
    sem_init(&ptr->noverflowmutex,1,1);

    index = 0;
    lastnoverflow =0;
    for(;;)
    {
        sem_wait(&ptr->nstored);
        sem_wait(&ptr->mutex);
        offset = ptr->msgoff[index];
        printf("index = %d :%s\n",index,&ptr->msgdata[offset]);
        if(++index >= NMESG)
            index =0;
        sem_post(&ptr->mutex);
        sem_post(&ptr->nempty);

        sem_wait(&ptr->noverflowmutex);
        temp = ptr->noverflow;
        sem_post(&ptr->noverflowmutex);
        if(temp != lastnoverflow){
            printf("noverflow = %d\n",temp);
            lastnoverflow = temp;
        }
    }
    exit(0);
}
生产者程序:
#define FILE_MODE 0644
#define MESGSIZE 256
#define NMESG 16
struct shmstruct{
    sem_t mutex;
    sem_t nempty;
    sem_t nstored;
    int nput;
    long noverflow;
    sem_t noverflowmutex;
    long msgoff[NMESG];
    char msgdata[NMESG*MESGSIZE];
};
   
int main(int argc,char **argv)
{
    int fd,i,nloop,nusec;
    pid_t pid;
    char mesg[MESGSIZE];
    long offset;
    struct shmstruct *ptr;

    if(argc != 4)
    {
        printf("命令行参数格式出错!\n");
        exit(0);
    }
    nloop = atoi(argv[2]);
    nusec = atoi(argv[3]);

    fd = shm_open(argv[1],O_RDWR,FILE_MODE);
    ptr = mmap(NULL,sizeof(struct shmstruct),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
   
    pid =getpid();
    for(i = 0; i< nloop;i++)
    {
        sleep(nusec/1000);
        snprintf(mesg,MESGSIZE,"pid %ld:message %d",(long)pid,i);
        if(sem_trywait(&ptr->nempty) == -1)
        {
            if(errno == EAGAIN)
            {
                sem_wait(&ptr->noverflowmutex);
                   ptr->noverflow++;
                sem_post(&ptr->noverflowmutex);
                continue;
            }
            else
            {
                printf("sem_trywait error");
                exit(0);
            }
         }
        sem_wait(&ptr->mutex);
        offset = ptr->msgoff[ptr->nput];
        if(++(ptr->nput) >= NMESG)
            ptr->nput =0;
        sem_post(&ptr->mutex);
        strcpy(&ptr->msgdata[offset],mesg);
        sem_post(&ptr->nstored);
    }
    exit(0);
}


PS:进程通信就这样告一段落了,也算对四种IPC通道有了一定的了解了,接下来。。。。。

原文地址:https://www.cnblogs.com/meronzhang/p/2753565.html