进程间通信--共享内存

来源:互联网 发布:js md5算法 编辑:程序博客网 时间:2024/05/21 19:38
  一、共享内存的数据结构
    共享内存就是分配一块能被其他进程访问的内存。每个共享内存段在内核中维护着一个内部结构shmid_ds(和消息队列、信号量一样),该结构定义在头文件linux/shm.h中,代码如下所示:
struct shmid_ds{
struct ipc_perm        shm_perm;
int                             shm_segsz;
__kernel_time_t         shm_atime;
__kernel_time_t         shm_dtime;
__kernel_time_t         shm_ctime;
__kernel_ipc_pid_t     shm_cpid;
__kernel_ipc_pid_t     shm_lpid;
ushort                      shm_nattch;
ushort                      shm_unused;
void                          *shm_unused2;
void                          *shm_unused3;
};
    代码中主要字段含义如下:
    shm_perm:操作许可,里面包含共享内存的用户ID、组ID等信息。
    shm_segsz:共享内存段的大小,以字节为单位。
    shm_atime:最后一个进程访问共享内存的时间。
    shm_dtime:最后一个进程离开共享内存的时间。
    shm_ctime:最后一次修改共享内存的时间。
    shm_cpid:创建共享内存的进程ID。
    shm_lpid:最后操作共享内存的进程ID。
    shm_nattch:当前使用该共享内存段的进程数量。
    二、共享内存的创建与操作
    1、共享内存区的创建
    Linux下使用函数shmget来创建一个共享内存区,或者访问一个已存在的共享内存区。该函数定义在头文件sys/shm.h中,原型如下:
int shmget(key_t key,size_t size,int shmflg);
    函数中:参数key是由ftok()得到的键值;参数size以字节为单位指定内存的大小;shmflg为操作标志位,它的值为一些宏,如下所示。
    IPC_CREAT:调用shmget时,系统将些值与其他所有共享内存区的key进行比较,如果存在相同的key,说明共享内存区已存在,此时返回该共享内存区标识符,否则新建一个共享内存区并返回其标识符。
    IPC_EXCL:该宏必须和IPC_CREAT一起使用,否则没有意义。当shmflg取IPC_CREAT|IPC_ECXL时,表示如果发现信号集已经存在,则返回-1,错误码为EEXIST。
    注意:当创建一个新的共享内存区时,size值必须大于0;如果是访问一个已存在的共享内存区,置size为0。
    2、共享内存区的操作
    在使用共享内存区前,必须通过shmat函数将其附加到进程的地址空间。进程与共享内存就建立了连接。shmat调用成功后就会返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败返回-1。该函数声明在sys/shm.h文件中,具体结构代码原型如下:
void *shmat(int shmid,const void *shmaddr,int shmflg);
    参数shmid为shmget的返回值;参数shmflg为存取权限标志;参数shmaddr为共享内存的附加点。参数shmaddr不同取值情况的含义说明如下:
    如果为空,则由内核选择一个空闲的内存区;如果非空,返回地址取决于调用者是否给shmflg参数指定了SHM_RND值,如果没有指定,则共享内存区附加到由shmaddr指定的地址;否则附加地址为shmaddr向下舍入一个共享内存低端边界地址后的地址SHMLBA,一个常址)。
    通常将参数shmaddr设置为NULL。
    当进程结束使用共享内存区时,要通过函数shmdt断开与共享内存区的连接。该函数声明在sys/shm.h文件中,具体结构代码原型如下:
int shmdt(const void* shmaddr);
    参数shmaddr为shmat函数的返回值。该函数调用成功后,返回0,否则返回-1。进程脱离共享内存区后,数据结构shmid_ds中的shm_nattch就会减1。但是共享内存段依然存在,shm_nattch为0后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。
    3、共享内存区的控制
    Linux对共享内存区的控制是通过调用函数shmctl来完成的,该函数定义在头文件sys/shm.h中,原型代码如下所示:
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    函数中:参数shmid为共享内存区的标识符;buf为指向shmid_ds结构体的指针;cmd为操作标志位,支持以下3种控制操作。
    IPC_RMID:从系统中删除由shmid标识的共享内存区。
    IPC_SET:设置内存内存区的shmid_ds结构。
    IPC_STAT:读取共享内存区的shmid_ds结构,并将其存储到buf指向的地址中。
    三、共享内存的应用实例
    本例10-17通过读写者问题(不考虑优先级)来演示共享内存和信号量如何配合使用。这里的读者写者问题要求一个进程读共享内存的时候,其他进程不能写内存:当一个进程写共享内存的时候,其他进程不能读内存。
    程序首先定义了一个包含公用函数的头文件10-17.h。
    例10-17
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>

#define SHM_SIZE    1024

union semun{
int                val;
struct semid_ds    *buf;
unsigned short     *array;
};

/*创建信号量函数*/
int createsem(const char *pathname,int proj_id,int members,int init_val)
{
key_t msgkey;
int index,sid;
union semun semopts;

if ((msgkey = ftok(pathname,proj_id)) == -1)
    {
      perror("ftok error!\n");
      return -1;
    }

if ((sid = semget(msgkey,members,IPC_CREAT|0666)) == -1)
    {
      perror("semget call failed.\n");
      return -1;
    }

/*初始化操作*/
semopts.val = init_val;
for (index = 0;index < members;index++)
    {
      semctl(sid,index,SETVAL,semopts);
    }
return (sid);
}


/*打开信号量函数*/
int opensem(const char *pathname,int proj_id)
{
key_t msgkey;
int sid;

if ((msgkey = ftok(pathname,proj_id)) == -1)
    {
      perror("ftok error!\n");
      return -1;
    }

if ((sid = semget(msgkey,0,IPC_CREAT|0666)) == -1)
    {
      perror("semget call failed.\n");
      return -1;
    }

return (sid);
}

/*P操作函数*/
int sem_p(int semid,int index)
{
struct sembuf buf ={0,-1,IPC_NOWAIT};

if (index < 0)
    {
      perror("index of array cannot equals a minus value!");
      return -1;
    }

buf.sem_num = index;
if (semop(semid,&buf,1) == -1)
    {
      perror("a wrong operation to semaphore occurred!");
      return -1;
    }

return 0;
}

/*V操作函数*/
int sem_v(int semid,int index)
{
struct sembuf buf = {0,+1,IPC_NOWAIT};

if (index < 0)
    {
      perror("index of array cannot equals a minus value!\n");
      return -1;
    }

buf.sem_num = index;
if (semop(semid,&buf,1) == -1)
    {
      perror("a wrong operation to semaphore occurred!\n");
      return -1;
    }

return 0;
}

/*删除信号集函数*/
int sem_delete(int semid)
{
return(semctl(semid,0,IPC_RMID));
}

/*等待信号为1*/
int wait_sem(int semid,int index)
{
while(semctl(semid,index,GETVAL,0) == 0)
    {
      sleep(1);
    }

return 1;
}

/*创建共享内存函数*/
int createshm(char *pathname,int proj_id,size_t size)
{
key_t shmkey;
int sid;

/*获取键值*/
if ((shmkey = ftok(pathname,proj_id)) == -1)
    {
      perror("ftok error!\n");
      return -1;
    }

if ((sid = shmget(shmkey,size,IPC_CREAT|0666)) == -1)
    {
      perror("shmget call failed.\n");
      return -1;
    }

return (sid);
}
    例10-18和例10-19写入和读取程序,两个程序在进入共享内存区之前,首先都检查信号集中信号的值是否为1(相当于是否能进入共享内存区),如果不为1,调用sleep()进入睡眠状态直到信号的值变为1。进入共享内存区之后,将信号的值减1(相当于加锁),这样就实现了互斥访问共享资源。在退出共享内存时,将信号值加1(相当于解锁)。
    例10-18
#include "10-17.h"

int main()
{
int semid,shmid;
char *shmaddr;
char write_str[SHM_SIZE];

if ((shmid = createshm(".",'m',SHM_SIZE)) == -1)
    {
      exit(1);
    }

if ((shmaddr = shmat(shmid,(char *)0,0)) == (char *)-1)
    {
      perror("attach shared memory error!\n");
      exit(1);
    }

if ((semid = createsem(".",'s',1,1)) == -1)
    {
      exit(1);
    }

while (1)
    {
      wait_sem(semid,0);
      sem_p(semid,0); //P操作

      printf("write:");
      fgets(write_str,1024,stdin);
      int len = strlen(write_str) - 1;
      write_str[len] = '\0';
      strcpy(shmaddr,write_str);
      sleep(10);    //使10-19处于阻塞状态

      sem_v(semid,0); //V操作
      sleep(10);      //等待10-19进行读操作
    }
}
    例10-19
#include "10-17.h"

int main()
{
int semid,shmid;
char *shmaddr;

if ((shmid = createshm(".",'m',SHM_SIZE)) == -1)
    {
      exit(1);
    }

if ((shmaddr = shmat(shmid,(char *)0,0)) == (char *)-1)
    {
      perror("attach shared memory error!\n");
      exit(1);
    }

if ((semid = opensem(".",'s')) == -1)
    {
      exit(1);
    }

while (1)
    {
      printf("reader:");
      wait_sem(semid,0); //等待信号值为1
      sem_p(semid,0);     //P操作
      
      printf("%s\n",shmaddr);
      sleep(10);          //使10-18处于阻塞状态
      
      sem_v(semid,0);     //V操作
      sleep(10);          //等待10-18进行写操作
    }
}
    程序说明:
    注意10-18和10-19的头文件引用使用的是""这样把头文件10-17放到与10-18,10-19在同一目录下就可以引用了。
    同时在两个终端运行10-18和10-19,在10-18端输入字符串“hello,world”,等待一会,看到10-19端输出后,再在10-18端的提示符后输入字符串“new information”。执行的结果如下:
$ ./10-18
write:hello world
write:new information
$ ./10-19
reader:hello world
reader:new information
    从程序运行结果可以看出,10-18和10-19进程是同步的,10-18写入的信息均被10-19完整正确地读出,即使去掉程序中的sleep()也不会影响运行结果。


转自:http://hi.baidu.com/monalisa88188/blog/item/7b10eaae510016f2fbed5049.html