Linux进程通信(System V)第五节------>共享内存区

来源:岁月联盟 编辑:exp 时间:2012-03-20
一:简介 前面已经学过:pipe,FIFO,msg queue, 今天要学的是“共享内存区” 1.共享内存区是进程通信中最快的方式,而且传递的信息量是很大的!2.是通过内存区间映射到进程空间来实现的!因此这种进程间的通信不再涉及到内核!(即进程不是通过执行任何的进入内核的系统调用来传递数据的。这样内核就必须建立允许各个进程之间的共享内存区的映射关系,然后一直管理该内存区!同时也要保证所谓的同步,有序,而且没有死锁!)3.简单的实现过程:               <1>.server获得访问共享内存区的权限               <2>.server从输入文件中读取数据到共享内存区(需要一次copy内容 )               <3>.server输入数据OK后,通知用户进程               <4>.最后用户进程从共享内存区中取出data( 需要一次copy内容)                              但是与pipe,FIFO以及msg queue的区别是:                               此三者需要进程的操作是:server和client的发送和接受data都是需要进行一次copy,所以一共有4次数据拷贝,所以效率不如“共享内存区”               4.数据结构:                #include<sys/shm.h>                               structshmid_ds                {                   structipc_perm      shm_perm;         //!> 权限设置结构体                   size_t               shm_segsz;         //!> 内存块大小                  pid_t               shm_lpid;            //!> 最后一次操作的进程ID                  pid_t               shm_cpid;            //!> 创建进程的ID                  shmatt_t            shm_nattch;         //!> 当前的附接数                  shmatt_t            shm_cnattch;         //!> 内核的附接数                  time_t               shm_atime;         //!> 最后一次关联时间                  time_t               shm_dtime;         //!> 分离时间                  time_t               shm_ctime;         //!> 最后一次改变时间               };                                       5.共享内存区的创建和操作:                 #include<sys/types>                #include<sys/ipc.h>                #include<sys/shm.h>                      int shmget(key_t key, size_t size, int oflag );                参数:                     key:ftok返回值或者IPC_PRIVATE                     size:共享内存区大小( 字节为单位,if访问一个已经存在的,那么就是0 )                      oflag:权限的组合(同前面讲的 )                                       创建OK后,那么就可以使用shmat函数来链接到它的地址空间!                void *shmat( int shmid, const void * addr, int flag);                         //!> -------->链接共享区到哪个地址上与addr参数以及flag中是否指定SHM_RND有关!               //!> if addr == 0,那么连接到内核选择的第一个可用地址上                //!> if addr!= 0 && 没有SHM_RND,那么连接到addr上                //!> if addr !=0 && 指定SHM_RND,那么连接到addr-( addr modSHMLBA )上,SHM_RND是取整意思                //!>SHMLBA是指最低边界地址倍数。所以此算式表示靠近addr的一个边界地址上                     使用OK后就可以断开链接:shmdt函数                int shmdt(const void * addr);                           //!> 注意参数是shmat的返回值!!!                                             最后要删除内存:shmctl函数                int shmctl(int shmid, int cmd, struct shmid_ds * buff );                参数:                     shmid:就是创建或者打开是shmget函数的返回值                     cmd:有多种取值                     buff:主要用在IPC_STAT取回结构体的值和IPC_SET设置结构体值中使用!                      cmd:                           IPC_STAT:取得shmid_ds的结构体,放在buff中                           IPC_SET:按照buff设置结构体权限值-> sem_perm.uid, sem_perm.gid, sem_perm.mode;注意其允许执行的权限进程( 与前面一样)                           IPC_RMID:删除共享内存区,注意其删除是与文件inode差不多,只有当计数值为0才删除,否则仅仅是删除一个标志顺便计数器--就OK!                           SHM_LOCK:锁住共享内存区,只有super用户权限才OK!                           SHM_UNLOCK:   解锁,权限用户为super                        二.//-------------------------------------------------------------------------------------------------//简单的函数应用  //////       创建实例 #include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <stdlib.h>#include <sys/shm.h> int main( int argc, char ** argv ){    int       semid;    int       flag;   size_t   len;     if( argc !=3 )                     //!> 表示我们要输入2个字符串参数(因为第一个是默认的程序运行的全路径名 )    {       printf("usage:shmget <pathname><length>/n" );       exit(EXIT_FAILURE );    }       len = atoi(argv[2] );               //!> 第二个参数作为长度而已    flag =IPC_CREAT;   //!> 创建标志( 具有唯一性 )------->注意IPC_EXCL:决定了唯一性! | IPC_EXCL    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.   //!> Create the sempore    if( ( semid= semget( ftok( argv[1], 'a' ), len, flag ) ) == -1 )    {      printf("/nCreate semaprore error.../n");       exit(EXIT_FAILURE );    }     printf("/nThe semid = %d/n", semid );         //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.   //!> Display attribute    intdis;    structshmid_ds  buff[10];    if( ( dis =shmctl( semid, IPC_STAT, buff ) ) == -1 )    {       printf("/nDisplay the attribute error.../n" );       exit(EXIT_FAILURE );    }    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.   //!> Delete the semapore    intdel;    if( ( del =shmctl( semid, IPC_RMID, NULL ) ) == -1 )    {       printf("/nDelete error... /n");       exit(EXIT_FAILURE );    }     return0;} 三://-------------------------------------------------------------------------------------------------// 生产者与消费者 1.core简介:      我们可以知道在server(生产者)中我们给的最大的src就是5个,所以if我们仅仅是只执行serever,那么执行5次后必须要等待,因为P不到src了,但是if有client(消费者)存在,那么就可以,因为消费者消费OK后归还src,那么server又可以执行下去了。 2.CODE: //////      producer #include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>#include <sys/shm.h>#include <sys/stat.h>          #define MAXSHM      5         //!> 定义缓冲区变量个数 union semun                     //!> 此处我们主要是为了SETVALUE使用{    int                  val;         //!>设置信号的值                   structsemid_ds*      buf;      //!> buffer:IPC_STAT, IPC_SET    unsignedshort*     array;      //!> GETALL, SETALL的数组}; int main(){   key_t      ipckey;            //!> ipc 的 key   key_t      semkey;            //!> 信号量的key       int         shmid;               //!> ipc(此处是共享内存区模式导致)的ID    int         semid;               //!> 共享内存区ID      char*      addr_c;            //!> 共享区的地址      //!>>>>>>>>>>>>>>>>>>>>>>>>>>   //!>   对于共享区的处理(key  and id  and  addr. )       ipckey =ftok( "/tmp/Linux/ipc", 368);               //!> get ipc key    if( ipckey== -1 )    {       printf("/nCreate IPC key error.../n" );       exit(EXIT_FAILURE );    }       shmid =shmget( ipckey, 1024, IPC_CREAT | 0666);   //!> get ipc id    if( shmid ==-1 )    {       printf("/nCreate IPC id error.../n" );       exit(EXIT_FAILURE );    }       addr_c = (char * )shmat( shmid, NULL, 0);            //!> 链接到第一个可用的地址上    if( *( ( int* )addr_c ) == -1)                        //!> Set addr...    {       printf("/nSet addr. error.../n" );       exit(EXIT_FAILURE );    }      //!>>>>>>>>>>>>>>>>>>>>>>>>>>   //!>   对于信号量的处理(key  and  id)     structsembuf   P, V;                     //!> P V    操作变量   union semun       arg1, arg2,arg3;         //!> 设置semid此信号集合中的三种信号量                                          //!> 具体的下面有解释                                              semkey =ftok( "/tmp/Linux/sem", 368);      //!> get sempore key    if( semkey== -1 )    {       printf("/nCeate sem. key error.../n" );       exit(EXIT_FAILURE );    }       //!> 请注意此处创建的一个信号量集合!   //!> 里面可以有不同的信号处理不同事件!!!!    semid =semget( semkey, 3, IPC_CREAT | 0666);      //!> get sem. id                                                   //!> 信号集合中信号为3种    if( semid< 0 )    {       printf("/nCreate sem. id error.../n" );       exit(EXIT_FAILURE );   }       //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!>   初始化信号灯中信号量             arg1.val =0;                              //!> 缓冲区无数据,( 即信号满 )    if( semctl(semid, 0, SETVAL, arg1 ) == -1)   //!> 设置VALUE = arg1 = 0   {                                       //!> sem 编号为0       printf("/nSelValue (信号满) error.../n" );       exit(EXIT_FAILURE );    }       arg2.val =MAXSHM;                     //!> 缓冲区 5 个空闲元素    if( semctl(semid, 1, SETVAL, arg2 ) == -1)   //!> 设置VALUE == arg2 = 5   {                                       //!> sem 编号为1       printf("/nSetValue (信号空) error.../n" );       exit(EXIT_FAILURE );    }       arg3.val =1;                              //!> 这个相当于是互斥使用缓冲区    if( semctl(semid, 2, SETVAL, arg3 ) == -1 )    {       printf("/nCreate 互斥缓冲区 error.../n" );       exit(EXIT_FAILURE );    }      //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!>    初始化P  V  操作       P.sem_num =0;                         P.sem_op =-1;                     //!> 注意:P的操作是 -- 操作( 就是-1处理)       P.sem_flg =SEM_UNDO;       V.sem_num =0;    V.sem_op= 1;                     //!> 注意:V的操作是 ++ 操作( 就是+1处理 )    V.sem_flg =SEM_UNDO;                                       //!>下面进行的就是简单的PV操作       int i =0;          while( i< 10)                     //!> 进行10次操作    {       P.sem_num =1;                  //!> 注意P操作的是index=1的信号量                                    //!> 也就是存在元素的集合(--操作)       semop(semid, &P, 1);            //!> 进行P操作一次             P.sem_num =2;                  //!> 此处是互斥操作信号量       semop(semid, &P, 1);            //!> 只让一个进程操作,其他的等待             addr_c[i] =i + 'a';               //!> 仅仅是为了输出显示而已       printf("/n产生空间  addr_c[%d]  =%c  /n", i, addr_c[i]);             V.sem_num =2;                  //!> 对互斥操作进行V       semop(semid, &V, 1 );             V.sem_num =0;                  //!> P一个src后,就要加入开始没有元素的信号量中       semop(semid, &V, 1 );             i++;       sleep( 1);    }     sleep( 60);      //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 退出共享区失败       if( shmdt(addr_c ) == -1)                         {       printf("/n退出共享区失败/n" );       exit(EXIT_FAILURE );    }    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 删除共享区       if( shmctl(shmid, IPC_RMID, NULL ) == -1)          {       printf("/n删除共享区失败/n" );       exit(EXIT_FAILURE );    }      //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 删除信号集       if( semctl(semid, 0, IPC_RMID, 0 ) == -1 )    {       printf("/n撤销信号集失败/n" );       exit(EXIT_FAILURE );    }     exit(EXIT_SUCCESS );}   /////      consumer #include <stdio.h>#include <string.h>#include <stdlib.h>#include <fcntl.h>#include <sys/stat.h>#include <sys/ipc.h>#include <sys/sem.h>#include <sys/shm.h>#include <sys/types.h> #define MAXSHM          5 union semun                     //!> 此处我们主要是为了SETVALUE使用{    int                  val;         //!>设置信号的值                   structsemid_ds*      buf;      //!> buffer:IPC_STAT, IPC_SET    unsignedshort*     array;      //!> GETALL, SETALL的数组}; int main(){   key_t      ipckey;   key_t      semkey;       int          shmid;    int          semid;      char*      addr_c;    //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 得到IPC/SEM的key和id       ipckey =ftok( "/tmp/Linux/ipc", 368);         //!> get ipc key    if( ipckey== -1 )    {      printf("/nCreate IPC key error.../n");       exit(EXIT_FAILURE );    }       shmid =shmget( ipckey, 1024, IPC_EXCL | 0666 );    if( shmid ==-1 )    {       printf("/nCreate IPC  ID error.../n" );       exit(EXIT_FAILURE );    }          addr_c = (char * )shmat( shmid, NULL, 0 );    if( *( ( int* ) addr_c ) == -1 )    {       printf("/nCreate addr error.../n" );       exit(EXIT_FAILURE );   }            //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 定义信号量的数据结构     structsembuf       P, V;       semkey =ftok( "/tmp/Linux/sem", 368 );    if( semkey== -1 )    {      printf("/nCreate sem. key error... /n");       exit(EXIT_FAILURE );    }     semid =semget( semkey, 0, IPC_EXCL | 0666);      //!> 在“生产者”中已经创建了,所以此处只要引用就好,所以第二参数0    if( semid< 0 )    {       printf("/nCreate sem. ID error.../n" );       exit(EXIT_FAILURE );    }      //!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   //!> 初始化P V 操作       P.sem_num =0;    P.sem_op =-1;                  //!> 减去1操作    P.sem_flg =SEM_UNDO;       V.sem_num =0;    V.sem_op= 1;                  //!> 加上1操作    V.sem_flg =SEM_UNDO;     int i =0;    while( i< 10 )    {      //!> 先需要等待       P.sem_num =0;         //!> 注意开始我们知道这里面没有空间,                                  //!> 所以要等待server端的V操作,整体                                  //!> 看来我们知道,server P10次V10次,                                  //!> 此处就连续的读写就可以了       semop( semid, &P, 1 );              P.sem_num =2;               //!> 此处是互斥操作       semop( semid, &P, 1 );              printf("/n消费空间 addr_c[%d] = %c/n", i,addr_c[i]);                V.sem_num =2;               //!> 释放互斥区       semop( semid, &V, 1 );              V.sem_num =1;               //!> 那么 原来装有src信号量有了新的空间       semop( semid, &V, 1);         //!> 也就是 ++ 处理              i++;       sleep( 2 );    }    //!>>>>>>>>>>>>>>>>>>>>>>>>   //!>    下面释放链接       if( shmdt(addr_c ) == -1 )    {       printf("/n退出共享区失败/n" );       exit(EXIT_FAILURE );    }     exit(EXIT_FAILURE );}    摘自 shanshanpt的专栏