首页 > 其他分享 > mmap

mmap

时间:2023-02-12 17:33:42浏览次数:47  
标签:文件 映射 int mmap 内存 进程

mmap内存映射在应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写中的使用

目录

​一、Mmap用途、步骤实例、细节、及相关函数... 2​

​1、mmap函数主要用途有三个(应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写)     2​

​2、使用步骤:所有对mmap返回地址空间的操作只是在内存中才有意义,只有在调用了munmap或者msync时,才把内存中的相应内容写回磁盘文件. 3​

​3、mmap使用细节(理清 文件(被映射对象)大小、文件物理页大小、mmap映射区的长度length,映射区的可操作范围之间的关系和注意)... 3​

​4、mmap相关函数... 5​

​void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);   5​

​int munmap( void * addr, size_t len ). 7​

​int msync( void *addr, size_t len, int flags ). 7​

​二、Mmap基础概念... 8​

​三、mmap内存映射原理 (使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。)... 9​

​四、参考... 11​

​认真分析mmap:是什么 为什么 怎么用... 11​

​使用mmap实现大文件的复制(单进程和多进程)... 11​

​使用mmap实现多进程对大文件拷贝... 11​

​两个子程序,这两个子程序编译为mmap_read和mmap_write.两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信... 15​

​驱动和应用共享内存... 17​

​linux 同步IO: sync msync、fsync、fdatasync与 fflush内存和磁盘同步的操作和策略     21​

 

 

一、Mmap用途、步骤实例、细节、及相关函数

mmap操作提供了一种机制,让用户程序直接访问设备内存。

常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同,数据不通的繁琐过程。因此mmap效率更高。对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

 mmap_mmap

进程间通信(IPC)之内存映射mmap和共享内存shm

1共享内存shm是在内存中创建空间,然后每个进程映射到此处;内存映射mmap是创建一个文件,然后每个进程映射到此处;

2当机器重启时,mmap把文件保存在磁盘上,所以不会丢失,而共享内存shm存储在内存上就会丢失;

1、mmap函数主要用途有三个(应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写)

1)实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

2)提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件(将一个普通文件映射)或匿名映射(将特殊文件进行匿名内存映射)到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。

(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。

 同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。

(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。

3)可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。凡是需要用磁盘空间扩展内存的时候,mmap都可以发挥其功效。

提高大文件的读写效率,使用了内存映射的方法,将磁盘上的文件与进程中的进程虚拟空间进行了映射,减少一次内核空间到用户空间的一次复制。文件内存映射mmap解决大文件快速读写。

 

2、使用步骤:所有对mmap返回地址空间的操作只是在内存中才有意义,只有在调用了munmap或者msync时,才把内存中的相应内容写回磁盘文件.

* 用open系统调用打开文件, 并返回描述符fd.

* 用mmap建立内存映射, 并返回映射首地址指针start.

* 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).

*可以通过调用msync实现磁盘上文件内容与共享内存区的内容一致。

* 用munmap(void *start, size_t lenght)关闭内存映射.

* 用close系统调用关闭文件fd.

 

 

 //打开目标文件

 FILE * tfp = fopen(tname, "w"); //不存在则创建

 //计算源文件的大小(字节数)

 unsigned long byte_num = 1000;

 //--------建立 mmap 映射区 --------------

 //获取被复制文件的文件描述符

 int tfd = open(tname, O_RDWR|O_CREAT, 0644);

 ftruncate(tfd, byte_num); //将tfd指向的文件的大小改变为byte_num

 char *tmem = (char*)mmap(NULL, byte_num, PROT_WRITE, MAP_SHARED,tfd, 0);

 if (tmem == MAP_FAILED)

 perror("mmap err");

 close(tfd); //内存映射区建立之后,就可以关闭文件描述符

​//  memcpy​​​​将数据拷贝到​​tmem

msync( tmem, byte_num, MS_SYNC );

munmap(tmem, byte_num);

3、mmap使用细节(理清 文件(被映射对象)大小、文件物理页大小、mmap映射区的长度length,映射区的可操作范围之间的关系和注意)

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

start:映射区的开始地址 指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

length:映射区的长度。  映射区的长度    是映射到调用进程地址空间(虚拟内存)的字节数length。(超过文件的物理页大小部分进行操作会有问题,问题如果没有超过length,进程不能对其进行读写,会报SIGBUS错误。如果超过length,进程不能对其读写,会引发SIGSEGV错误。

prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。

由于文件大小(合法的物理页对应),文件的物理页大小是物理页大小(page_size)的整倍数,如果文件大小不是物理页大小(page_size)的整倍数,要以物理页大小(page_size)的整倍数为准,只是多出部分用零填充(填充部分:进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中)。

offset:被映射对象内容的起点。文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。要映射的用户空间的内存区域在内核空间中已经分配好的的内存区域中的偏移。大小为PAGE_SIZE的整数倍

 

 

 

1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。

2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。

3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

 

在上面的知识前提下,我们下面看看如果大小不是页的整倍数的具体情况:

情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。

分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。映射后的对应关系如下图所示:

               

 mmap_零拷贝_02

此时:

(1)读/写前5000个字节(0~4999),会返回操作文件内容。

(2)读字节5000~8191时,结果全为0。写5000~8191时,进程不会报错,但是所写的内容不会写入原文件中 。

(3)读/写8192以外的磁盘部分,会返回一个SIGSECV错误。

 

情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。

分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:

                 

 mmap_liunx_03

此时:

(1)进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。

(2)对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。

(3)对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。

(4)对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。

 

情形三:一个文件初始大小为0,使用mmap操作映射了1000*4K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。

分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。

但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。

这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。

 

4、mmap相关函数

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

返回说明

成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1], error被设为以下的某个值:

1 EACCES:访问出错

 2 EAGAIN:文件已被锁定,或者太多的内存已被锁定

 3 EBADF:fd不是有效的文件描述词

 4 EINVAL:一个或者多个参数无效

 5 ENFILE:已达到系统对打开文件的限制

 6 ENODEV:指定文件所在的文件系统不支持内存映射

 7 ENOMEM:内存不足,或者进程已超出最大内存映射数量

 8 EPERM:权能不足,操作不允许

 9 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志

10 SIGSEGV:试着向只读区写入

11 SIGBUS:试着访问不属于进程的内存区

参数

start:映射区的开始地址  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

length:映射区的长度    是映射到调用进程地址空间(虚拟内存)的字节数length。

prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

PROT_EXEC:页内容可以被执行

 PROT_READ:页内容可以被读取

 PROT_WRITE:页可以被写入

 PROT_NONE:页不可访问

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

MAP_DENYWRITE //这个标志被忽略。

MAP_EXECUTABLE //同上

MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。

MAP_FILE //兼容标志,被忽略。

MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1

offset:被映射对象内容的起点。文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。要映射的用户空间的内存区域在内核空间中已经分配好的的内存区域中的偏移。大小为PAGE_SIZE的整数倍

 

 

int munmap( void * addr, size_t len ) 

成功执行时,munmap()返回0。失败时,munmap返回-1,error返回标志和mmap一致;

该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小;

当映射关系解除后,对原来映射地址的访问将导致段错误发生。 

 

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

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。

可以通过调用msync实现磁盘上文件内容与共享内存区的内容一致。

 

该函数的作用就是将映射区的数据冲洗到磁盘。

addr:文件映射到进程空间的地址;

len:映射空间的大小;

flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC/ MS_INVALIDATE

其中:

取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成;

取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;

取MS_INVALIDATE(通知使用该共享区域的进程,数据已经改变)时,在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值;

返回值

成功则返回0;失败则返回-1;

可能的错误

EBUSY/ EINVAL/ ENOMEM

解除内存映射

二、Mmap基础概念

提高大文件的读写效率,使用了内存映射的方法,将磁盘上的文件与进程中的进程虚拟空间进行了映射,减少一次内核空间到用户空间的一次复制。

 

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

 mmap_mmap_04

由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

 

 

Linux通过下图的方式来组织虚拟内存。这里其他先不看,重点关注以下vm_area_struct。

linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域(text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射)。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问,如下图所示:

 mmap_零拷贝_05

在Linux内核,我们使用vm_area_struct结构来表示一个虚拟内存区域,一个具体的vm_area_struct包含以下字段:

  • vm_start:指向这个区域的起始处。
  • vm_end:指向这个区域的结束处。
  • vm_port:描述这个区域包含的所有页的读写权限
  • vm_flags:描述这个区域是否是私有的还是共享的
  • vm_next:指向链表中下一个区域结构。

为了解释清楚这里说一下上图中与vm_area_struct有关联的task_strcut 和 mm_strcut。

内核系统为每个进程维护一个单独的任务结构在内核源码中就是task_strcut ,该结构中的元素包含内核运行该进程所需要的所有信息,(如PID、执行用户栈的指针、程序计数器)。

任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。其中有两个字段是我们感兴趣的,pgd和mmap。pgd指向第一级页表的基址,mmap指向vm_area_struct的链表。

这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。具体步骤请看下一节。

 

三、mmap内存映射原理 (使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。)

常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

 

mmap内存映射的实现过程,总的来说可以分为三个阶段:

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

1、进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

 

(二)调用内核空间的系统调用函数mmap(不同于用户空间函数,实现文件物理地址和进程虚拟地址的一一映射关系

5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

 

(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步,这样所写的内容就能立即保存到文件里了。

 

 

 

四、参考

认真分析mmap:是什么 为什么 怎么用

​https://www.cnblogs.com/huxiao-tee/p/4660352.html#4008787​

 

使用mmap实现大文件的复制(单进程和多进程)

 ​​https://www.jb51.net/article/172100.htm​​ 

使用mmap实现多进程对大文件拷贝

​https://www.jb51.net/article/172108.htm​

 

 

假设有一个超大文件,需对其完成拷贝工作。为提高效率,可采用多进程并行拷贝的方法来实现。假设文件大小为len,共有n个进程对该文件进行拷贝。那每个进程拷贝的字节数应为len/n。但未必一定能整除,我们可以选择让最后一个进程负责剩余部分拷贝工作。可使用len % (len/n)将剩余部分大小求出。

为降低实现复杂度,可选用mmap来实现源、目标文件的映射,通过指针操作内存地址,设置每个进程拷贝的起始、结束位置。借助MAP_SHARED选项将内存中所做的修改反映到物理磁盘上。

 mmap_mmap_06

思路:

//1.指定创建子进程的个数
//2.打开源文件
//3.打开目的文件,不存在则创建
//4.获取文件大小
//5.根据文件大小拓展目标文件
//6.为源文件创建映射
//7.为目标文件创建映射
//8.求出每个子进程该拷贝的字节数
//9.创建N个子进程
//10.子进程完成分块拷贝(注意最后一个子进程拷贝起始位置)
//11.释放映射区

 

#include<stdio.h>

#include<stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <sys/mman.h>

#include<sys/wait.h>

#include<string.h>

#include <fcntl.h>

 

 

int main(int argc,char*argv[])

{

 int n;

 if(argc < 3 || argc > 4)

 {

 printf("Enter like this : ./a.out file_src file_dst [proc_number]\n");

 exit(1);

 }

 else if(argc == 3) //用户未指定,默认创建5个进程。

 n = 5;

 else

 n = atoi(argv[3]);

 

 //2.打开源文件

 int fd_src = open(argv[1],O_RDONLY);

 if(fd_src < 0)

 {

 perror("open");

 exit(2);

 }

 //3.打开目标文件,不存在就创建,存在则截断为0的大小。

 int fd_dst = open(argv[2],O_RDWR |O_CREAT |O_TRUNC,0664);

 if(fd_dst < 0)

 {

 perror("open");

 exit(3);

 }

 //4.获取源文件大小。

 struct stat sbuf;

 int ret = fstat(fd_src,&sbuf); //fd_src所指向的文件信息保存到结构体sbuf中。

 if(ret < 0)

 {

 perror("fstat");

 exit(4);

 }

 int flen = sbuf.st_size; //源文件大小。

 if(flen < n) //文件长度小于进程个数。

 {

 n = flen;

 }

 //5.根据文件大小拓展目标文件。

 ret = ftruncate(fd_dst,flen);//将参数fd指定的文件大小改为参数length指定的大小

 if(ret < 0)

 {

 perror("ftruncate");

 exit(5);

 }

 //6.为源文件创建映射。

// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

//addr == NULL,表示内核选择一个合适的地址创建一个length大小的共享内存,

 char *mp_src = (char*)mmap(NULL,flen,PROT_READ,MAP_SHARED,fd_src,0); //0,表示将fd_src所指向的文件从起始映射到共享内存中,共享内存的权限为只读,进程间共享。

 if(mp_src == MAP_FAILED) //mmap一定要检查返回值。

 {

 perror("mmap");

 exit(6);

 }

 close(fd_src);

 

 //7.为目标文件创建映射。

 char *mp_dst = (char*)mmap(NULL,flen,PROT_READ|PROT_WRITE,MAP_SHARED,fd_dst,0);

 if(mp_dst == MAP_FAILED) //mmap一定要检查返回值。

 {

 perror("mmap");

 exit(7);

 }

 close(fd_dst);

 

 //8.求出每个进程拷贝的字节数。

 int bs = flen / n;

 int mod = flen % bs; //求出均分后余下的字节数,让最后一个子进程处理。

 

 char *temp_src = mp_src;

 char *temp_dst = mp_dst;

 

 //9.创建n个子进程。

 int i ;

 pid_t pid;

 for(i = 0; i < n; ++i)

 {

 printf("create %dth proc\n",i);

 if( (pid =fork()) == 0 )

  break;

 }

 if(n == i) //父进程。

 {

 int j = 0;

 for(j = 0; j < n; ++j)

  wait(NULL);

 }

 else if(i == (n-1)) //10.子进程拷贝,最后一个子进程,它多处理均分后剩下的字节数。

 {

 printf("i = %d\n",i);

 memcpy(temp_dst+i*bs,temp_src+i*bs,bs+mod);

 }

 else if(i == 0)

 {

 printf("i = %d\n",i);

 memcpy(temp_dst,temp_src,bs);

 }

 else

 {

 printf("i = %d\n",i);

 memcpy(temp_dst+i*bs,temp_src+i*bs,bs);

 }

 

 //11.释放映射区。

 

 munmap(mp_src,flen);

 munmap(mp_dst,flen);

 

 return 0;

}

 

两个子程序,这两个子程序编译为mmap_read和mmap_write.两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信

​https://ce123.blog.csdn.net/article/details/11778399?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control​

 

.mmap_write试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,然后对映射后的地址空间进行写操作.mmap_read把命令行参数指定的文件映射到进程的地址空间,然后对映射的地址空间执行读操作.这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信.写操作操作子程序源码如下:

 

/**************************************************************************************/

/*简介:mmap_write共享内存,写操作子程序                        */

/*************************************************************************************/

#include<sys/mman.h>

#include<sys/types.h>

#include<fcntl.h>

#include<unistd.h>

#include<stdlib.h>

#include<string.h>

#include<stdio.h>

 

typedef struct

{

 charname[4];

 int  age;

}people;

 

int main(intargc,char** argv)

{

 intfd,i;

    people *p_map;

 charname[4];

 if(argc !=2)

    {

        perror("usage: mmap_write <mmap file>");

 return 1;

    }

 

    fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);

    lseek(fd,sizeof(people)*5-1,SEEK_SET);

    write(fd,"",1);

   

    p_map = (people*) mmap(NULL,sizeof(people)*10,\

        PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    close( fd );

    name[0] ='a';

    name[1] ='\0';

 for(i=0; i<10; i++)

    {

        name[0] ++;

 memcpy( ( *(p_map+i) ).name, &name,sizeof(name) );

        ( *(p_map+i) ).age =20+i;

    }

 printf(" initialize over \n ");

    sleep(10);

 

    munmap( p_map,sizeof(people)*10);

 printf("umap ok \n");

 return 0;

}

 

 

在上面的程序中,首先定义了一个people的数据格式(共享内存区的数据往往有固定的格式,由通信的各个进程决定,采用结构的方式具有普遍代表性).mmap_write首先打开或创建一个文件,并把文件的长度设为5个people结构大小.然后从mmap的返回地址开始,设置了10个people结构.然后进程睡眠10s,等待其他进程映射同一个文件,最后解除映射.读操作子程序的源代码如下:

/**************************************************************************************/

/*简介:mmap_read共享内存,读操作子程序                     */

/*************************************************************************************/

#include <sys/mman.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

 

typedef struct

{

    char name[4];

    int  age;

}people;

 

int main(int argc, char** argv)

{

    int fd,i;

    people*p_map;

    if (argc != 2)

    {

        perror("usage: mmap_read <mmapfile>");

        return 1;

    }

    fd=open( argv[1],O_CREAT|O_RDWR,00777 );

    p_map = (people*)mmap(NULL,sizeof(people)*10,\

        PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    close(fd);

    for(i = 0;i<10;i++)

    {

       printf( "name:%sage%d;\n",(*(p_map+i)).name, (*(p_map+i)).age);

    }

   munmap(p_map,sizeof(people)*10);

   return0;

}

mmap_read只是简单的映射一个文件,并以people数据结构的格式从mmap返回的地址处读取10个people结构,并输出读取的值,然后解除映射.下图是运行结果.

 

文件被映射后,调用mmap的进程对返回地址的访问其实是对某一内存区域的访问,暂时脱离了磁盘上文件的影响.所有对mmap返回地址空间的操作只是在内存中才有意义,只有在调用了munmap或或者msync时,才把内存中的相应内容写回磁盘文件.

驱动和应用共享内存

 首先在驱动程序分配一页大小的内存,然后用户进程通过mmap()将用户空间中大小也为一页的内存映射到内核空间这页内存上。映射完成后,驱动程序往这段内存写10个字节数据,用户进程将这些数据显示出来。

 

驱动程序:

 

#include <linux/miscdevice.h>

 #include <linux/delay.h>

 #include <linux/kernel.h>

 #include <linux/module.h>

 #include <linux/init.h>

 #include <linux/mm.h>

 #include <linux/fs.h>

 #include <linux/types.h>

 #include <linux/delay.h>

 #include <linux/moduleparam.h>

 #include <linux/slab.h>

 #include <linux/errno.h>

 #include <linux/ioctl.h>

 #include <linux/cdev.h>

 #include <linux/string.h>

 #include <linux/list.h>

 #include <linux/pci.h>

 #include <linux/gpio.h>

 

 

 #define DEVICE_NAME "mymap"

 

 

 static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9};

 static unsigned char *buffer;

 

 

 static int my_open(struct inode *inode, struct file *file)

 {

     return 0;

 }

 

 

 static int my_map(struct file *filp, struct vm_area_struct *vma)

 {   

     unsigned long page;

     unsigned char i;

     unsigned long start = (unsigned long)vma->vm_start;

     //unsigned long end =  (unsigned long)vma->vm_end;

     unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);

 

     //得到物理地址

     page = virt_to_phys(buffer);   

     //将用户空间的一个vma虚拟内存区映射到以page开始的一段连续物理页面上

     if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三个参数是页帧号,由物理地址右移PAGE_SHIFT得到

         return -1;

 

     //往该内存写10字节数据

     for(i=0;i<10;i++)

         buffer[i] = array[i];

    

     return 0;

 }

 

 

 static struct file_operations dev_fops = {

     .owner    = THIS_MODULE,

     .open    = my_open,

     .mmap   = my_map,

 };

 

 static struct miscdevice misc = {

     .minor = MISC_DYNAMIC_MINOR,

     .name = DEVICE_NAME,

     .fops = &dev_fops,

 };

 

 

 static int __init dev_init(void)

 {

     int ret;   

 

     //注册混杂设备

     ret = misc_register(&misc);

     //内存分配

     buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL);

     //将该段内存设置为保留

     SetPageReserved(virt_to_page(buffer));

 

     return ret;

 }

 

 

 static void __exit dev_exit(void)

 {

     //注销设备

     misc_deregister(&misc);

     //清除保留

     ClearPageReserved(virt_to_page(buffer));

     //释放内存

     kfree(buffer);

 }

 

 

 module_init(dev_init);

 module_exit(dev_exit);

 MODULE_LICENSE("GPL");

 MODULE_AUTHOR("LKN@SCUT");

 

 

 

 

 

 

 

 

 

应用程序

 

 #include <unistd.h>

 #include <stdio.h>

 #include <stdlib.h>

 #include <string.h>

 #include <fcntl.h>

 #include <linux/fb.h>

 #include <sys/mman.h>

 #include <sys/ioctl.h>

 

 #define PAGE_SIZE 4096

 

 

 int main(int argc , char *argv[])

 {

     int fd;

     int i;

     unsigned char *p_map;

    

     //打开设备

     fd = open("/dev/mymap",O_RDWR);

     if(fd < 0)

     {

         printf("open fail\n");

         exit(1);

     }

 

     //内存映射

     p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);

     if(p_map == MAP_FAILED)

     {

         printf("mmap fail\n");

         goto here;

     }

 

     //打印映射后的内存中的前10个字节内容

     for(i=0;i<10;i++)

         printf("%d\n",p_map[i]);

    

 

 here:

     munmap(p_map, PAGE_SIZE);

     return 0;

 }

 

 先加载驱动后执行应用程序,用户空间打印如下:

 

​linux同步IO: sync msync、fsync、fdatasync与 fflush​​内存和磁盘同步的操作和策略

​https://www.cnblogs.com/KevinT/p/3823281.html​

 

内存和磁盘同步的操作和策略。

 

当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。

 

1)nc函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。

通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。

 

2)fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。

对于提供事务支持的数据库,在事务提交时,都要确保事务日志(包含该事务所有的修改操作以及一个提交记录)完全写到硬盘上,才认定事务提交成功并返回给应用层。

write不够,需要fsync intfsync(intfd);

fsync的功能是确保文件fd所有已修改的内容已经正确同步到硬盘上,该调用会阻塞等待直到设备报告IO完成.

fsync的性能问题,与fdatasync   在同步上fsync是低效的

除了同步文件的修改内容(脏页),fsync还会同步文件的描述信息(metadata,包括size、访问时间st_atime & st_mtime等等),因为文件的数据和metadata通常存在硬盘的不同地方,因此fsync至少需要两次IO写操作,fsync的man page这样说:多余的一次IO操作,有多么昂贵呢?根据​​Wikipedia的数据​​,当前硬盘驱动的平均寻道时间(Average seek time)大约是3~15ms,7200RPM硬盘的平均旋转延迟(Average rotational latency)大约为4ms,因此一次IO操作的耗时大约为10ms左右。这个数字意味着什么?下文还会提到。

 

如果采用内存映射文件的方式进行文件IO(使用mmap,将文件的page cache直接映射到进程的地址空间,通过写内存的方式修改文件),也有类似的系统调用来确保修改的内容完全同步到硬盘之上:

int msync(void *addr, size_t length, int flags)

 

 

 

 

3)fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

使用fdatasync优化日志同步 int fdatasync(int fd);
数据库的日志文件是常常需要同步IO的。由于需要同步等待硬盘IO完成,所以事务的提交操作常常十分耗时,成为性能的瓶颈。

在Berkeley DB下,如果开启了AUTO_COMMIT(所有独立的写操作自动具有事务语义)并使用默认的同步级别(日志完全同步到硬盘才返回),写一条记录的耗时大约为5~10ms级别,基本和一次IO操作(10ms)的耗时相同。

我们已经知道,在同步上fsync是低效的。但是如果需要使用fdatasync减少对metadata的更新,则需要确保文件的尺寸在write前后没有发生变化。日志文件天生是追加型(append-only)的,总是在不断增大,似乎很难利用好fdatasync。

且看Berkeley DB是怎样处理日志文件的:

        1.每个log文件固定为10MB大小,从1开始编号,名称格式为“log.%010d"

        2.每次log文件创建时,先写文件的最后1个page,将log文件扩展为10MB大小

        3.向log文件中追加记录时,由于文件的尺寸不发生变化,使用fdatasync可以大大优化写log的效率

        4.如果一个log文件写满了,则新建一个log文件,也只有一次同步metadata的开销

 

4)fflush

 标准IO函数(如fread,fwrite等)会在内存中建立缓冲,该函数刷新内存缓冲,将内容写入内核缓冲,而要想将其真正写入磁盘,还需要调用fsync。(即先调用fflush然后再调用fsync,否则不会起作用)。fflush以指定的文件流描述符为参数(对应以fopen等函数打开的文件流),仅仅是把上层缓冲区中的数据刷新到内核缓冲区就返回,因此相对于fsync而言不是很安全,还需要再调用一下fsync来把数据真正写入硬盘。为了实现以上功能,需要把文件流描述符(fp)转换为文件描述符(fd),以方便fsync的调用,使用以下函数:int fileno(FILE *fp); <- in stdio.

 

 

HTTP上传大文件要考虑的问题


1、大文件上传服务器内存占用

如果请求不光是正常文本,还带着上传文件,则需要考虑Web容器限制当个请求的大小。如很多WEB容器默认一个请求最多分配20M的服务器内存,如果要在一次请求中上传文件则要根据限制上传文件大小调整这个配置。比如100M,这个时候如果有10个人同时上传100M文件,则就会占用1G内存。如果要上传更大文件,则要考虑分割文件分多次请求上传。


如果上传文件占用内存太大可能会导致服务器被拖死,所以部分大型网站用专用服务器来处理大文件上传。


2、文件大小限制


HTTP协议1.1版本中消息体长度字段Content-Length的类型规定为16个字节的Decimal类型【它能表示的最大值大到没朋友】,且没有对该长度做逻辑上限制。但如果程序里用Int类型表示文件大小字节数,由于4个字节的int类型最大能表示的you有符号整数位为2GB-1,无符号整数最大值为4GB,会导致程序能上传的最大文件为2GB或4GB。

3、其它常见问题

大文件传输,应该支持断点续传;

要有文件校验,校验不对的话能自动重传;

要考虑多线程分片上传,并发控制,带宽压力,限速上传;

多文件排队上传;

中转临时文件的删除机制;

标签:文件,映射,int,mmap,内存,进程
From: https://blog.51cto.com/u_12948550/6052109

相关文章

  • linux源码解析06–常用内存分配函数kmalloc、vmalloc、malloc和mmap实现原理
    1.kmalloc函数static__always_inlinevoid*kmalloc(size_tsize,gfp_tflags){ if(__builtin_constant_p(size)){#ifndefCONFIG_SLOB unsignedintindex;#end......
  • MT7621 openWrt插件操作GPIO(mmap)
    OpenWrt应用中操作GPIO的方式多数都是:修改设备树通过shell脚本export出对应的GPIO通过shell脚本或操作文件的方式控制GPIO/通过LEDELED配置控制GPIO不用这种方式的......
  • 虚拟内存跟物理内存之间的映射mmap\munmap
    #include<stdio.h>#include<sys/mman.h>intmain(void){/**创建虚拟内存的映射*void*mmap(void*__addr,size_t__len,int__prot,int__fla......
  • mmap
    CProgramming原文链接:https://linuxhint.com/using_mmap_function_linux/HowtousemmapfunctioninClanguage?3yearsagoby BamdebGhoshThe mmap() fun......
  • 修改文件时mmap如何处理
    拷贝二进制(elf)文件在拷贝二进制文件的时候,如果文件是一个可执行文件,并且有一个进程在运行这个可执行文件,那么拷贝的时候会出现"文本忙"(ETXTBSY)的错误提示,并且拷贝失败......
  • Linux的mmap文件内存映射机制
    在讲述文件映射的概念时,不可避免的要牵涉到虚存(SVR4的VM).实际上,文件映射是虚存的中心概念,文件映射一方面给用户提供了一组措施,好似用户将文件映射到自己地址空......
  • mmap详解
    p107内存映射,简而言之就是将内核空间的一段内存区域映射到用户空间。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反,内核空间对这段区域的修改也直接反映......
  • linux 内存管理(四)malloc--mmap--brk
    用strace分析malloc内存申请过程。长话短说,申请内存空间一般就两种方法,一种是malloc,另一种是mmap映射空间。 在使用malloc()分配内存的时候,可能系统调用brk(),也可能调用m......
  • mmap: Operation not permitted
    char*map_shm=mmap(NULL,size,PROT_READ,MAP_SHARED,fd,0);该种情况下会报mmap:Operationnotpermitted修改为char*map_shm=mmap(NULL,size,PROT_READ,......
  • 【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名
     ......