`
anzhsoft
  • 浏览: 23387 次
  • 性别: Icon_minigender_1
  • 来自: 天津
文章分类
社区版块
存档分类
最新评论

Linux 共享内存 详解

 
阅读更多


一、什么是共享内存区

共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率。

共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。

要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号灯。

在将共享内存前我们要先来介绍下面几个函数。

二、mmap

mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数有三个目的:

1.使用普通文件以提供内存映射I/O

2.使用特殊文件以提供匿名内存映射。

3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区。

名称::

mmap

功能:

把I/O文件映射到一个存储区域中

头文件:

#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,这表示由系统选择该映射区的起始地址。

filedes指要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。

off是要映射字节在文件中的起始偏移量。通常将其设置为0。

prot参数说明对映射存储区的保护要求。可将prot参数指定为PROT_NONE,或者是PROT_READ(映射区可读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行)任意组合的按位或,也可以是PROT_NONE(映射区不可访问)。对指定映射存储区的保护要求不能超过文件open模式访问权限。

flag参数影响映射区的多种属性:

MAP_FIXED返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标志。

MAP_SHARED这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件。

MAP_PRIVATE本标志导致对映射区建立一个该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。

要注意的是必须指定MAP_FIXED或MAP_PRIVATE标志其中的一个,指定前者是对存储映射文件本身的一个操作,而后者是对其副本进行操作。

mmap成功返回后,fd参数可以关闭。该操作对于由mmap建立的映射关系没有影响。为从某个进程的地址空间删除一个映射关系,我们调用munmap.

名称::

munmap

功能:

解除存储映射

头文件:

#include <sys/mman.h>

函数原形:

int munmap(caddr_t addr,size_t len);

参数:

addr 指向映射存储区的起始地址

len 映射的字节

返回值:

若成功则返回0,若出错则返回-1

其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址导致向调用进程产生一个SIGSEGV信号。

如果被映射区是使用MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都被丢弃掉。

内核的虚存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步(前提它是MAP_SHARED内存区)。这就是说,如果我们修改了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一致,于是调用msync来执行这种同步。

名称::

msync

功能:

同步文件到存储器

头文件:

#include <sys/mman.h>

函数原形:

int msync(void *addr,size_t len,int flags);

参数:

addr 指向映射存储区的起始地址

len 映射的字节

prot flags

返回值:

若成功则返回0,若出错则返回-1

其中addr和len参数通常指代内存中的整个内存映射区,不过也可以指定该内存区的一个子集。flags参数为MS_ASYNC(执行异步写),MS_SYNC(执行同步写),MS_INVALIDATE(使高速缓存的数据实效)。其中MS_ASYNC和MS_SYNC这两个常值中必须指定一个,但不能都指定。它们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果还指定了MS_INVALIDATE,那么与其最终拷贝不一致的文件数据的所有内存中拷贝都失效。后续的引用将从文件取得数据。

名称::

memcpy

功能:

复制映射存储区

头文件:

#include <string.h>

函数原形:

void *memcpy(void *dest,const void *src,size_t n);

参数:

dest 待复制的映射存储区

src 复制后的映射存储区

n 待复制的映射存储区的大小

返回值:

返回dest的首地址

memcpy拷贝n个字节从dest到src。

下面就是利用mmap函数影射I/O实现的cp命令。

#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
 
int main(int argc,char *argv[])
{
int fdin,fdout;
void *arc,dst;
struct stat statbuf;
 
if(argc!=3)
{
    printf(“please input two file!\n”);
    exit(1);
}
if((fdin=open(argv[1],O_RDONLY))<0) /*打开原文件*/
    perror(argv[1]);
if((fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC))<0)/*创建并打开目标文件*/
    perror(argv[2]);
 
if(fstat(fdin,&statbuf)<0) /*获得文件大小信息*/
    printf(“fstat error”);
 
if(lseek(fdout,statbuf.st_size-1,SEEK_SET)==-1)/*初始化输出映射存储区*/
    printf(“lseek error”);
if(write(fdout,”1”)!=1)
    printf(“write error”);
 
if((src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)
    /*映射原文件到输入的映射存储区*/
    printf(“mmap error);
if((dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0)) ==MAP_FAILED) /*映射目标文件到输出的映射存储区*/
    printf(“mmap error);
memcpy(dst,src,statbuf.st_size);/*复制映射存储区*/
munmap(src,statbuf.st_size); /*解除输入映射*/
munmap(dst,statbuf.st_size); /*解除输出映射*/
close(fdin);
close(fdout);
}

下面是运行结果:

#cc –o mycp mycp.c

#./mycp test1 test2

三、posix共享内存函数

posix共享内存区涉及两个步骤:

1、指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个以存在的共享内存区对象。

2、调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数随后由希望共享该内存区的任何其他进程使用。

名称::

shm_open

功能:

打开或创建一个共享内存区

头文件:

#include <sys/mman.h>

函数原形:

int shm_open(const char *name,int oflag,mode_t mode);

参数:

name 共享内存区的名字

cflag 标志位

mode 权限位

返回值:

成功返回0,出错返回-1

oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.

mode参数指定权限位,它指定O_CREAT标志的前提下使用。

shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。

名称::

shm_unlink

功能:

删除一个共享内存区

头文件:

#include <sys/mman.h>

函数原形:

int shm_unlink(const char *name);

参数:

name 共享内存区的名字

返回值:

成功返回0,出错返回-1

shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。

下面是创建一个共享内存区的例子:

#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
 
int main(int argc,char **argv)
{
int shm_id;
 
if(argc!=2)
{
    printf(“usage:shm_open <pathname>\n”);
    exit(1);
}
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);
printf(“shmid:%d\n”,shm_id);
shm_unlink(argv[1]);
}

下面是运行结果,注意编译程序我们要加上“-lrt”参数。

#cc –lrt –o shm_open shm_open.c

#./shm_open test

shm_id:3


四、ftruncate和fstat函数

普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。

名称::

ftruncate

功能:

调整文件或共享内存区大小

头文件:

#include <unistd.h>

函数原形:

int ftruncate(int fd,off_t length);

参数:

fd 描述符

length 大小

返回值:

成功返回0,出错返回-1

当打开一个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息。

名称::

fstat

功能:

获得文件或共享内存区的信息

头文件:

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

函数原形:

int stat(const char *file_name,struct stat *buf);

参数:

file_name 文件名

buf stat结构

返回值:

成功返回0,出错返回-1

对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。

struct stat{

mode_t st_mode;

uid_t st_uid;

gid_t st_gid;

off_t st_size;

};

#include <unistd.h>
#include <sys/type.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>
 
int main(int argc,char **argv)
{
    int shm_id;
struct stat buf;
 
if(argc!=2)
{
    printf(“usage:shm_open <pathname>\n”);
    exit(1);
}
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存*/
ftruncate(shm_id,100);/*修改共享内存的打开*/
fstat(shm_id,&buf); /*把共享内存的信息记录到buf中*/
printf(“uid_t:%d\n”,buf.st_uid); /*共享内存区所有者ID*/
printf(“git_t:%d\n”,buf.st_gid); /*共享内存区所有者组ID*/
printf(“size :%d\n”,buf.st_size); /*共享内存区大小*/
}

下面是运行结果:

#cc –lrt –o shm_show shm_show.c

#./shm_show test

uid_t:0

git_t:0

size:100

五、共享内存区的写入和读出

上面我们介绍了mmap函数,下面我们就可以通过这些函数,把进程映射到共享内存区。

然后我们就可以通过共享内存区进行进程间通信了。

下面是共享内存区写入的例子:

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
 
int main(int argc,char **argv)
{
int shm_id;
struct stat buf;
char *ptr;
 
if(argc!=2)
{
    printf(“usage:shm_open <pathname>\n”);
    exit(1);
}
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
ftruncate(shm_id,100);/*修改共享区大小*/
fstat(shm_id,&buf);
ptr=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
strcpy(ptr,”hello linux”);/*写入共享内存区*/
printf(“%s\n”,ptr);/*读出共享内存区*/
shm_unlink(argv[1]);/*删除共享内存区*/
}

下面是运行结果:

#cc –lrt –o shm_write shm_write.c

#./shm_write test

hello linux

六、程序例子

下面是利用pisix共享内存区实现进程间通信的例子:服务器进程读出共享内存区内容,然后清空。客户进程向共享内存区写入数据。直到用户输入“q”程序结束。程序用posix信号量实现互斥。

/*server.c服务器程序*/
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
 
int main(int argc,char **argv)
{
int shm_id;
char *ptr;
sem_t *sem;
 
if(argc!=2)
{
    printf(“usage:shm_open <pathname>\n”);
    exit(1);
}
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
ftruncate(shm_id,100);/*调整共享内存区大小*/
sem=sem_open(argv[1],O_CREAD,0644,1);/*创建信号量*/
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
strcpy(ptr,”\0”);
while(1)
{
    if((strcmp(ptr,”\0”))==0)/*如果为空,则等待*/
        continue;
        else
        {
             if((strcmp(ptr,”q\n”))==0)/*如果内存为q\n退出循环*/
                 break;
             sem_wait(sem);/*申请信号量*/
             printf(“server:%s”,ptr);/*输入共享内存区内容*/
             strcpy(ptr,”\0”);/*清空共享内存区*/
             sem_pose(sem);/*释放信号量*/
         }
         sem_unlink(argv[1]);/*删除信号量*/
         shm_unlink(argv[1]);/*删除共享内存区*/
     }
}

客户端程序:

/*user.c 客户端程序*/
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdio.h>
 
int main(int argc,char **argv)
{
int shm_id;
char *ptr;
sem_t *sem;
 
if(argc!=2)
{
    printf(“usage:shm_open <pathname>\n”);
    exit(1);
}
shm_id=shm_open(argv[1],0);/*打开共享内存区
sem=sem_open(argv[1],0);/*打开信号量*/
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
while(1)
{
         sem_wait(sem);/*申请信号量*/
         fgets(ptr,10,stdin);/*从键盘读入数据到共享内存区*/
         printf(“user:%s”,ptr);
         if((strcmp(ptr,”q\n”))==0)
             exit(0);
         sem_pose(sem);/*释放信号量*/
         sleep(1);
     }
     exit(0);
}

#cc –lrt –o server server.c

#cc –lrt –o user user.c

#./server test&

#./user test

输入:abc

user:abc

server:abc

输入:123

user:123

server:123

输入:q

user:q

分享到:
评论

相关推荐

    linux 共享内存详解

    linux下面的共享内存详解,详细介绍了共享内存的使用和药注意的事项

    linux共享内存的参考代码

    linux的信息传输方式之一,共享内存,希望对初学者有用

    Linux共享内存实现机制的详解

    Linux共享内存实现机制的详解 内存共享: 两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一...

    Linux_进程间通信 共享内存shmget方式详解

    Linux_进程间通信_-_共享内存shmget方式

    详解Linux进程间通信——使用共享内存

    共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。这篇文章主要介绍了详解Linux进程间通信——使用共享内存,有兴趣的可以了解一下。

    Python进程间通信之共享内存详解

    前一篇博客说了怎样通过命名管道实现进程间通信,但是要在windows是使用命名管道,需要使用python调研windows api,太麻烦,于是想到是不是可以通过共享内存的方式来实现。查了一下,Python中可以使用mmap模块来实现...

    Linux下的进程间通信 详解

    linux进程间通信详解,包括管道及有名管道,消息队列,共享内存等

    嵌入式Linux应用程序开发详解

    267 8.5.2 消息队列实现 268 8.6 实验内容 272 8.6.1 管道通信实验 272 8.6.2 共享内存实验 275 本章小结 277 思考与练习 278 第9章 多线程编程 279 9.1 Linux下线程概述 279 9.1.1 线程概述 279 ...

    LINUX系统开发技术详解---基于ARM

    ║2 嵌入式系统开发技术详解——基于ARM 3.1 Linux 常用工具.............................................................................................................. 28 3.1.1 Shell简介..................

    华清远见嵌入式linux应用程序开发技术详解下载(内部资料).rar

     8.4 共享内存   8.5 消息队列  8.6 实验内容   本章小结   思考与练习 第9章 多线程编程   9.1 Linux下线程概述   9.2 Linux线程实现   9.3 实验内容——“生产者消费者”实验   本章小结 ...

    linux设备驱动程序第三版

    1. Linux 设备驱动第三版 .................................................................................................................... 5 2. 第 1 章 设备驱动简介 ....................................

    IPC库:在Linux/Windows上使用共享内存的高性能进程间通信。- mutouyun / cpp-ipc

    IPC库:在Linux/Windows上使用共享内存的高性能进程间通信。- mutouyun / cpp-ipc-源码

    Linux进程间通信-详解 (经典)

    详细讲解了管道、信号、 消息队列、 信号灯 、共享内存、 套接口。

    Linux C 多线程编程之互斥锁与条件变量实例详解

    对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy. 原型: int pthread_mutex_init(pthread_mutex_t *restrict mutex, ...

    嵌入式Linux应用程序开发标准教程(第2版全)

    8.5.2 共享内存的应用 8.6 消息队列 8.6.1 消息队列概述 8.6.2 消息队列的应用 8.7 实验内容 8.7.1 管道通信实验 8.7.2 共享内存实验 8.8 本章小结 8.9 思考与练习 第9章 多线程编程 9.1 Linux线程概述 9.1.1 线程...

    Linux free命令用法详解

    free指令会显示内存的使用情况,包括实体内存,虚拟的交换文件内存,共享内存区段,以及系统核心使用的缓冲区等。 语法free [-bkmotV][-s ] 参数说明: -b 以Byte为单位显示内存使用情况。 -k 以KB为单位显示内存...

    linux中各种锁机制的使用与区别详解

    例如,进程间通信的机制之一:共享内存(在这里不做详解):多个进程可同时访问同一块内存。如果不对访问这块内存的临界区进行互斥或者同步,那么进程的运行很可能出现一些不可预知的错误和结果。 接下来我们了解三...

Global site tag (gtag.js) - Google Analytics