精华内容
下载资源
问答
  • 内存和磁盘的速度差异--内存快10万-100万倍 为什么这么慢? 因为内存是电子产品,其搜索速度和物料结构基本无关。而磁盘读取数据需要磁头移动和盘片旋转两种物理移动。内存搜索一次要几微秒,而磁盘要花费几毫秒。 ...

    以下内容转载自《大规模Web服务开发技术》

    大规模数据的难点在于无法在内存中计算,这是因为:

    无法再内存中计算的话,就必须搜索磁盘上的数据

    但是磁盘十分缓慢,I/O十分耗费时间


    内存和磁盘的速度差异--内存快10万-100万倍

    为什么这么慢?

    因为内存是电子产品,其搜索速度和物料结构基本无关。而磁盘读取数据需要磁头移动和盘片旋转两种物理移动。内存搜索一次要几微秒,而磁盘要花费几毫秒。


    传输速度和总线的速度差异

    接下来看看传输速度的差异吧。不论是内存还是磁盘,都用总线与CPU连接。不过这些总线也有速度差异啊,连接内存的和CPU的总线相当快,能达到7.5GB/秒。但磁盘只能达到58MB/秒。因此传输过程也要花费时间。数据量越大,磁盘和内存的速度差异就越明显,传输速度也是磁盘慢一些。



    展开全文
  • 点上方蓝字关注,一起愉快的写代码哦~前言:目前为止大多数游戏对于内存的需求是...正文部分:【带宽位宽时钟频率】电脑里各个元器件之间有大量的数据要进行交换处理,那么这些数据传输是要经过一定的通道的...

    点上方蓝字关注,一起愉快的写代码哦~

    4da2b247f8d95d8ad37fedc856773bc6.gif

    1b660c78130c03d03ab90807b9a644ee.gif前言:目前为止大多数游戏对于内存的需求是越来越高了,所以我在写单子的时候都尽可能的写8GX2这种组合,这时候就有很多人,而且几乎是全部的人,都会问我可不可以直接买单16G呢,所以我们今天来详细了解一下双8G内存和单16G内存的区别。b5bf628aec1a1904ac28ac669f3b9e58.gif 正文部分:【带宽和位宽和时钟频率】电脑里各个元器件之间有大量的数据要进行交换和处理,那么这些数据传输是要经过一定的通道的,这些通道就和我们城市间的马路一样,只不过上面走的不是汽车而是数据了。那么既然是电脑中的道路,那他和实际的道路一样,有自己的车道数量,有速度限制。所以这里就可以引出两个名词位宽和时钟频率了。而位宽这个名词你可以简单的理解为车道的数量而时钟频率你可以理解为车道的车辆限速。而带宽你就可以简单理解为这条道路的车流通行能力。那这里就可以引入一个公式:带宽=时钟频率x总线位数/8那么影响电脑里数据交换能力的主要关键因素就是:车道的数量(位宽),还有车道限速(电脑元器件时钟频率)【双通道与四通道内存】这里就可以解释双8G和单16G到底谁好了。如果你是就一条16G,你这时候打开一个软件,这个软件的数据就只会在这个内存里。而如果是双8G内存,那么你打开的这个软件会被平均分到两个内存里,那么CPU在读取这个软件数据的时候就是同时从两个内存获取数据,那么也就是车道的数量翻倍了,也就是位宽翻倍,而假设双8G内存和单16G内存频率是完全一样的,根据公式:带宽=时钟频率x总线位数/8,那么双8G内存的总带宽就是单16G的两倍,带宽代表数据交换的能力,那么就可以理解为双8G内存的数据读写速度是单16G的两倍。

    2b77d03ead336294c355e57e68e36982.png

    而很多人所追求的高频内存,一方面是因为频率高了,内存的响应速度快了,另一方面也是因为时钟频率的提高带来的带宽提升的收益。而频率提高的收益相对于双通道内存太小了,假设单通道2400内存的速度是15GB/S,那双通道2400的内存,速度也许可以到30GB/S,而单通道3000频率的内存速度可能也就是20GB/S。因此双通道的作用就比高频优先级要高一些,当然,最好就是双通道高频全都要,不过预算也就上去了。如果是2个槽的内存,那插满就是双通道了,那么很多人就说了,我的主板上有4个内存槽,那我插满4条是不是就可以4通道了?当然不是,CPU里用于和内存交互数据的东西叫内存控制器,而家用平台,比如Z系列主板,B系列主板,H系列主板,即便他有4个内存槽,因为内存控制器本身只能支持双通道内存,所以最高也就是双通道,4x8G的速度和2X16G是完全一样的,因为他们都是双通道。而只有X99,X299,X399这样的至尊平台,他们的主板上是有8个内存槽的,他们的CPU内存控制器都是四通道的,这些主板才有资格享用四通道内存(听说最近AMD好像搞出8通道的消费级平台了?)【不对称双通道】很多人也提到过一个情况,我的电脑本身有8G内存, 我买不起8G的,但是我想买个4G内存凑合一下,这样能不能组双通道呢?其实是可以的,这就是不对称双通道。

    b19a94bb5326c99d79b088b4fcd48215.png

    4+8这种组合,其中4+4的部分,也就是那条4G和8G的前半部分是双通道,而8G的后半部分还是单通道。那么这里还有个问题就是,系统会优先使用哪部分呢?根据我查询到的资料,windows会把这两部分当做等同来看待,也就是说,他并不会说优先把你的游戏往双通道里塞,很多的软件又尤其吃内存带宽,比如吃鸡就对内存带宽非常敏感,双通道内存会比单通道高10多帧,因此你不可能指望系统优先把你的游戏往双通道部分丢,这样就会导致实际虽然你组出来了双通道,但是可能性能提升并不明显。当然肯定也要比单8强很多,毕竟还有8G部分是双通的。那么如果是4条内存,比如2+4+2+4,那这里面有8G是双通道,4G是单通道,就是2条2G全是双通道,两条4G的前半部分的2G是双通道,后半部分2G是单通道,所以最后就是2+2+2+2是双通道,2(4*1/2)+2(4*1/2)是单通道。【磁盘阵列】这里又有聪明的小伙伴要说了,那既然内存可以双通道,那硬盘是不是也可以“双通道呢”当然可以,这就是磁盘阵列系统。在正常情况下,如果你有多个磁盘的话,这多个磁盘里的数据都是相互独立的,他们之间都是完全不相关的,这就是传统的JBOD磁盘系统。

    3cd98457f66f30629a33848a107cef90.png

    RAID0系统:这个系统很像磁盘双通道,一个软件的数据会被平均分散到两个甚至多个磁盘里,电脑在读取数据的时候,同时从两个或者多个磁盘读取数据,这样可以搭建和类似双通道一样的带宽翻倍的效果。而且不涉及到内存控制的问题,这里主要是磁盘控制器,只要你的磁盘数量足够多,那你就可以获得成倍的连续读写速度翻倍的效果。这里磁盘RAID0只能提高连续读写性能,随机读写性能甚至可能还会下降。虽然RAID0性能高,但是任意一个磁盘出现损坏后,所有的数据都是报废的,因为你光有另一半数据是没法知道这是个什么东西的。

    28555adaa7defe3e31db4f50a094b8bb.png

    RAID1系统:此系统常见于企业用户,两个磁盘的数据内容完全一样,也就是这个系统的作用就是数据备份,当然,你也可以挂2个硬盘,然后手动复制,但是磁盘RAID1后,这个数据备份就是全自动了,完全不需要你干预,如果其中某个磁盘损坏或者数据丢失,另外一个磁盘依旧可以正常找回数据。

    ca022ee634738e353b71d6d10bf072ef.png

    RAID2系统:RAID2 称为纠错海明码磁盘阵列,其设计思想是利用海明码实现数据校验冗余。海明码是一种在原始数据中加入若干校验码来进行错误检测和纠正的编码技术,其中第2n位( 1, 2, 4, 8, … )是校验码,其他位置是数据码。因此在 RAID2 中,数据按位存储,每块磁盘存储一位数据编码,磁盘数量取决于所设定的数据存储宽度,可由用户设定。图所示的为数据宽度为 4 的 RAID2 ,它需要 4 块数据磁盘和 3 块校验磁盘。如果是 64 位数据宽度,则需要 64 块 数据磁盘和 7 块校验磁盘。可见, RAID2 的数据宽度越大,存储空间利用率越高,但同时需要的磁盘数量也越多。

    8985f90fb7769f043a40e0e35c228b5e.png

    RAID3系统:RAID3系统非常像RAID0,他也会将数据打散到各个磁盘去来借此提高读取速度,但是和RAID3不一样的地方是他有一块磁盘里存取着校验数据,这样即便012里任意一块磁盘损坏也不会影响到整体的数据安全,向 RAID3 写入数据时,必须计算与所有同条带的校验值,并将新校验值写入校验盘中。一次写操作包含了写数据块、读取同条带的数据块、计算校验值、写入校验值等多个操作,系统开销非常大,性能较低。3805dd5c3d8753781f9558746cf2a8b3.pngRAID4系统:相对于RAID3来说,将校验的数据进行了压缩,采用块的方式来组织数据,写操作只涉及当前数据盘和校验盘两个盘,多个 I/O 请求可以同时得到处理,提高了系统性能。

    4fa5df438a4aa2b0486179eb5539072a.png

    RAID5系统:RAID5 应该是目前最常见的 RAID 等级,它的原理与 RAID4 相似,区别在于校验数据分布在阵列中的所有磁盘上,而没有采用专门的校验磁盘。对于数据和校验数据,它们的写操作可以同时发生在完全不同的磁盘上。因此, RAID5 不存在 RAID4 中的并发写操作时的校验盘性能瓶颈问题。另外, RAID5 还具备很好的扩展性。当阵列磁盘 数量增加时,并行操作量的能力也随之增长,可比 RAID4 支持更多的磁盘,从而拥有更高的容量以及更高的性能。

    622a4952792de899b0e9fd1ab654f283.png

    RAID6系统:前面所述的各个 RAID 等级都只能保护因单个磁盘失效而造成的数据丢失。如果两个磁盘同时发生故障,数据将无法恢复。RAID6引入双重校验的概念,它可以保护阵列中同时出现两个磁盘失效时,阵列仍能够继续工作,不会发生数据丢失。RAID6 等级是在 RAID5 的基础上为了进一步增强数据保护而设计的一种 RAID 方式,它可以看作是一种扩展的 RAID5 等级。

    c80ea2575aaa9d20f721ef291e7b5979.png

    那么常见的RAID系统就这些,RAID系统除了独立存在外还可以组合存在,比如先组个RAID0,然后再用两个RAID0系统再组合一个RAID1。下图就举例了几种RAID的组合,当然我们家用一般就是RAID0 RAID1 RAID5,就没有这么复杂了。

    b915b16d16deef7b7206ef924df32522.png

    5951cc8e100933f24e613fa53ca33c84.png


    你知道什么叫做DNS,什么叫DNS劫持吗?

    关于硬盘的那些事儿~

    雷电3,Type-c,兄弟两个谁更对

    主板问题知多少?

    关注哈哟87387ceeb53f4902463f6cc17f5c3374.png42d93329fac60220edbf9d5125ec5bca.png
    展开全文
  • 数据存储在磁盘(IO读写)--进化-->将数据存储在数据库中 磁盘: ①寻址--毫秒ms级别。 ②带宽--单位时间内能传输的字节流能有多少,几个G或几M。 ...内存: ...磁盘磁道扇区,一扇区51...

    将数据存储在磁盘(IO读写)--进化-->将数据存储在数据库中

    磁盘:

    ①寻址--毫秒ms级别的。

    ②带宽--单位时间内能传输的字节流能有多少,几个G或几M。

    内存:

    ①寻址--纳秒ns级别的。秒=1000毫秒=1000*1000微妙=1000*1000*1000纳秒。在寻址上,磁盘比内存慢了10万倍。

    ②带宽--很大

    I/O Buffer:

    磁盘中的磁道和扇区,一扇区512字节byte,如果磁盘容量很大,而扇区很小,势必会增大索引成本。

    操作系统无论从磁盘读取多少数据都是以4K为单位。

    随着文件变大,速度会越慢,磁盘IO会成为瓶颈。

    数据库:

    数据库的出现是为了改善磁盘IO的瓶颈。但整体而言,磁盘IO和数据库的IO总量是相等的,因此就有了索引的概念,如果没有索引,仅仅只是建了数据库和表,不会有太大帮助,依旧很慢。

    数据库中最小的单位为page页,以4k为单位。

    关系型数据库建表,必须先给出schema,数据类型(字节宽度),存数据时倾向于行级存储。先给出字节宽度的好处时,保留了位置,在插入或更新数据时直接进行覆写而不用进行数据移动。

    索引也是数据,和表数据一样都存储在硬盘中。在内存中创建一棵B+树用于将索引的区间和偏移存储起来,索引和数据存在磁盘,因为内存有限,存不下这么多的数据,利用索引提高遍历查找的速度,减少磁盘IO和寻址的过程,但数据还是从磁盘获取。

     

    展开全文
  • 1、mmap函数主要用途有三个(应用内核/驱动交互,进程间交互,大规模数据传输/大文件读写) 2 2、使用步骤:所有对mmap返回地址空间操作只是在内存中才有意义,只有在调用了munmap或者msync时,才把内存相应...

    目录

    一、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读写,提高了文件读取效率。

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

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

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

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

    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);

    3mmap使用细节(理清 文件(被映射对象)大小、文件物理页大小、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的字节部分用零填充。映射后的对应关系如下图所示:

                   https://images0.cnblogs.com/blog2015/571793/201507/200521495513717.png

    此时:

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

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

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

     

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

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

                     https://images0.cnblogs.com/blog2015/571793/201507/200522381763096.png

    此时:

    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 //使用指定的映射起始地址,如果由startlen参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

    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 //将映射区放在进程地址空间的低2GBMAP_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返回-1error返回标志和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等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

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

     

     

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

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

    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 mmappgd指向第一级页表的基址,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选项将内存中所做的修改反映到物理磁盘上。

    https://img.jbzj.com/file_images/article/201910/20191016154416751.jpg?2019916154424

    思路:

    //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_readmmap_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

    {

        char name[4];

        int  age;

    }people;

     

    int main(int argc, char** argv)

    {

        int fd,i;

        people *p_map;

        char name[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首先打开或创建一个文件,并把文件的长度设为5people结构大小.然后从mmap的返回地址开始,设置了10people结构.然后进程睡眠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 <mmap file>");

            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: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );

        }

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

        return 0;

    }

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

     

    文件被映射后,调用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  int fsync(int fd);

    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、其它常见问题

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

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

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

    多文件排队上传;

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

     

    展开全文
  • 前言许多Web应用程序提供大量静态内容,这相当于从磁盘读取数据并将完全相同的数据写回到响应套接字。此活动似乎只需要相对较少的CPU活动,但效率有点低下:内核从磁盘读取数据并将其跨越内核用户边界推送到应用程序...
  • 顺序读写,磁盘会预读,预读即在读取起始地址连续读取多个页面,主要时间花费在了传输时间。 2 内存映射、零拷贝 2.1 写加速 内存映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当时候)...
  • 许多Web应用程序提供大量的静态内容,这相当于从磁盘上读取数据并将完全相同的数据写回响应套接字。该活动似乎需要较少的CPU活动,但效率较低:内核从磁盘读取数据并将其跨内核用户边界推送到应用程序,然后应用程序...
  • 许多Web应用程序提供大量静态内容,这相当于从磁盘读取数据并将完全相同的数据写回到响应套接字。此活动似乎只需要相对较少的CPU活动,但效率有点低下:内核从磁盘读取数据并将其跨越内核用户边界推送到应用程序,...
  • NIO与通道数据传输

    2020-02-08 11:24:22
    NIO的通道通道( Channel) :由java.nio.channels包定义的。...应用程序与磁盘之间的数据写入或者读出,都需要由用户地址空间和内存地址空间之间来回复制数据,内存地址空间中的数据通过操作系统层面...
  • — 1、概述 许多Web应用程序提供大量静态内容,...每次数据穿越内核空间 - 用户空间边界时,都必须进行复制,这会消耗CPU周期和内存带宽。 幸运是,您可以通过一种称为足够零拷贝技术来消除这些副本。使用零拷贝
  • 一. 前言 Datanode最重要的功能之一就是读取数据块,如果高效的完成数据的读取是影响效率的关键. 二. 操作系统层面读取数据 ...Datanode对数据进行了两次多余的数据拷贝操作(步骤二步骤三) , Datano
  • Hadoop如何组织中间数据的存储和传输(源码级分析)1 解读了MapTask的整体执行流程,该文档将分析MapTask从内存缓冲区刷新到本地磁盘的过程。 MapTask环境设置:io.sort.mb = 200MB, io.sort.spill.percent=0.8. 1、...
  • 磁盘: 硬盘里面的结构如此,磁盘有很多个磁道,数据记录在磁道上。磁道会被划分为很多“块”,磁盘的读取是以“块”为...2传输内存。既然每次读是读一个块,那么我们可以考虑把相关的数据都放在一个块中,这样...
  • 随机访问存储器与磁介质存储器之间主要区别是:随机访问存储器是...从磁盘取出数据到内存供微处理器访问需要额外步骤,即需要微处理器执行一段小程序去访问磁盘驱动器,使磁盘驱动器把数据传输内存。随机访问...
  • 此文为摘抄胡凯翻译安卓官方文档。 一、Volley简介: ...通过标准 HTTP cache coherence(高速缓存一致性)缓存磁盘和内存透明响应。支持指定请求优先级。撤销请求 API。我们可以取消单
  • 1、EasyRecovery一款威力非常强大的硬盘数据恢复工具,能够恢复丢失的数据以及重建文件系统。主要体现在可以从被病毒破坏或是已经格式化的硬盘中恢复数据。EasyRecovery在使用过程中不会在原始的驱动器中写入任何...
  • 磁盘,提供持久的数据存储,不像内存,如果突然断电了,在内存的数据一般都会被丢弃,内存的数据在保存的时候,会被写到硬盘里面,磁盘也是一种I/O设备 在磁盘分区后,还要进行格式化,这个格式化操作就是制定...
  • 提起MySQL数据库在硬件方面优化无非是CPU、内存和IO。下面我们着重梳理一下关于磁盘I/O方面优化。1.磁盘冗余阵列RAIDRAID(Redundant Array of Inexpensive Disk)基本目的是把小型廉价硬盘合并成一块大容量...
  • 内存

    2019-04-29 15:58:49
    内存(RAM)(如图所示)是服务器中一个临时存储器,它只负责数据中转而不能永久保存。...特点:内存容量和处理速度直接决定了电脑数据传输的快慢。内存和CPU、硬盘一起并称为电脑三大件。 ...
  • day25-磁盘体系深入

    2019-04-29 16:43:33
    image.png image.png 你用过·什么配置的服务器?...2U或几路 内存 600G*8 raid级别 ...磁盘的常见接口含义及...硬盘接口是硬盘与主机系统间的连接部件,作用是在硬盘缓存主机内存之间传输数据。不同的硬盘接口决...
  • 序列化:将对象状态信息转换为可以存储或者传输的形式过程(1)· Serializeble是java序列化方式,Parcelable是Android特有序列化方式(2)· 在使用内存的时候,Parcelable比Serializeble性能高(3)· ...
  • 目前来说,内存与设备间的传输方式有2中,分别是PIODMA:PIO: 我们拿磁盘来说,很早以前,磁盘和内存之间的数据传输是需要CPU控制的,也就是说如果我们读取磁盘文件到内存中,数据要经过CPU存储转发,这种方式...
  • 磁盘数据的存取和传输内存的速度一直是计算机系统性能瓶颈。虽然现在磁盘转速总线速度已经有了很大提高,但磁盘容量大幅度增大又减缓了对其上数据的存取速度。本文介绍了嵌入式内存数据库技术发展现状、...
  • 磁盘管理

    2019-12-13 15:45:41
    你用过·什么配置服务器?...硬盘接口是硬盘与主机系统间连接部件,作用是在硬盘缓存主机内存之间传输数据。不同硬盘接口决定着硬盘与计算机之间连接速度。 磁盘接口分类 sata(笔记本,台式机,...
  • flume 是分布式,高可靠 ,高可用,...官网下载地址 http://flume.apache.org/download.html今天介绍以flume1.9.0版本为例系统需求: 1、java 1.8 or later 2、足够的内存 3、足够的磁盘空间 4、机器读写权限下载...
  • 扇区是硬件设备传送数据基本单位,而块是VFS文件系统传送数据...块设备每次数据传输操作都是作用于一组称为扇区相邻字节。大部分磁盘设备中,一个扇区大小是512字节。 块: 在Linux中,块大小必须四
  • 其实有些时候,下载速度太快了(windows系统下载update文件情况下有过100多MB/S速度),这种情况下,低端笔记本(比如我这个老家伙)运行内存和磁盘读写速度就会跟不上,系统就会强制停止网络数据的接收,看...
  • 摘要: 磁盘数据的存取和传输内存的速度一直是计算机系统性能瓶颈。虽然现在磁盘转速总线速度已经有了很大提高,但磁盘容量大幅度增大又减缓了对其上数据的存取速度。本文介绍了嵌入式内存数据库技术发展...
  • 文章目录B-树B*树B+树LSM树 B-树 在20世纪80年代,B-树被运用在数据库管理系统上,主要用于解决如何确保...增加树的节点大小以最小化搜索,并最大化每次读取时传输的数据量 将搜索时间减少到很少的次数,即使是对于大的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,047
精华内容 418
关键字:

内存和磁盘的数据传输