Linux共享内存实例及文件映射编程及实现原理.docx

上传人:b****6 文档编号:7920853 上传时间:2023-01-27 格式:DOCX 页数:14 大小:27.51KB
下载 相关 举报
Linux共享内存实例及文件映射编程及实现原理.docx_第1页
第1页 / 共14页
Linux共享内存实例及文件映射编程及实现原理.docx_第2页
第2页 / 共14页
Linux共享内存实例及文件映射编程及实现原理.docx_第3页
第3页 / 共14页
Linux共享内存实例及文件映射编程及实现原理.docx_第4页
第4页 / 共14页
Linux共享内存实例及文件映射编程及实现原理.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

Linux共享内存实例及文件映射编程及实现原理.docx

《Linux共享内存实例及文件映射编程及实现原理.docx》由会员分享,可在线阅读,更多相关《Linux共享内存实例及文件映射编程及实现原理.docx(14页珍藏版)》请在冰豆网上搜索。

Linux共享内存实例及文件映射编程及实现原理.docx

Linux共享内存实例及文件映射编程及实现原理

Linux共享内存实例及文件映射编程及实现原理.txt“我羡慕内些老人羡慕他们手牵手一直走到最后。

━交话费的时候,才发现自己的话那么值钱。

目录

(一)IPC共享内存和文件映射的区别1

(二)共享内存实现流程总结1

(三)存储映射I/O(包含实现原理说明)2

文件映射API补充4

(四)IPC共享存储(包含实现原理说明)6

(五)共享内存实现基本原理10

(六)IPC共享内存实现机制11

(七)文件映射的实现机制13

(一)IPC共享内存和文件映射的区别

1.文件映射的页框是磁盘文件高速缓存中的页框,内核线程pdflush会将页框中的内容回写进磁盘,如果是私有映射类型,将会进行写时复制。

而IPC共享内存映射的是一种特殊文件系统中的文件高速缓存,它没有相应的磁盘映像。

2.IPC共享内存只存在于内存中,系统重新启动,数据将会丢失。

而文件共享映射会将数据写回磁盘。

3.IPC共享内存的大小是在创建的时候指定,而且大小不能改变,而文件在创建时大小为0,此时还不能建立映射,文件的大小会间接的决定映射区的大小。

例如文件的大小是123,而要求映射的区域大小是4096*2,但实际只会分配4096的映射空间,此时引用4096以后的线性空间将引起缺页异常。

4.当第一次读取共享内存时IPC共享内存对象将分配一个新的页框,而文件映射分配新页框的同时会将磁盘中的数据写入新页框。

5.IPC共享内存不需要写回磁盘操作,完全是为共享内存而设计,所以使用效率会更高。

6.IPC共享内存对象必须调用shmctl()显示的撤销,否则会一直保留着,使用key或者id号定位一个共享内存对象,key和id号的对应关系并不是固定的。

例如,第一次使用key建立一个共享内存对象为shm1对应的id为id1,之后系统重新启动,然后再使用key建立一个共享内存对象shm2,对应的id是id2,此时id2和id1是不同的。

而文件映射使用相同的路径将会定位相同的磁盘文件。

总结:

IPC共享内存和文件映射的实现机制是一样的,文件映射的目的是加快对文件的读写速度,而IPC共享内存就是为了共享内存而设计的,所以效率会高一些。

(二)共享内存实现流程总结

1.建立一个线性区对象structvm_area_struct并加入进程的内存描述符current->mm中。

函数mmap()和shmat()就是用于建立并注册线性区对象,这个对象中的structfile*vm_file指向映射文件的文件对象,vm_page_prot是线性区中页框的访问许可权。

但此时并未修改进程的页表,而是注册相应的缺页异常回调函数,注册在对象的vm_ops。

2.当进程第一次访问共享内存区时,由于相应的页表还未填写,将产生缺页异常,并根据线性地址找到对应的线性区对象,然后调用前边注册过的缺页异常回调函数,并根据vm_file文件对象和vm_page_prot的信息来填写相应的页表项,最后重新执行产生缺页异常的代码。

说明:

文件映射和IPC共享内存映射的物理页框都是磁盘文件的页高速缓存中的,IPC共享内存使用一种特殊文件系统,这个文件系统并没有对应的磁盘映像,只是复用了文件系统的框架。

更详细的内容参见后边的五,六,七节。

下面3,4节是《UNIX环境高级编程》对文件映射和IPC共享内存的讲解,已经说明的很详细了,我在它的基础上附加了一些内核实现原理的说明,实现原理说明部分放在括号内。

(三)存储映射I/O(包含实现原理说明)

存储映射I/O使一个磁盘文件与存储空间中的一个缓存相映射。

于是当从缓存中取数据,就相当于读文件中的相应字节。

与其类似,将数据存入缓存,则相应字节就自动地写入文件。

这样,就可以在不使用read和write的情况下执行I/O。

为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。

这是由mmap函数实现的。

#include

#include

void*mmap(voidaddr,size_tlen,intprot,intflag,intfd,off_toff);

返回:

若成功则为映射区的起始地址,若出错则为-1

addr参数用于指定映射存储区的起始地址。

通常将其设置为0,这表示由系统选择该映射区的起始地址。

此函数的返回地址是:

该映射区的起始地址。

fd指定要被映射文件的描述符(fd用于定位是哪个磁盘文件的页高速缓存)。

在映射该文件到一个地址空间之前,先要打开该文件。

len是映射的字节数。

off是要映射字节在文件中的起始位移量(下面将说明对off值有某些限制)。

在说明其余参数之前,先看一下存储映射文件的基本情况。

图12-12显示了一个存储映射文件。

在此图中,“起始地址”是mmap的返回值。

在图中,映射存储区位于堆和栈之间:

这属于实现

细节,各种实现之间可能不同。

prot参数说明映射存储区的保护要求。

见表12-8。

对于映射存储区所指定的保护要求与文件的open方法匹配。

例如,若该文件是只读打开的,那么对映射存储区就不能指定PROT_WRITE。

(对存储映射区的保护是通过设置页表项的保护标志来实现的,如果页表项的read/write标志位为0,说明页是只读的,如果进程试图修改页的内容,将产生段错误,这些保护方案都是由CPU硬件控制的)

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

?

MAP_FIXED返回值必须等于addr。

因为这不利于可移植性,所以不鼓励使用此标志。

如果未指定此标志,而且addr非0,则内核只把addr视为何处设置映射区的一种建议。

通过将addr指定为0可获得最大可移植性。

?

MAP_SHARED这一标志说明了本进程对映射区所进行的存储操作的配置。

此标志指定存储操作修改映射文件—也就是,存储操作相当于对该文件write。

(这里映射的页是包含在文件的页高速缓存中,用户态进程在读写磁盘的时候,内核先在页高速缓存中增加一个新页,将所请求的磁盘块写入新页,用户态进程从页高速缓存中取出数据。

如果要写入数据,也是要添加一个页将磁盘中的数据写入该页,然后再将数据写入该页,内核会在一定的时机对磁盘进行更新。

(以上的页高速缓存是组织在inode的i_mmaping对象中,对于一个磁盘文件唯一对应一个磁盘inode,每个磁盘inode也唯一对应一个内核inode,也就是,每个磁盘文件只有一个页高速缓存,如果两个进程映射的是同一个文件的页高数缓存,则它们共享相同的物理页)

?

MAP_PRIVATE本标志说明,对映射区的存储操作导致创建该映射文件的一个副本。

所有后来对该映射区的存访都是存访该副本,而不是原始文件。

(这里内核用到了写时复制技术,在相应的页表项中设置写时复制标志,当进程试图修改该页,内核将会产生缺页异常,内核就把该页框进行复制,并在进程页表中用复制的页来替换原来的页框,显然这个新的页框已经不在页高速缓存中了,对页框的内容进行修改将不会写回文件,其它进程将无法共享这个页框。

如果本进程还未进行写复制,而其它进程修改了页的内容,本进程是可以获得更新后的数据)

因为映射文件的起动位移量受系统虚存页长度的限制,那么如果映射区的长度不是页长度的整数倍时,将如何呢?

假定文件长12字节,系统页长为512字节,则系统通常提供512字节的映射区,其中后500字节被设0。

可以修改这50字节,但任何变动都不会在文件中反映出来。

(这是由于内核分配线性区和分配物理内存都是以页为单位)

与映射存储区相关有两个信号:

SIGSEGV和SIGBUS。

信号SIGSEGV通常用于指示进程试图存取它不能存取的存储区。

如果进程企图存数据到用mmap指定为只读的映射存储区,那么也产生此信号。

如果存取映射区的某个部分,而在存取时这一部分已不存在,则产生SIGBUS信号。

例如,用文件长度映射一个文件,但在存访该映射区之前,另一个进程已将该文件截短。

此时,如果进程企图存取对应于该文件尾端部分的映射区,则接收到SIGBUS信号。

(对信号的实现机制有待进一步分析)

在fork之后,子进程继承存储映射区(因为子进程复制父进程地址空间,而存储映射区是该地址空间中的一部分),但是由于同样的理由,exec后的新程序则不继承此存储映射区。

(关闭文件描述符也不影响存储映射区,磁盘文件的页高速缓存并不会因为进程的撤销而撤销,如果有足够的空闲内存,页高速缓存中的页将长期存在,使其它进程再使用该页时不再访问磁盘。

进程终止时,或调用了munmap之后,存储映射区就被自动去除。

关闭文件描述符fd并不解除映射区。

(关闭存储映射区,只是撤销进程页表中的相应目录项,并不影响页高速缓存。

#include

#include

intmunmap(voidaddr,size_tlen);

返回:

若成功则为0,若出错则为-1

munmap并不影响被映射的对象—也就是说,调用munmap并不使映射区的内容写到磁盘文件上。

对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。

(pdflush内核线程用于刷新脏页)

例子程序:

#include

#include

#include

#include

#include

#include

intmain(intargc,char*argv[])

{

intfd,i,counter;

pid_tpid;

char*area=NULL;

if((fd=open("test",O_RDWR))<=0)

printf("openerror\n");

area=(char*)mmap(0,sizeof(long),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

printf("area:

%p\n",area);

close(fd);

*(area+1)='c';

}

文件映射API补充

msync函数的使用原型:

?

?

#include?

?

intmsync(constvoid*start,size_tlength,intflags);msync函数用来把映像的文件写入磁盘。

调用msync可以用对内存中的映像的更新写入一个被映像的文件,被强行写入到磁盘的内存取从start指定的地址开始,写入length个字节的数据。

flags可以是下面的一个值或多个的逻辑“或”:

?

?

1、MS_ASYNC调度一次写入操作然后返回?

?

2、MS_SYNC在msync返回前写入数据?

?

3、MS_INVALIDATE让映像到同一文件的映像无效,以便用新数据更新它们(MS_INVALIDATE的作用是使映射的页高速缓存中的内容无效,重新从磁盘写入数据到映射的页高速缓存。

可以使用MS_INVALIDATE来测试内核是否进行页高速缓存数据的回写磁盘操作,测试过程:

写一个字符到映射区,然后使用MS_INVALIDATE使映射区的数据失效,并从磁盘写入数据,从测试结果看字符会被写入磁盘,也就是说内核几乎在对映射区进行写入操作的同时就进行了回写磁盘操作)

mprotect函数的使用mprotect函数修改在内存映像上的保护模式。

函数原型:

?

?

#include?

?

#include?

?

intmprotect(constvoid*start,size_tlen,intprot);mprotect把自start开始的内存区的保护模式修改为prot指定的值,如果执行成功返回0,如果执行失败,mprotect返回-1,并且设置errno变量。

port可以是PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)、PROT_NONE(不可访问)中的一个或多个(这里只是修改本进程页表中的访问控制标志,并不涉及物理页,对其它进程没有影响)

锁定内存原型:

?

?

?

#includeintmlock(constvoid*start,size_tlen);?

?

?

intmunlock(void*start,size_tlen);?

?

?

intmlockall(intflags);?

?

?

intmunlockall(void);以4上个函数是对指定的内存映像加锁和解锁,其中mlockall的flags包括MCL_CURRENT和MCL_FUTURE。

只有root权限才能使用它们。

start指出被加锁或解锁的内存区,len指出加锁或解锁的内存区大小。

flags的值可以是MCL_CURRENT和MCL_FUTURE之一或者两个都有。

MCL_CURRENT在调用返回前请求锁住所有内存页面,MCL_FUTURE指出锁住所有增加到进程的地址空间的内存页面。

(mlock的操作结果是vma的属性,VM_LOCKED.作用是被lock的内存不参加swap,保证一直存在于内存中.内核中具体的策略执行可以看函数swap_out_vma.当使用exec的时候,lock失效.fork的子进程也不继承此属性.

对于实时进程和安全程序,此调用很有意义.对于加密程序,密码不会被dump到磁盘上.但是lock并不能阻止系统休眠的时候内存被存储到磁盘.

mlock,munlock的操作不可堆叠.多次调用mlock的一段内存也会被一次unlock操作解锁.

mlock/munlock指定的地址会被rounddown到一个page的边界.)

mremap函数的使用mremap函数用于改变一个被映像的文件大小。

原型:

?

?

#include?

?

void*mremap(void*old_addr,size_told_len,size_tnew_len,unsignedlongflags);mremap用指定的flags把地址在old_addr的内存映像大小从old_len调整为new_len,flags如果为MREMAP_MAYMOVE则调整此内存映像的地址。

成功返回新地址,失败返回NULL。

(内核要做事情是:

改变线性区对象的长度。

内核会检查是否可以直接扩大或者缩小线性区的大小,如果线性对象相邻的线性空间已经被使用了,此时将没法扩大了,如果此时设置了MREMAP_MAYMOVE标志,将会重新分配一块新的线性空间,显然这个空间的起始地址已经改变)

(四)IPC共享存储(包含实现原理说明)

IPC共享存储允许两个或多个进程共享一给定的存储区。

因为数据不需要在客户机和服务器之间复制,所以这是最快的一种IPC。

使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。

若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。

通常,信号量被用来实现对共享存储存取的同步。

调用的第一个函数通常是shmget,它获得一个共享存储标识符。

#include

#include

intshmget(key_tkey,size_tsize,intshmflg);

如果参数key为IPC_PRIVATE。

则会建立新的共享内存对象其大小由size(单位字节Byte)指定,如果key不为IPC_PRIVATE,并且存在键值为key的共享内存对象,则返回所关联的id号,如果不存在键值为key的共享内存对象,那么系统会视参数shmflg是否有IPC_CREAT来决定是否新建一个共享内存对象。

(每个共享内存对象都对应一个目录对象和一个inode对象,每个inode对象都包含一个address_spacei_mapping对象,但是这些对象并没有磁盘映像,而是为了可以重复利用文件映射中提供的代码,每个进程映射的物理页就存储在i_mapping对象里。

(以上是一种特殊的文件系统,它并没有挂载在某个目录下,只是为了方便实现共享内存)

size是要建立共享内存的长度。

所有的内存分配操作都是以页为单位的。

实际的大小是((bytes进位到4096整数倍)/4096+4)*4096。

(因为线性区和物理内存的分配都是以页为单位)

shmflg主要和一些标志有关,其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT和O_EXCL相当。

IPC_CREAT如果共享内存不存在,则创建一个共享内存,否则打开操作。

IPC_EXCL只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。

如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。

如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。

IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。

返回值

成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。

EINVAL参数size小于SHMMIN或大于SHMMAX。

EEXIST预建立key所致的共享内存,但已经存在。

EIDRM参数key所致的共享内存已经删除。

ENOSPC超过了系统允许建立的共享内存的最大值(SHMALL)。

ENOENT参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。

EACCES没有权限。

ENOMEM核心内存不足。

structshmid_ds

shmid_ds数据结构表示每个新建的共享内存。

当shmget()创建了一块新的共享内存后,返回一个可以用于引用该共享内存的shmid_ds数据结构的标识符。

include/linux/shm.h

structshmid_ds{

structipc_permshm_perm;/*operationperms*/

intshm_segsz;/*共享内存的大小*/

__kernel_time_tshm_atime;/*最后一次附加这个共享内存的时间*/

__kernel_time_tshm_dtime;/*最后一次分离这个共享内存的时间*/

__kernel_time_tshm_ctime;/*最后一次改变这个共享内存结构的时间*/

__kernel_ipc_pid_tshm_cpid;/*建立这个共享内存的进程识别码*/

__kernel_ipc_pid_tshm_lpid;/*最后一个操作共享内存的进程识别码*/

unsignedshortshm_nattch;/*附加这个共享内存的进程个数*/

unsignedshortshm_unused;/*compatibility*/

void*shm_unused2;/*ditto-usedbyDIPC*/

void*shm_unused3;/*unused*/

};

structipc_perm

对于每个IPC对象,系统共用一个structipc_perm的数据结构来存放权限信息,以确定一个ipc操作是否可以访问该IPC对象。

structipc_perm{

__kernel_key_tkey;//共享内存对象的key

__kernel_uid_tuid;//共享内存所属的用户识别码(可以修改)

__kernel_gid_tgid;//共享内存所属的组识别码(可以修改)

__kernel_uid_tcuid;//建立共享内对象的用户识别码

__kernel_gid_tcgid;//建立共享内对象的组识别码

__kernel_mode_tmode;//这个共享内存的读写权限(可以修改)

unsignedshortseq;//序号

};

shmctl函数对共享存储段执行多种操作。

#include

#include

#include

intshmctl(intshmid,intcmd,structshmid_ds*buf);

返回:

若成功则为0,若出错则为-1

cmd参数指定下列5种命令中一种,使其在shmid指定的段上执行。

?

IPC_STAT对此段取shmid_ds结构,并存放在由buf指向的结构中。

?

IPC_SET按buf指向的结构中的值设置与此段相关结构中的下列三个字段:

(只能修改这3个字段)shm_perm.uid、shm_perm.gid以及shm_perm.mode。

此命令只能由下列两种进程执行:

一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。

?

IPC_RMID从系统中删除该共享存储段。

因为每个共享存储段有一个连接计数(shm_nattch在shmid_ds结构中),所以除非使用该段的最后一个进程终止或与该段脱接,否则不会实际上删除该存储段。

不管此段是否仍在使用,该段标识符立即被删除,所以不能再用shmat与该段连接。

此命令只能由下列两种进程执行:

一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。

?

SHM_LOCK锁住共享存储段。

此命令只能由超级用户执行。

?

SHM_UNLOCK解锁共享存储段。

此命令只能由超级用户执行。

一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中。

#include

#include

#include

void*shmat(intshmid,void*addr,intflag);

返回:

若成功则为指向共享存储段的指针,若出错则为-1。

共享存储段连接到调用进程的哪个地址上与addr参数以及在flag中是否指定SHM_RND位有关。

(1)如果addr为0,则此段连接到由内核选择的第一个可用地址上。

(2)如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。

(3)如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addrmodSHMLBA))所表示的地址上。

SHM_RND命令的意思是:

取整。

SHMLBA的意思是:

低边界地址倍数,它总是2的乘方。

该算式是将地址向下取最近1个SHMLBA的倍数。

除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。

所以一般应指定addr为0,以便由内核选择地址。

如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。

否则以读写方式连接此段。

(在进程页

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 工学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1