Linux 多线程编程( POSIX )( 六 )----->共享内存区

转自:http://blog.sina.com.cn/s/blog_6dc9e4cf0100xeut.html

1. 介绍 


    POSIX 共享内存和SYSTEMV的共享内存的框架是差不多的,细节有区别!
      在前面的SYSTEM V的共享内存区我们已经知道共享内存区是最快的IPC形式!
      例如在两个进程之间传输data,那么此方法将会是非常高效的!、
     
      共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址
      空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所
      有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所
      做的改动会立刻被有访问同一段共享内存的其他进程看到。
     
      但是我们知道同时可以有多个进程访问,那么要控制一段时间内只能有一个,所以
      为了同步,那么必须要用到前面的信号量,互斥量,条件量之类的可以同步的机
      制!
     
2.   mmap函数
      mmap函数把一个文件或一个POSIX共享内存区对象映射到调用进程的地址空间。使
      用该函数的目的:
      >: 1.使用普通文件以提供内存映射I/O
      >: 2.使用特殊文件以提供匿名内存映射。
      >: 3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区。
     
      #include<sys/mman.h>
      void * mmap(void * addr, size_t len, int prot, int flag, int filedes, off_t off)
      参数:
      addr          指向映射存储区的起始地址
      len                  映射的字节
      prot                对映射存储区的保护要求
      flag                flag标志位
      filedes            要被映射文件的描述符
      off                  要映射字节在文件中的起始偏移量

      若成功则返回映射区的起始地址,若出错则返回MAP_FAILED

      注意:
      >:      addr参数用于指定映射存储区的起始地址。通常将其设置为NULL,这表示由系
                统选择该映射区的起始地址。
      >:      prot参数说明对映射存储区的保护要求。可是PROT_READ(映射区可
                读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行)任意组合的按位
                或,也可以是PROT_NONE(映射区不可访问)。对指定映射存储区的保护要求不
                能超过文件open模式访问权限。
      >:      flag参数影响映射区的多种属性:     
            >:MAP_FIXED: 返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标
            志。
            >:MAP_SHARED: 这一标志说明了本进程对映射区所进行的存储操作的配置。
            此标志指定存储操作修改映射文件。
            >:MAP_PRIVATE: 本标志导致对映射区建立一个该映射文件的一个私有副本。所有
            后来对该映射区的引用都是引用该副本,而不是原始文件。
           
            要注意的是必须指定MAP_FIXED或MAP_PRIVATE标志其中的一个,指定前者是对存储
            映射文件本身的一个操作,而后者是对其副本进行操作。
     
      >:      filedes指要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文
                件。len是映射的字节数。

      >:      off是要映射字节在文件中的起始偏移量。通常将其设置为0。
     
      注意:mmap成功返回后,fd参数可以关闭。
     
3.   munmap函数
      删除一个映射关系
     
      #include<sys/mman.h>
      int munmap(caddr_t addr, size_t len );
      参数:
      addr                指向映射存储区的起始地址
      len                  映射的字节

      成功返回0,失败返回-1
     
      注意:
                  >:      addr是由mmap返回的地址,len是映射区大小,if已经被删除后再次访问这些
                            地址,那么会返回给进程一个SIGSEGV信号!
                  >:      同时注意,if flag是MAP_PRIVATE,那么对映射区所做的都无效,仅仅是对其
                        产生的一个副本进行的操作!
     
      对于映射文件和内存映射区来说,通常会有一个同步的需求!这就是说,如果我们修改
      了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地
      更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一
      致,于是调用msync来执行这种同步。
     
3.   文件与内存同步函数
      #include<sys/mman.h>     
      int msync(void * addr, size_t len, int flags );
      参数:
            addr                指向映射存储区的起始地址
            len                  映射的字节
            flags            修改方式标志
     
      成功返回0,失败返回-1;
     
      flags取值:
                        MS_ASYNC:异步写
                        MS_SYNC:同步写
                        MS_INVALIDATE:使高速缓存的数据实效
      注意:前两个必须指定且只能指定一个!!!
      区别:MS_ASYNC:一旦写操作由内核排入队列,立即返回!
                  MS_SYNC:等到写操作完成后才返回!
      if还指定了MS_INVALIDATE,那么最终拷贝不一致的文件数据的所有内存中拷贝都失效!
      后续的引用将从文件中取得~!
                       
4.   memcpy函数
      复制映射存储区
      #include<string.h>
      void *memcpy( void * dest, const void * src, size_t n );
      参数:
      dest            待复制的映射存储区
      src              复制后的映射存储区
                      待复制的映射存储区的大小
      返回dest的首地址
     
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.     
      下面讨论POSIX的共享内存函数
           
      1、指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个以存在的
            共享内存区对象。
      2、调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数
            随后由希望共享该内存区的任何其他进程使用。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>     
     
5.   打开或创建一个共享内存区
      #include<sys/mman.h>
      intshm_open( const char * name, int oflag, mode_t mode );
      参数:
                  name            共享区名称
                  oflag            标志位
                  mode            权限位
      成功返回ID,失败返回-1。
     
      oflag取值:      必须有O_RDONLY 或者 O_RDWR
                              还可以有O_CREAT,O_EXCL, O_TRUNC
      mode:只有指定O_CREAT标志时才有效
      shm_open返回整数描述符,随后可以用作mmap的第五个参数
     
6.   删除一个共享区

      #include<sys/mman.h>     
      intshm_unlink( const char * name );
      shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的
      open,msq_open或sem_open调用取得成功。
     
7.   ftruncate 和 fstat 函数
      普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。
     
      ftruncate:调整文件或共享内存区大小
      #include<unistd.h>
      intftruncate( int fd, off_t len );
      参数:
                  fd            描述符
                  len            修改的大小
      成功返回0,出错返回-1;
     
      当打开一个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息。
      //!> 其实所有的内存都是可以描述为文件的,只要是文件系统都是可以fstat获取信息的!
      #include<unistd.h>
      #include<sys/types.h>
      #include<sys/stat.h>
      int fstat(const char * filename, struct stat * buf );
      成功返回0,失败返回-1;
     
      但是要注意的是:
      对于普通的文件而言,fstat获得的结构体成员有12个,但是对于共享区而言,只有4个信息
      structstat
      {
            mode_t      st_mode;      //!> 模式
            uid_t            st_uid;            //!> 用户ID
            gid_t            st_gid;            //!> 全局ID
            off_t            st_size;      //!> 文件大小
      };
     
8.   共享内存区的写入和读出
注意:以下编译加上  -lrt
例如:gcc  -o server  server.c  -lrt

1.创建一个共享内存区的例子


#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>

int main( int argc, char ** argv )
{
 
    intshm_id;
     
      if( argc !=2 )
      {
            printf("请输入共享区名称!\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( shm_id= shm_open( argv[1], O_CREAT | O_RDWR, 0644 ) ) <= 0)
                                                                            //!> 创建共享区
            printf("创建共享区失败...\n");
            exit(EXIT_FAILURE );
      }

      printf("共享区ID == %d \n", shm_id );

      shm_unlink(argv[1] );            //!> 删除共享区

      return0;
}

2.使用mmap映射将一个文件中内容cp到一个新建的文件
  注意:必须有一个现有的 old 文件


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>

int main( int argc, char ** argv )
{
      int            fd_in,fd_out;            //!> 源文件和新建的文件 文件描述符
      void *src;                        //!> 源
      void *des;                        //!> 目的
      struct statstat_buf;            //!> 文件信息结构体
     
      if( argc !=3 )                        //!> 注意参数:2个文件名
      {
            printf("请输入两个文件名!\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( fd_in= open( argv[1], O_RDONLY ) ) < 0)            //!> 打开源文件
                                                                                              //!> 只读打开
            perror(argv[1] );
      }
      if( ( fd_out= open( argv[2], O_RDWR|O_CREAT|O_TRUNC ) ) < 0)
                                                                                              //!> 创建新文件
            perror(argv[2] );                                                            //!> 若文件存在,则长度被截为0,属性不变
         
     
      if( fstat(fd_in, &stat_buf ) < 0)            //!> 获取源文件信息
      {
            printf("获取源文件信息失败" );
            exit(EXIT_FAILURE );
      }
     
      if( lseek(fd_out, stat_buf.st_size - 1, SEEK_SET ) == -1 )
                                                                //!> 初始化输出映射存储区
            printf("lseek Error...\n");
            exit(EXIT_FAILURE );
      }
     
      if( write(fd_out, "1", sizeof( "1" )) == -1 )
      {
            printf("写入错误!\n");
            exit(EXIT_FAILURE );
      }

      if( ( src =mmap( 0, stat_buf.st_size, PROT_READ, MAP_SHARED, fd_in, 0 ) ) ==MAP_FAILED )
      {
            printf("源文件映射错误。。。");
            exit(EXIT_FAILURE );
      }
     
      if( ( des =mmap( 0, stat_buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED,fd_out, 0 ) ) == MAP_FAILED )
      {
            printf("目标文件映射错误。。。\n");
            exit(EXIT_FAILURE );
      }
     
      memcpy( des,src, stat_buf.st_size);            //!> 复制
      munmap( src,stat_buf.st_size);                        //!> 删除映射关系
      munmap( des,stat_buf.st_size );
     
      close( fd_in);
      close(fd_out );
     
      return0;
}

3.显示共享区信息

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>

int main( int argc, char ** argv )
{
      int            shm_id =-1;
      struct      statbuf;
     
      if( argc !=2 )
      {
            printf("请输入共享区名称!\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( shm_id= shm_open( argv[1], O_RDWR | O_CREAT, 0644 ) ) == -1 )
                                                                                  //!> 创建共享区
            printf("创建共享区失败....\n");
            exit(EXIT_FAILURE );
         

      if(ftruncate( shm_id, 100 ) != 0)                        //!> 修改共享区大小
      {
            printf("修改共享区大小失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if( fstat(shm_id, &buf ) != 0)                              //!> 获取文件(共享区)信息
      {
            printf("获取共享区信息失败...\n");
            exit(EXIT_FAILURE );
      }
     
      printf("uid_t: %d\n", buf.st_uid);            //!> 共享内存区所有者ID
      printf("git_t: %d\n", buf.st_gid);            //!> 共享内存区所有者组ID
      printf("size: %d\n", ( int )buf.st_size);      //!> 共享内存区大小
     
      return0;
}

4.共享内存区的写入和读出

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>

int main( int argc, char ** argv )
{
      int            shm_id =-1;
      char*      ptr =NULL;
      struct      stat            buf;
     
      if( argc !=2 )
      {
            printf("请输入共享区名称!\n");
            exit(EXIT_FAILURE );
      }

      if( ( shm_id= shm_open( argv[1], O_CREAT | O_RDWR, 0644 ) ) == -1 )
                                                                //!> 创建共享区
            printf("创建共享区失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if(ftruncate( shm_id, 100 ) != 0)      //!> 修改共享区大小
      {
            printf("修改共享区失败...\n" );
            exit(EXIT_FAILURE );
      }

      if( fstat(shm_id, &buf ) != 0 )
      {
            printf("获取共享区信息失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( ptr =mmap( NULL, buf.st_size, PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0 )) == MAP_FAILED )
      {
            printf("创建映射失败....\n");
            exit(EXIT_FAILURE );
      }
     
      //!> 注意,此后的ptr就是共享区首地址咯!
     
      strcpy( ptr,"Hello Linux memory map ...");      //!> 写入
      printf("输入共享区内容:%s\n", ptr);

      shm_unlink(argv[1] );      //!> 删除映射
     
      return0;
}

5.C-S模式实例

//!> cs.h


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>


//!> server.c

#include "cs.h"

int main( int argc, char ** argv )
{
      int                  shm_id =-1;
      sem_t*      sem;
      char        ptr;
     
      if( argc !=2 )
      {
            printf("请输入共享区名称!\n" );
            exit(EXIT_FAILURE );
      }
     
      if( ( shm_id= shm_open( argv[1], O_RDWR | O_CREAT, 0644 ) ) == -1 )
      {
            printf("创建共享区失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if(ftruncate( shm_id, 100 ) != 0 )
      {
            printf("修改内存区大小失败...\n");
            exit(EXIT_FAILURE );
      }

      if( ( sem =sem_open( argv[1], O_CREAT, 0644, 1 ) ) == SEM_FAILED )
                      //!> 注意此处创建的信号量在内核运行段都有效,只要不销毁
            printf("创建互斥信号量失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( ptr =mmap( NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0 ) )== MAP_FAILED )
      {
            printf("创建映射失败...\n");
            exit(EXIT_FAILURE);
      }
     
      strcpy( ptr,"\0" );
     
      //!> 我们分析可以知道:ptr是和共享区连到一起,那么此处其实
      //!> 就是接收client传过来的请求字符串,所以只要不是NULL和
      //!> q退出字符,那么就输出而已... ...
     
      while( 1)
      {
            if( (strcmp( ptr, "\0" ) ) == 0)                  //!> 内容为空则等待
            {
                  continue;
            }
            else
            {
                  if( (strcmp( ptr, "q\n" ) ) == 0)      //!> q + 回车退出
                  {
                        break;
                  }
                 
                  sem_wait(sem );            //!> 申请信号量
                 
                  printf("server: %s \n", ptr);
                  strcpy( ptr,"\0" );
                 
                  sem_post(sem );            //!> 释放信号量
            }
           
            sem_unlink(argv[1] );            //!> 删除信号量
            shm_unlink(argv[1] );            //!> 删除共享区
           
      }
     
      return0;
}



//!> client.c


#include "cs.h"

int main( int argc, char ** argv )
{
      int                  shm_id =-1;
      char*            ptr;
      sem_t*      sem;
     
      if( argc !=2 )
      {
            printf("请输入共享区名称!\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( shm_id= shm_open(argv[1], O_RDWR | O_CREAT, 0644 ) ) == -1)      //!> 打开共享区
      {
            printf("打开共享区失败...\n");
            exit(EXIT_FAILURE );
      }
     
      if( ( sem =sem_open( argv[1], 0 ) ) == SEM_FAILED)      //!> 打开信号量
      {
            printf("打开信号量失败...\n");
            exit(EXIT_FAILURE );
      }


      if( ( ptr =mmap( NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0 ) )== MAP_FAILED)                        //!> 链接到共享区
      {
            printf("连接映射失败...\n");
            exit(EXIT_FAILURE);
      }

      while( 1)
      {
            sem_wait(sem );            //!> 申请信号量
           
            printf("客户请求->");
            fgets( ptr,10, stdin );      //!> 从键盘读入
            printf("client: %s\n", ptr);
            if( strcmp(ptr, "q\n" ) == 0)      //!> 退出
            {
                  exit(EXIT_SUCCESS );
            }
           
            sem_post(sem );            //!> 释放
            sleep( 1);
      }

      return0;
}


      结果:
      客户请求->aaa
      client:aaa
     
      server:aaa
       
      客户请求->bbb
      client:bbb
     
      server:bbb
       
      客户请求->q
      client:q

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值