linux 映射内存(linux如何查看内存大小)
各位老铁们好,相信很多人对linux 映射内存都不是特别的了解,因此呢,今天就来为大家分享下关于linux 映射内存以及linux如何查看内存大小的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!
linux共享内存和mmap的区别
共享内存的创建
根据理论:
1.共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。
mmap的机制如:就是在磁盘上建立一个文件,每个进程存储器里面,单独开辟一个空间来进行映射。如果多进程的话,那么不会对实际的物理存储器(主存)消耗太大。
shm的机制:每个进程的共享内存都直接映射到实际物理存储器里面。
结论:
1、mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存)(这里一个问题,需要高手解答,会不会太多拷贝到主存里面???);缺点:进程间读取和写入速度要比主存的要慢。
2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)
使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用shm。
参看百度:
mmap就是一个文件操作
看这些百度的描述:
mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void*)-1],munmap返回-1。errno被设为以下的某个值 EACCES:访问出错EAGAIN:文件已被锁定,或者太多的内存已被锁定EBADF:fd不是有效的文件描述词EINVAL:一个或者多个参数无效 ENFILE:已达到系统对打开文件的限制ENODEV:指定文件所在的文件系统不支持内存映射ENOMEM:内存不足,或者进程已超出最大内存映射数量 EPERM:权能不足,操作不允许ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志SIGSEGV:试着向只读区写入 SIGBUS:试着访问不属于进程的内存区参数fd为即将映射到进程空间的文件描述字,
一般由open()返回,同时,fd可以指定为-1,此时须指定 flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)
相关文章参考:
mmap函数是unix/linux下的系统调用,来看《Unix Netword programming》卷二12.2节有详细介绍。
mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间,只是将文件映射到调用进程的地址空间里,然后你就可以用memcpy等操作写文件,而不用write()了.写完后用msync()同步一下,你所写的内容就保存到文件里了.不过这种方式没办法增加文件的长度,因为要映射的长度在调用mmap()的时候就决定了.
简单说就是把一个文件的内容在内存里面做一个映像,内存比磁盘快些。
基本上它是把一个档案对应到你的virtual memory中的一段,并传回一个指针。
重写总结:
1、mmap实际就是操作“文件”。
2、映射文件,除了主存的考虑外。shm的内存共享,效率应该比mmap效率要高(mmap通过io和文件操作,或“需要写完后用msync()同步一下”);当然mmap映射操作文件,比直接操作文件要快些;由于多了一步msync应该可以说比shm要慢了吧???
3、另一方面,mmap的优点是,操作比shm简单(没有调用比shm函数复杂),我想这也是许多人喜欢用的原因,包括nginx。
缺点,还得通过实际程序测试,确定!!!
修正理解(这也真是的,这个网站没办法附加;只能重写了):
今天又细心研究了一下,发现百度这么一段说明:
2、系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。
(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
看了一下windows“内存映射文件”:
内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。
这里再总结一次:
1、mmap有两种方式,一种是映射内存,它把普通文件映射为实际物理内存页,访问它就和访问物理内存一样(这也就和shm的功能一样了)(同时不用刷新到文件)
2、mmap可以映射文件,不确定会不会像windows“内存映射文件”一样的功能,如果是,那么他就能映射好几G甚至好几百G的内存数据,对大数据处理将提供强大功能了???
3、shm只做内存映射,和mmap第一个功能一样!只不过不是普通文件而已,但都是物理内存。
一文搞懂Linux内存映射实现(一)
下面介绍一下Linux内存映射的实现
一、基础概念
1、mmap文件映射
mmap是一种内存映射文件的方法,将一个文件映射到进程的地址空间,建立文件磁盘地址和进程虚拟地址的一种对应关系,如此进程通过读取相应的虚拟地址就可以直接读取相应文件中的内容。mmap是一种内存映射文件的方法,将一个文件映射到进程的地址空间,建立文件磁盘地址和进程虚拟地址的一种对应关系,如此进程通过读取相应的虚拟地址就可以直接读取相应文件中的内容。
这样映射的最大好处是进程可用直接访问内存,避免了频繁的使用read/write等文件系统的系统调用。需要注意的是mmap并不分配物理内存,它所做的最重要的工作就是为进程映射区的虚拟地址建立页表项
从图上可以看出进程的虚拟地址空间,是由多个虚拟内存区域构成的。如图所示的text数据段,初始数据段,bss数据段,堆,栈都是一个个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆和栈之间的空余部分。
2、进程的虚拟地址空间
Linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个虚拟内存区域功能和内部机制都不尽相同,因此一个进程会使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。即我们在上一页看到的text数据段,bss数据段等等。每个vm_area_struct都对应虚拟地址空间上一段连续的地址,它们之间使用链表或者树形结构链接,方便进程进行快速的查找/访问。
这里我们可用看到vm_area_struct结构中的一些字段,其中包括虚拟内存区的起始和结束的地址;vm_flags是该虚拟内存区的标志位。如果虚拟区域映射的是磁盘文件或者设备文件的话,那么vm_inode指向该文件的inode索引节点
这里有一个重要的vm_ops的字段,它是一个指向vm_operations_struct结构体的指针,在vm_operation_struct结构体中,定义了与该虚拟内存区操作相关的接口,其中包括了:open,close,fault等等这些操作。
每个虚拟内存区域都必须在vm_operations_struct结构中实现这些操作
一个进程的全部虚拟地址空间由mm_struct结构体来管理的,它里面包括了进程虚拟空间的一些管理的信息,包括进程pgd页表的地址等等,另外它还有一个指向,虚拟内存区链表的指针mmap。
最好,在进程描述符中有一个mm字段,指向mm_struct结构,这些共同组成了linux中进程虚拟地址空间的抽象描述
3、Linux中的字符设备驱动
最后是Linux中有关设备驱动的概念。
我们知道,所有的设备在linxu里面都是以设备文件的形式存在的,设备文件允许应用程序通过标准输入输出系统调用,与驱动程序进行交互,既然都是文件,当然也可以进行mmap映射,这是一种操作设备的方法。
有关设备和驱动的东西是一块很大的领域,此处以今天要使用的字符设备驱动为例,简单介绍它的基本内容。在用户程序看来,操作一个设备就是对设备文件的读写,而其具体的实现过程则是相应的驱动程序来完成的。
如上图示,方框中就是一个设备驱动的主要内容,它其中自然少不了对模块的加载和卸载函数,它们主要完成设备的初始化和删除的。它使用struct_cdev结构体来抽象描述一个字符设备,而每个cdev结构体就由一个dev_t类型的设备号来唯一指定,设备号分为主(major)设备号和次设备号,主设备号用来表明设备类型,次设备号用来表明其编号
这里可以看到由一个file_operations结构体,该结构体是linux文件系统中的一个非常重要的结构体,linux的VFS虚拟文件系统能够将不同类型的文件系统统一管理,并且为用户提供一个统一的接口,就是通过file_operations结构体实现的。
而在设备文件中,它主要用来存储驱动模块提供的对设备进行各种操作的函数,对于普通文件的read、write,驱动程序需要将其转化为对应的对设备的操作,就是通过该结构体(file_operations)完成的。它其中包括许多的钩子函数,包括read,release,mmap等等。read是进程在读设备文件时要做的,release则是在进程调用close时所要做的工作,它用来释放一些系统资源。最后是mmap,不同的文件有自己定义的mmap钩子,比如ext3文件系统对应了一个叫做generic_file_mmap的一个钩子函数
今天要做的主要工作,就是为一个虚拟字符设备编写其驱动模块,在其驱动中完成设备空间,即内核空间到用户空间的映射。
二、具体实现
进入源码,看内存映射具体的实现过程。驱动程序源码map_driver.c
完整源码如下:
驱动程序大概有三部分组成,1-模块的装载卸载; 2-file_operations结构体和mmap函数;3-vm_operations_struct结构体和fault函数。
首先是模块的装载函数,它所要完成的工作是两个,一是设备的注册,二是在内核中为设备申请一块内存。
设备的注册由register_chrdev这个函数来实现,这里需要指定设备的主设备号MAP_DEV_MAJOR,设备的名称MAP_DEV_NAME,还有它所链接的file_operations结构&mapdrvo_fops);
这里如果主设备号为零,该设备将自己分配一个主设备号,返回给result。如果返回值为0,表示分配成功;返回值为负表示设备注册失败。
接下来是申请内存,此处用的是vmalloc函数,vmalloc函数的特点是申请的内存区域在内核的线性地址是连续的,但物理地址不连续。
这里我们看到这里还有为申请到的页框的PageReserved标志位置位,这样做是告诉系统,该物理页框已经被我使用。
下面是模块的卸载函数。在模块的卸载函数中,要做的正相反。
首先是清理PageReserved标志位,接着是通过vfree释放我们申请的vmalloc线性区的线性地址,最后是通过unregister_chrdev注销掉这个设备。
接下来介绍的是file_operations结构体和mmap函数。如下为驱动程序中定义的file_operations结构体
驱动模块中只实现了三个功能,owner是用来指向该驱动module结构的指针
open函数在这里,我们用它来打印了调用该模块进程的pid
再就是mmap函数,具体分析过程在下面
include/linux/fs.h
内核源码中有关file_operations结构体的完整定义如下,设备的读取,写入,保持等等这些操作,都是由存储在file_operations结构体中的这些函数指针来处理的,这些函数指针所指向的函数都需要我们在驱动模块中将其实现,在这里我们可以看到file_operations结构体中提到了许多的函数指针,包括read,write,mmap,open等。但是我们可以不全使用它们。对于那些指向未实现函数的只是可以简单的设置为空。操作系统将负责实现该功能。
Linux将设备地址映射到用户空间内存映射与VMA
一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap()实现了这样的一个映射过程:它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程。
mmap()必须以PAGE_SIZE为单位进行映射,实际上,内存只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行页对齐,强行以PAGE_SIZE的倍数大小进行映射。
从file_operations文件操作结构体可以看出,驱动中mmap()函数的原型如下:
int(*mmap)(struct file*, struct vm_area_struct*);
驱动中的mmap()函数将在用户进行mmap()系统调用时最终被调用,mmap()系统调用的原型与file_operations中mmap()的原型区别很大,如下所示:
caddr_t mmap(caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
参数fd为文件描述符,一般由open()返回,fd也可以指定为-1,此时需指定flags参数中的MAP_ANON,表明进行的是匿名映射。
len是映射到调用用户空间的字节数,它从被映射文件开头offset个字节开始算起,offset参数一般设为0,表示从文件头开始映射。
prot参数指定访问权限,可取如下几个值的“或”:PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)。
参数addr指定文件应被映射到用户空间的起始地址,一般被指定为NULL,这样,选择起始地址的任务将由内核完成,而函数的返回值就是映射到用户空间的地址。其类型caddr_t实际上就是void*。
当用户调用mmap())的时候,内核会进行如下处理。
1)在进程的虚拟空间查找一块VMA。
2)将这块VMA进行映射。
3)如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。
4)将这个VMA插入进程的VMA链表中。
file_operations中mmap()函数的第一个参数就是步骤1)找到的VMA。
由mmap()系统调用映射的内存可由munmap()解除映射,这个函数的原型如下:
int munmap(caddr_t addr, size_t len);
驱动程序中mmap()的实现机制是建立页表,并填充VMA结构体中vm_operations_struct指针。