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

 

时间: 2024-08-07 10:49:41

Linux 共享内存 详解的相关文章

linux多线程编程详解教程

 这篇文章主要介绍了linux多线程编程详解教程,提供线程通过信号量实现通信的代码,大家参考使用吧 线程分类   线程按照其调度者可以分为用户级线程和核心级线程两种.   (1)用户级线程  用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持.在这里,操作系统往往会提供一个用户空间的线程库,该线程库提供了线程的创建.调度.撤销等功能,而内核仍然仅对进程进行管理.如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程

linux top命令详解(转)

linux top命令详解(转) top命令和ps命令的基本作用是相同的,显示系统当前的进程和其它状况:但是top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如? 前台执行该命令,它将独占前台,直到用户终止该程序为止. 比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最"敏感"的任务列表.该命令可以按CPU使用.内存使用.执行时间对任务进行排序:而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定.在后面的介绍中将把命令参数

Linux账户管理详解

当用户登陆Linux系统时,Linux将做如下检查: 1)在/etc/passwd文件里匹配输入的用户名,获取该用户名的UID和GID(其中GID和/etc/group关联) .Home目录和Shell设置 2)在/etc/shadow里核对该用户的密码 /etc/passwd文件结构 这个文件的每一行代表一个账号,如下所示: oracle:x:501:501::/home/oracle:/bin/bash 1. 用户名 2. 密码:早期的密码放在该字段,但如今的密码已单独放在/etc/shad

linux iostat命令详解和使用实例

 它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况.同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析.iostat属于sysstat软件包.可以用yum install sysstat 直接安装. 1.命令格式: iostat[参数][时间][次数] 2.命令功能:   通过iostat方便查看CPU.网卡.tty设备.磁盘.CD-ROM 等等设备的活动情况, 负载信息. 3.命令参数: -C 显示CPU使用情况 -d 显示磁

《嵌入式 Linux应用程序开发标准教程(第2版)》——2.2 Linux启动过程详解

2.2 Linux启动过程详解 嵌入式 Linux应用程序开发标准教程(第2版) 在了解了Linux的常见命令之后,下面详细讲解Linux的启动过程.Linux的启动过程包含了Linux工作原理的精髓,而且在嵌入式开发过程中非常需要这方面的知识. 2.2.1 概述 用户开机启动Linux过程如下: (1)当用户打开PC(intel CPU)的电源时,CPU将自动进入实模式,并从地址0xFFFF0000开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址.这时BIOS进行开机自检,并按BI

【Linux】Linux crontab 命令详解

原文来自:http://ir.hit.edu.cn/~wsong/development/crontab.htmlLinux crontab 命令详解 在 Linux 中,任务可以被配置在指定的时间段.指定的日期.或系统平均载量低于指定的数量时自动运行.红帽企业 Linux 预配置了对重要系统任务的运行,以便使系统能够时时被更新.譬如,被 locate 命令使用的 slocate 数据库每日都被更新.系统管理员可使用自动化的任务来执行定期备份.监控系统.运行定制脚本等等. 红帽企业 Linux

Linux管道命令详解

Linux的管道命令是'|',通过它可以对数据进行连续处理,其示意图如下: 注意: 1)管道命令仅为处理标准输出(即正确的输出),对于标准错误输出,将忽略 2)管理命令的后一个命令必须能将前一个命令的标准输出变为它的标准输入才可以,如 less,more,head,tail就可以,而ls, cp, mv就不行. 下面我们看几个管道命令. cut - 列选取命令 cut以行为单位,根据分隔符把行分成若干列,这样我们就可以指定选取哪些列了. cut -d '分隔字符' -f 选取的列数 echo $

linux时间函数详解

我们在编程中可能会经常用到时间,比如取得系统的时间(获取系统的年.月.日.时.分.秒,星期等 ),或者是隔一段时间去做某事,那么我们就用到一些时间函数. linux下存储时间常见的有两种存储 方式,一个是从1970年到现在经过了多少秒,一个是用一个结构来分别存储年月日时分秒的. time_t 这种类型就是用来存储从1970年到现在经过了多少秒,要想更精确一点,可以用结构struct timeval,它精确 到微妙. struct timeval { long tv_sec ; /*秒*/ lon

Linux 文件权限详解 含义和修改和安全

Linux文件权限详解 文件和目录权限概述 在linux中的每一个文件或目录都包含有访问权限,这些访问权限决定了谁能访问和如何访问这些文件和目录. 第一次接触Linux 的时候,对于文件权限方面的知识基本上是一窍不懂. 只知道文件打不开或执行不过去的时候,使用 sudo ,涉及到文件夹的时候,就使用 sudo chmod -R 777 /home/name/xxx 这样基本上都可以执行了,这样是可以使用,但是安全方面也就没有了保障,可以说就是定时炸弹,哈哈 首先介绍一下不同权限所代表的意思: 通