精华内容
下载资源
问答
  • 2021-01-12 13:42:12

    关于DMZ主机和端口映射的问题?

    DMZ和端口映射函数不应关联。当路由器开启DMZ功能时,所有数据包将直接转发到DMZ主机。此时,DMZ实际上完全暴露在Internet上,可以看作是Internet上的一个主机

    端口映射(虚拟服务器)只是单个外部端口和单个内部主机端口之间的映射,这实际上是在一个静态项中创建的NAT转换表,任何符合NAT表项的外部包直接转发到内部主机。

    所以你还是要仔细检查一下,看看你的其他操作是否有问题,不要打开DMZ功能就可以使用端口映射了。

    dmz主机和端口映射?

    端口映射和DMZ提供内部和外部网络映射,具体如下:

    DMZ:相当于DNAT(destination NAT),只寻址目的IP地址。所有的数据包都是由你自己的IP转发到目的地的。NAT在转发过程中应用于目的地。

    端口映射:特定数据包的DNAT。这与传统的SNAT类似。要检测5个元组(源IP、目标IP、协议类型、源端口、目标端口),需要对目标IP而不是源IP进行NAT。

    UPnP是即插即用协议,不提供内部和外部映射。也就是说,它可以自动识别网络中的设备。一个典型的应用是USB,可以通过插入它来找到它。

    更多相关内容
  • 作为EtherCAT协议栈的补充,实现PDO动态映射,是一个完整的EtherCAT从站必要的组成部分。
  • iOS之深入解析文件内存映射MMAP

    万次阅读 热门讨论 2021-08-31 19:33:16
    ④ mmap 内存映射原理 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域: 进程在用户空间调用库函数 mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset...

    一、常规文件操作

    • 常规文件操作(read/write)有以下重要步骤:
      • 进程发起读文件请求;
      • 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的 inode;
      • inode 在 address_space 上查找要请求的文件页是否已经缓存在内核页高速缓冲中。如果存在,则直接返回这片文件页的内容;
      • 如果不存在,则通过 inode 定位到文件磁盘地址,将数据从磁盘复制到内核页高速缓冲,之后再次发起读页面过程,进而将内核页高速缓冲中的数据发给用户进程。
    • 常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。由于页缓存处在内核空间,不能被用户进程直接寻址,所以需要将页缓存中数据页再次拷贝到内存对应的用户空间中;
    • read/write 是系统调用很耗时,如下图,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,实际上完成了两次数据拷贝;

    在这里插入图片描述

    • 如果两个进程都对磁盘中的一个文件内容进行访问,那么这个内容在物理内存中有三份:进程 A 的地址空间 + 进程 B 的地址空间 + 内核页高速缓冲空间;
    • 写操作也是一样,待写入的 buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

    二、mmap 内存映射

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

    在这里插入图片描述

    • 由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS 数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。
    • 在日常开发中偶尔会遇到 mmap,它最常用到的场景是 MMKV,其次用到的是日志打印。
    • 进程是 App 运行的基本单位,进程之间相对独立。iOS 系统中 App 运行的内存空间地址是虚拟空间地址,存储数据是在各自的沙盒。当在 App 中去读写沙盒中的文件时,会使用 NSFileManager 去查找文件,然后可以使用 NSData 去加载二进制数据。文件操作的更底层实现过程,是使用 linux 的 read()、write() 函数直接操作文件句柄(也叫文件描述符 fd)。
    • 在操作系统层面,当 App 读取一个文件时,实际是有两步:
      • 将文件从磁盘读取到物理内存;
      • 从系统空间拷贝到用户空间(可以认为是复制到系统给 App 统一分配的内存)。
    • iOS 系统使用页缓存机制,通过 MMU(Memory Management Unit)将虚拟内存地址和物理地址进行映射,并且由于进程的地址空间和系统的地址空间不一样,所以还需要多一次拷贝。
    • 而 mmap 将磁盘上文件的地址信息与进程用的虚拟逻辑地址进行映射,建立映射的过程与普通的内存读取不同:正常的是将文件拷贝到内存,mmap 只是建立映射而不会将文件加载到内存中。

    在这里插入图片描述

    • 在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程由系统调用 mmap() 实现,所以建立内存映射的效率很高。
    • 既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程。
    • mmap() 会返回一个指针 ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用 read 或 write 对文件进行读写,而只需要通过 ptr 就能够操作文件。但是 ptr 所指向的是一个逻辑地址,要操作其中的数据,必须通过 MMU 将逻辑地址转换成物理地址,如上图中过程 2 所示。这个过程与内存映射无关。
    • 前面说道,建立内存映射并没有实际拷贝数据,这时,MMU 在地址映射表中是无法找到与 ptr 相对应的物理地址的,也就是 MMU 失败,将产生一个缺页中断,缺页中断的中断响应函数会在 swap 中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过 mmap() 建立的映射关系,从硬盘上将文件读取到物理内存中,如上图中过程 3 所示。这个过程与内存映射无关。
    • 如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如图1中过程4所示。这个过程也与内存映射无关。
    • mmap 内存映射的实现过程,总的来说可以分为三个阶段:
      • 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域;
      • 调用内核空间的系统调用函数 mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系;
      • 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。
    ② 适用场景
    • 有一个很大的文件,因为映射有额外的性能消耗,所以适用于频繁读操作的场景;(单次使用的场景不建议使用)。
    • 有一个小文件,它的内容您想要立即读入内存并经常访问。这种技术最适合那些大小不超过几个虚拟内存页的文件(页是地址空间的最小单位,虚拟页和物理页的大小是一样的,通常为 4KB)。
    • 需要在内存中缓存文件的特定部分。文件映射消除了缓存数据的需要,这使得系统磁盘缓存中的其他数据空间更大。
    • 当随机访问一个非常大的文件时,通常最好只映射文件的一小部分。映射大文件的问题是文件会消耗活动内存。如果文件足够大,系统可能会被迫将其他部分的内存分页以加载文件。将多个文件映射到内存中会使这个问题更加复杂。
    ③ 不适合的场景
    • 希望从开始到结束的顺序从头到尾读取一个文件;
    • 文件有几百兆字节或者更大,将大文件映射到内存中会快速地填充内存,并可能导致分页,这将抵消首先映射文件的好处。对于大型顺序读取操作,禁用磁盘缓存并将文件读入一个小内存缓冲区;
    • 该文件大于可用的连续虚拟内存地址空间。对于 64 位应用程序来说,这不是什么问题,但是对于 32 位应用程序来说,这是一个问题。32 位虚拟内存最大是 4GB,可以只映射部分;
    • 因为每次操作内存会同步到磁盘,所以不适用于移动磁盘或者网络磁盘上的文件;
    • 变长文件不适用。
    ④ mmap 内存映射原理
    • 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域:
      • 进程在用户空间调用库函数 mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
      • 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址;
      • 为此虚拟区分配一个 vm_area_struct 结构,接着对这个结构的各个域进行了初始化;
      • 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中。
    • 调用内核空间的系统调用函数 mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系:
      • 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息;
      • 通过该文件的文件结构体,链接到 file_operations 模块,调用内核函数 mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数;
      • 内核 mmap 函数通过虚拟文件系统 inode 模块定位到文件磁盘物理地址;
      • 通过 remap_pfn_range 函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。
    • 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
      • 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
      • 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
      • 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用 nopage 函数把所缺的页从磁盘装入到主存中。
      • 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
    • 前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。
    • 修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用 msync() 来强制同步, 这样所写的内容就能立即保存到文件里。
    ⑤ 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:映射区的开始地址;
      • length:映射区的长度;
      • prot:期望的内存保护标志,不能与文件的打开模式冲突,是以下的某个值,可以通过 or 运算合理地组合在一起;
    	1 PROT_EXEC :页内容可以被执行
    	2 PROT_READ :页内容可以被读取
    	3 PROT_WRITE :页可以被写入
    	4 PROT_NONE :页不可访问
    
      • flags:指定映射对象的类型,映射选项和映射页是否可以共享,它的值可以是一个或者多个以下位的组合体;
    	 1 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
    	 2 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    	 3 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    	 4 MAP_DENYWRITE //这个标志被忽略。
    	 5 MAP_EXECUTABLE //同上
    	 6 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
    	 7 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
    	 8 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    	 9 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
    	10 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
    	11 MAP_FILE //兼容标志,被忽略。
    	12 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
    	13 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
    	14 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
    
      • fd:有效的文件描述词,如果 MAP_ANONYMOUS 被设定,为了兼容问题,其值应为-1;
      • offset:被映射对象内容的起点。
    • 相关函数:
    	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() 实现磁盘上文件内容与共享内存区的内容一致。
    • 当映射关系解除后,对原来映射地址的访问将导致段错误发生。
    ⑥ mmap 使用细节
    • 使用 mmap 需要注意的一个关键点是,mmap 映射区域大小必须是物理页大小(page_size)的整倍数(32 位系统中通常是 4k 字节)。这是因为内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位,为了匹配内存的操作,mmap 从磁盘到虚拟地址空间的映射也必须是页。
    • 内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。
    • 映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关,同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
    ⑦ 映射区域大小如果不是物理页的整倍数的具体情况分析
    • 情形一:一个文件的大小是 5000 字节,mmap 函数从一个文件的起始位置开始,映射 5000 字节到虚拟内存中。
      • 分析:因为单位物理页面的大小是 4096 字节,虽然被映射的文件只有 5000 字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此 mmap 函数执行后,实际映射到虚拟内存区域 8192 个字节,5000~8191 的字节部分用零填充,映射后的对应关系如下图所示:

    在这里插入图片描述

      • 此时:
        • 读/写前 5000 个字节(0~4999),会返回操作文件内容;
        • 读字节 5000 ~ 8191 时,结果全为 0, 写 5000 ~ 8191 时,进程不会报错,但是所写的内容不会写入原文件中 ;
        • 读/写 8192 以外的磁盘部分,会返回一个 SIGSECV 错误。
    • 情形二:一个文件的大小是 5000 字节,mmap 函数从一个文件的起始位置开始,映射 15000 字节到虚拟内存中,即映射大小超过了原始文件的大小。
      • 分析:由于文件的大小是 5000 字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出 5000 的部分不会体现在原文件中。由于程序要求映射 15000 字节,而文件只占两个物理页,因此 8192 字节~15000 字节都不能读写,操作时会返回异常。如下图所示:

    在这里插入图片描述

      • 此时:
        • 进程可以正常读/写被映射的前 5000 字节(0~4999),写操作的改动会在一定时间后反映在原文件中;
        • 对于 5000~8191 字节,进程可以进行读写过程,不会报错,但是内容在写入前均为 0,另外,写入后不会反映在文件中;
        • 对于 8192~14999 字节,进程不能对其进行读写,会报 SIGBUS 错误;
        • 对于 15000 以外的字节,进程不能对其读写,会引发 SIGSEGV 错误。
    • 情形三:一个文件初始大小为 0,使用 mmap 操作映射了 1000*4K 的大小,即 1000 个物理页大约 4M 字节空间,mmap 返回指针 ptr。
      • 分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为 0,并没有合法的物理页对应,如同情形二一样,会返回 SIGBUS 错误。
      • 但是如果,每次操作 ptr 读写前,先增加文件的大小,那么 ptr 在文件大小内部的操作就是合法的。例如,文件扩充 4096 字节,ptr 就能操作 ptr ~ [ (char)ptr + 4095] 的空间。只要文件扩充的范围在 1000 个物理页(映射范围)内,ptr 都可以对应操作相同的大小。
      • 这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。

    三、iOS 中的 mmap

    • 官方的demo为例,其它的代码很简明直接,核心就在于 mmap 函数:
    	/**
    	 *  @param  start  映射开始地址,设置 NULL 则让系统决定映射开始地址
    	 *  @param  length  映射区域的长度,单位是 Byte
    	 *  @param  prot  映射内存的保护标志,主要是读写相关,是位运算标志;(记得与下面fd对应句柄打开的设置一致)
    	 *  @param  flags  映射类型,通常是文件和共享类型
    	 *  @param  fd  文件句柄
    	 *  @param  off_toffset  被映射对象的起点偏移
    	 */
    	void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
    	
    	*outDataPtr = mmap(NULL,
    	                   size,
    	                   PROT_READ|PROT_WRITE,
    	                   MAP_FILE|MAP_SHARED,
    	                   fileDescriptor,
    	                   0);
    
    • 用官网的代码做参考,实现一个读写的例子:
    	#import "ViewController.h"
    	#import <sys/mman.h>
    	#import <sys/stat.h>
    	
    	int MapFile(const char * inPathName, void ** outDataPtr, size_t * outDataLength, size_t appendSize) {
    	    int outError;
    	    int fileDescriptor;
    	    struct stat statInfo;
    	    
    	    // Return safe values on error.
    	    outError = 0;
    	    *outDataPtr = NULL;
    	    *outDataLength = 0;
    	    
    	    // Open the file.
    	    fileDescriptor = open( inPathName, O_RDWR, 0 );
    	    if(fileDescriptor < 0) {
    	        outError = errno;
    	    } else {
    	        // We now know the file exists. Retrieve the file size.
    	        if( fstat( fileDescriptor, &statInfo ) != 0 ) {
    	            outError = errno;
    	        } else {
    	            ftruncate(fileDescriptor, statInfo.st_size + appendSize);
    	            fsync(fileDescriptor);
    	            *outDataPtr = mmap(NULL,
    	                               statInfo.st_size + appendSize,
    	                               PROT_READ|PROT_WRITE,
    	                               MAP_FILE|MAP_SHARED,
    	                               fileDescriptor,
    	                               0);
    	            if( *outDataPtr == MAP_FAILED ) {
    	                outError = errno;
    	            } else {
    	                // On success, return the size of the mapped file.
    	                *outDataLength = statInfo.st_size;
    	            }
    	        }
    	        
    	        // Now close the file. The kernel doesn’t use our file descriptor.
    	        close( fileDescriptor );
    	    }
    	    
    	    return outError;
    	}
    	
    	
    	void ProcessFile(const char * inPathName) {
    	    size_t dataLength;
    	    void * dataPtr;
    	    char *appendStr = " append_key";
    	    int appendSize = (int)strlen(appendStr);
    	    if( MapFile(inPathName, &dataPtr, &dataLength, appendSize) == 0) {
    	        dataPtr = dataPtr + dataLength;
    	        memcpy(dataPtr, appendStr, appendSize);
    	        // Unmap files
    	        munmap(dataPtr, appendSize + dataLength);
    	    }
    	}
    	
    	@interface ViewController ()
    	
    	@end
    	
    	@implementation ViewController
    	
    	- (void)viewDidLoad {
    	    [super viewDidLoad];
    	    
    	    NSString * path = [NSHomeDirectory() stringByAppendingPathComponent:@"test.data"];
    	    NSLog(@"path: %@", path);
    	    NSString *str = @"test str";
    	    [str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    	    
    	    ProcessFile(path.UTF8String);
    	    NSString *result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    	    NSLog(@"result:%@", result);
    	}
    

    四、MMKV 和 mmap

    ① MMKV 简介
    • NSUserDefault 是常见的缓存工具,但是数据有时会同步不及时,比如说在 crash 前保存的值很容易出现保存失败的情况,在 App 重新启动之后读取不到保存的值。
    • MMKV 很好的解决了 NSUserDefault 的局限,但是同样由于其独特设计,在数据量较大、操作频繁的场景下,会产生性能问题。这里的使用给出两个建议:
      • 不要全部用 defaultMMKV,根据业务大的类型做聚合,避免某一个 MMKV 数据过大,特别是对于某些只会出现一次的新手引导、红点之类的逻辑,尽可能按业务聚合,使用多个 MMKV 的对象;
      • 对于需要频繁读写的数据,可以在内存持有一份数据缓存,必要时再更新到 MMKV。
    ② MMKV 原理
    • 内存准备:通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由 iOS 负责将内存回写到文件,不必担心 crash 导致数据丢失。
    • 数据组织:数据序列化方面选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。考虑到要提供的是通用 KV 组件,key 可以限定是 string 字符串类型,value 则多种多样(int/bool/double 等)。要做到通用的话,考虑将 value 通过 protobuf 协议序列化成统一的内存块(buffer),然后就可以将这些 KV 对象序列化到内存中。
    	message KV {
    		string key = 1 ;
    		buffer value = 2;
    	}
    	- (B00L)setInt32:(int32 t)value forKey:(NSString*)key {
    		auto data = PBEncode(value); 
    		return [self setData:data forKey:key];
    	}
    	- (BO0L)setData: (NSData*)data forKey:(NSString*)key {
    		auto kv = KV[key,data];
    		auto buf = PBEncode(kv);
    		return [self write: buf];
    	}
    
    • 写入优化:标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。
    • 空间增长:使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。例如同一个 key 不断更新的话,是可能耗尽几百 M 甚至上 G 空间,而事实上整个 KV 文件就这一个 key,不到 1k 空间就存得下,这明显是不可取的。我们需要在性能和空间上做个折中:以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。
    	- (B00L)append: (NSData*)data {
    		if (space >= data.length) {
    			append(fd, data);
    		} else {
    			newData = unique(m_allKV);
    			if (total_space >= newData.length) {
    				write(fd, newData);
    			} else {
    				while (total_space < newData.length) {
    					total_ space *= 2;
    				}
    				ftruncate(fd, total . space);
    				write(fd, newData);
    			}
    		}
    	}
    
    • 数据有效性:考虑到文件系统、操作系统都有一定的不稳定性,另外增加了 crc 校验,对无效数据进行甄别。
    • MMKV 性能:写个简单的测试,将 MMKV、NSUserDefaults 的性能进行对比(循环写入1w 次数据,测试环境:iPhone X 256G, iOS 11.2.6,单位:ms):

    在这里插入图片描述

    • 可见 MMKV 性能远远优于 iOS 自带的 NSUserDefaults。另外,在测试中发现,NSUserDefaults 在每 2-3 次测试,就会有 1 次比较耗时的操作,怀疑是触发了数据 synchronize 重整写入。对比之下,MMKV 即使触发数据重整,也保持了性能的稳定高效。

    五、NSData 与 mmap

    • NSData 有一个静态方法和 mmap 有关系:
    	+ (id)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;
    	
    	typedef NS_OPTIONS(NSUInteger, NSDataReadingOptions) {
    	
    	    // Hint to map the file in if possible and safe. 在保证安全的前提下使用 mmap
    	    NSDataReadingMappedIfSafe =   1UL << 0,
    	    // Hint to get the file not to be cached in the kernel. 不要缓存。如果该文件只会读取一次,这个设置可以提高性能
    	    NSDataReadingUncached = 1UL << 1,
    	    // Hint to map the file in if possible. This takes precedence over NSDataReadingMappedIfSafe if both are given.  总使用 mmap
    	    NSDataReadingMappedAlways API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)) = 1UL << 3,
    	    ...
    	};
    
    • Mapped 的意思是使用 mmap,那么 ifSafe 是什么意思呢?NSDataReadingMappedIfSafe 和 NSDataReadingMappedAlways 有什么区别?
    • 如果使用 mmap,则在 NSData 的生命周期内,都不能删除对应的文件。
    • 如果文件是在固定磁盘,非可移动磁盘、网络磁盘,则满足 NSDataReadingMappedIfSafe。对 iOS 而言,这个 NSDataReadingMappedIfSafe = NSDataReadingMappedAlways。
    • 那什么情况下应该用对应的参数?
      • 如果文件很大,直接使用 dataWithContentsOfFile 方法,会导致 load 整个文件,出现内存占用过多的情况;此时用 NSDataReadingMappedIfSafe,则会使用 mmap 建立文件映射,减少内存的占用。
      • 使用场景:视频加载。视频文件通常比较大,但是使用的过程中不会同时读取整个视频文件的内容,可以使用 mmap 优化。

    六、总结

    • mmap 就是文件的内存映射。通常读取文件是将文件读取到内存,会占用真正的物理内存;而 mmap 是用进程的内存虚拟地址空间去映射实际的文件中,这个过程由操作系统处理。mmap 不会为文件分配物理内存,而是相当于将内存地址指向文件的磁盘地址,后续对这些内存进行的读写操作,会由操作系统同步到磁盘上的文件。
    • iOS 中使用 mmap 可以用 c 方法的 mmap(),也可以使用 NSData 的接口带上NSDataReadingMappedIfSafe 参数。前者自由度更大,后者用于读取数据。
    • mmap 优点:
      • 对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代 I/O 读写,提高了文件读取效率。
      • 实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
      • 提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
      • 如果进程 A 和进程 B 都映射了区域 C,当 A 第一次读取 C 时通过缺页从磁盘复制文件页到内存中;但当 B 再读 C 的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
      • 可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件 I/O 操作,极大影响效率。这个问题可以通过 mmap 映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap 都可以发挥其功效。
    展开全文
  • PE文件映射解析

    千次阅读 2021-09-06 10:00:24
    解析 PE 文件的内存映射,了解 PE 文件的磁盘和内存对齐方式,编程实现 PE 文件的内存映射加载。

    个人博客:coonaa.cn本文博客同步地址


    一. 映射基础

    1.1 文件结构

    在了解 PE 文件的内存映射之前,需要先了解 PE 文件的结构特征。针对 PE 文件结构部分的内容,可以参考我之前发过的文章《 PE 文件结构解析》


    1.2 结构成员

    PE 文件的 _IMAGE_OPTIONAL_HEADER 结构中存在两个非常重要的结构成员:SectionAlignment 和 FileAlignment 。

    SectionAlignment:内存对齐粒度,即 PE 文件映射到内存时的对齐粒度;默认值为系统页面大小 0x1000 B(4KB);该值必须大于或等于 FileAligment 的值。

    FileAlignment:磁盘对齐粒度,即 PE 文件在磁盘中存储时的对齐粒度;默认值为磁盘页面大小 0x200 B(512B);如果 SectionAlignment 的值小于系统页面大小,则该值必须与 SectionAlignment 的值相同。


    1.3 地址偏移

    PE 文件中几个重要地址的基本概念:

    VA(Virtual Address):虚拟地址
    PE 文件映射到内存空间时,数据在内存空间中对应的地址。

    ImageBase:映射基址
    PE 文件在内存空间中的映射起始位置,是个 VA 地址。

    RVA(Relative Virtual Address):相对虚拟地址
    PE 文件在内存中的 VA 相对于 ImageBase 的偏移量。

    FA(File Offset Address,FOA):文件偏移地址
    PE 文件在磁盘上存放时,数据相对于文件开头位置的偏移量,文件偏移地址等于文件地址。


    1.4 映射方式

    PE 文件在执行的时候,映射到内存中的结构布局与该文件在磁盘中存储时的结构布局是一致的。
    Windows 装载器(又称 PE 装载器)在载入一个 PE 文件时,把磁盘中的 PE 文件映射到进程的内存地址空间,是一种从磁盘偏移地址到内存偏移地址的转换。

    从 “头部” 和 “区块” 两部分进行 PE 文件的大小和映射分析:

    (1)头部
    DOS Header 部分的大小一般固定为 0x40 字节;
    DOS Stub 部分的大小可能随文件的不同而不同;
    PE Header 部分的大小一般固定为 0xF8 字节;
    Section Table 部分的大小可能随具体区块的数目不同而不同。

    一般而言,整个 PE 文件头部的大小固定为 0x400 字节,不足 0x400 字节的部分用 0 填充。

    DOS 首部的起始位置,即 FA = 0 的位置,映射到内存地址空间后所对应的地址即为 ImageBase ,同时该位置 RVA = 0 。
    从 FA = 0 的位置开始,将整个 PE 文件头部以类似于 “复制” 的形式,映射到以 ImageBase 为起始位置的内存地址空间中。

    (2)区块
    PE 文件中的区块数目不固定,同时每个区块的大小也不固定,这取决于 PE 文件的实际需要。
    但每个区块在磁盘中的大小都应该是 FileAlignment 值的整数倍,在内存地址空间中的大小都应该是 SectionAlignment 值的整数倍,不足的部分用 0 填充。

    由于整个 PE 文件头部的大小固定为 0x400 字节,因此 PE 文件中第一个区块的起始位置为 FA = 0x400 ,映射后 RVA = 0x1000 。

    从第二个区块开始,每个区块映射后的起始位置为 RVA = 0x1000 + 0x1000 * N 。其中 N 为正整数,其值由区块的实际大小决定。


    一个简单的 PE 文件内存映射对齐关系,用图表示如下:
    PE文件内存映射对齐

    二. 映射实现

    2.1 重点函数

    内存文件映射的一般方法(三大函数步骤):
    CreateFile() 函数:创建文件内核对象,获得文件内核对象句柄;
    CreateFileMapping() 函数:创建文件映射对象,获得文件映射对象句柄;
    MapViewOfFile() 函数:将文件映射对象映射到进程地址空间,获得映射基址。


    2.1.1 CreateFileA function

    函数定义(该函数定义于 fileapi.h 头文件):

    HANDLE CreateFileA(
      LPCSTR                lpFileName,
      DWORD                 dwDesiredAccess,
      DWORD                 dwShareMode,
      LPSECURITY_ATTRIBUTES lpSecurityAttributes,
      DWORD                 dwCreationDisposition,
      DWORD                 dwFlagsAndAttributes,
      HANDLE                hTemplateFile
    );
    

    函数参数
    (1)lpFileName:要创建或打开的文件或设备的名称。
    (2)dwDesiredAccess:要对文件或设备使用的访问权限。部分取值:

    GENERIC_READ:可读
    GENERIC_WRITE:可写
    GENERIC_READ | GENERIC_WRITE:可读可写
    0:皆非,只允许获取某些元数据,例如设备属性等

    (3)dwShareMode:要对文件或设备使用的共享模式。部分取值:

    FILE_SHARE_READ:可读
    FILE_SHARE_WRITE:可写
    FILE_SHARE_DELETE:可删除
    FILE_SHARE_READ | FILE_SHARE_WRITE:可读可写
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE:全部
    0:皆非,阻止其它进程共享或在句柄关闭之前同时打开该文件或设备

    (4)lpSecurityAttributes:指向 SECURITY_ATTRIBUTES 结构的指针,该结构包含两个数据成员:可选的安全描述符、确定返回的句柄是否可以被子进程继承的布尔值。部分取值:

    NULL:CreateFile 返回的句柄不能被应用程序可能创建的任何子进程继承,并且与返回的句柄关联的文件或设备将获得默认安全描述符。

    (5)dwCreationDisposition:对存在或不存在的文件或设备采取的操作。对于文件以外的设备,此参数通常设置为 OPEN_EXISTING 。部分取值:

    CREATE_ALWAYS:总是创建一个新文件。如果指定文件存在且可写则覆盖该文件,如果指定文件不存在且路径是可写的有效路径则创建一个新文件。
    CREATE_NEW:仅当指定文件不存在且路径有效时,创建一个新文件。
    OPEN_ALWAYS:始终打开一个文件。如果指定文件不存在且路径是可写的有效路径则创建一个新文件。
    OPEN_EXISTING:仅当指定文件或设备存在时,打开该文件或设备。
    TRUNCATE_EXISTING:仅当指定文件存在时,打开该文件并将其截断为零字节。

    (6)dwFlagsAndAttributes:文件或设备的属性和标志。对于文件来讲此参数通常的默认值为 FILE_ATTRIBUTE_NORMAL 。
    (7)hTemplateFile:具有 GENERIC_READ 访问权限的模板文件的有效句柄。模板文件为正在创建的文件提供文件属性和扩展属性。此参数通常设置为 NULL 。

    返回值
    如果函数成功,则返回值是指定文件、设备等的句柄。
    如果函数失败,则返回值是 INVALID_HANDLE_VALUE 。


    2.1.2 CreateFileMappingA function

    函数定义(该函数定义于 winbase.h 头文件):

    HANDLE CreateFileMappingA(
      HANDLE                hFile,
      LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
      DWORD                 flProtect,
      DWORD                 dwMaximumSizeHigh,
      DWORD                 dwMaximumSizeLow,
      LPCSTR                lpName
    );
    

    函数参数
    (1)hFile:要从中创建文件映射对象的文件句柄。
    (2)lpFileMappingAttributes:指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的句柄是否可以被子进程继承。部分取值:

    NULL:句柄不能被继承,文件映射对象获得一个默认的安全描述符。

    (3)flProtect:指定文件映射对象的页面保护方式。对象的所有映射视图必须与此保护兼容。部分取值:

    PAGE_EXECUTE_READ:允许将视图映射为只读、写时复制、执行。必须使用 GENERIC_READ 和 GENERIC_EXECUTE 访问权限创建文件句柄。


    PAGE_EXECUTE_READWRITE:允许将视图映射为只读、写时复制、读写、执行。必须使用 GENERIC_READ 、GENERIC_WRITE 和 GENERIC_EXECUTE 访问权限创建文件句柄。


    PAGE_EXECUTE_WRITECOPY:允许将视图映射为只读、写时复制、执行,此值等效于 PAGE_EXECUTE_READ 。必须使用 GENERIC_READ 和 GENERIC_EXECUTE 访问权限创建文件句柄。


    PAGE_READONLY:允许将视图映射为只读、写时复制,尝试写入特定区域会导致访问冲突。必须使用 GENERIC_READ 访问权限创建文件句柄。


    PAGE_READWRITE:允许将视图映射为只读、写时复制、读写。必须使用 GENERIC_READ 和 GENERIC_WRITE 访问权限创建文件句柄。


    PAGE_WRITECOPY:允许将视图映射为只读、写时复制,此值等效于 PAGE_READONLY 。必须使用 GENERIC_READ 访问权限创建文件句柄。

    (4)dwMaximumSizeHigh:文件映射对象最大大小(DWORD)的高位。
    (5)dwMaximumSizeLow:文件映射对象最大大小(DWORD)的低位。
    (6)lpName:文件映射对象的名称。部分取值:

    NULL:新建的文件映射对象没有名称。
    若此参数与现有映射对象的名称匹配,则该函数将请求访问具有 flProtect 指定保护的对象。

    返回值
    如果函数成功,则返回值是新创建的文件映射对象的句柄。
    如果映射对象先前已存在,则返回值是已有映射对象的句柄。
    如果函数失败,则返回值是 NULL 。


    2.1.3 MapViewOfFile function

    函数定义(该函数定义于 memoryapi.h 头文件):

    LPVOID MapViewOfFile(
      HANDLE hFileMappingObject,
      DWORD  dwDesiredAccess,
      DWORD  dwFileOffsetHigh,
      DWORD  dwFileOffsetLow,
      SIZE_T dwNumberOfBytesToMap
    );
    

    函数参数
    (1)hFileMappingObject:文件映射对象句柄。
    (2)dwDesiredAccess:对文件映射对象的访问类型。部分取值:

    FILE_MAP_ALL_ACCESS:文件的读写视图被映射。必须使用 PAGE_READWRITE 或 PAGE_EXECUTE_READWRITE 保护方式创建文件映射对象。当与 MapViewOfFile 函数一起使用时,FILE_MAP_ALL_ACCESS 等效于 FILE_MAP_WRITE 。


    FILE_MAP_READ:文件的只读视图被映射,尝试写入文件视图会导致访问冲突。必须使用 PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE_READ 或 PAGE_EXECUTE_READWRITE 保护方式创建文件映射对象。


    FILE_MAP_WRITE:文件的读写视图被映射。必须使用 PAGE_READWRITE 或 PAGE_EXECUTE_READWRITE 保护方式创建文件映射对象。当与 MapViewOfFile 函数一起使用时,(FILE_MAP_WRITE | FILE_MAP_READ) 和 FILE_MAP_ALL_ACCESS 均等效于 FILE_MAP_WRITE 。

    (3)dwFileOffsetHigh:视图起始位置文件偏移量(DWORD)高位。偏移量必须是系统内存分配粒度的整数倍。
    (4)dwFileOffsetLow:视图起始位置文件偏移量(DWORD)低位。偏移量必须是系统内存分配粒度的整数倍。
    (5)dwNumberOfBytesToMap:要映射到视图的文件映射对象的字节数,数值必须在 CreateFileMapping 指定的最大范围内。部分取值:

    0:映射从指定的偏移量扩展到文件映射对象的末尾。

    返回值
    如果函数成功,则返回值是映射视图的起始地址。
    如果函数失败,则返回值为 NULL 。


    2.2 程序实现

    通过运用上文中的三个函数来完成对文件的内存映射,同时对其是否是 PE 文件进行判断。

    判断是否是 PE 文件? 两个标志位:
    (1)_IMAGE_DOS_HEADER 中字段 e_magic 的取值是否等于 IMAGE_DOS_SIGNATURE 。
    (2)_IMAGE_NT_HEADER 中字段 Signature 的取值是否等于 IMAGE_NT_SIGNATURE 。

    程序代码如下:

    /*将项目属性中的字符集设置为“未设置”。*/
    
    #include <stdio.h>
    #include <Windows.h>
    #include <fileapi.h>
    #include <winbase.h>
    #include <memoryapi.h>
    #include <winnt.h>
    
    int main() {
    	LPCSTR FilePath;			//磁盘文件路径
    	HANDLE hFile;				  //文件内核对象句柄
    	HANDLE hMapping;			//文件映射对象句柄
    	LPVOID ImageBase;			//映射基址
    	PIMAGE_DOS_HEADER pDH = NULL;	//指向 _IMAGE_DOS_HEADER 结构的指针
    	PIMAGE_NT_HEADERS pNH = NULL;	//指向 _IMAGE_NT_HEADERS 结构的指针
    
    	//指定磁盘文件路径。
    	FilePath = "D:\\execase.exe";
    
    	//创建文件内核对象,返回文件内核对象句柄。
    	hFile = CreateFile(FilePath, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	if (!hFile) {
    		printf("Create File Kernel Object Error! \n");
    		return FALSE;
    	}
    
    	//创建文件映射对象,获得文件映射对象句柄。
    	hMapping = CreateFileMapping(hFile, NULL, PAGE_EXECUTE_READWRITE | SEC_IMAGE, 0, 0, NULL);
    	if (!hMapping) {
    		printf("Create File Image Object Error! \n");
    		CloseHandle(hFile);
    		return FALSE;
    	}
    
    	//将文件映射对象映射到进程地址空间,获得映射基址。
    	ImageBase = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    	if (!ImageBase) {
    		printf("Memory Mapping Error! \n");
    		CloseHandle(hMapping);
    		CloseHandle(hFile);
    		return FALSE;
    	}
    	printf("ImageBase: 0x%p \n", ImageBase);
    
    	//获取对应结构体指针。
    	pDH = (PIMAGE_DOS_HEADER)ImageBase;
    	pNH = (PIMAGE_NT_HEADERS)((DWORD)pDH + pDH->e_lfanew);
    
    	//判断标志位。
    	if (pDH->e_magic == IMAGE_DOS_SIGNATURE && pNH->Signature == IMAGE_NT_SIGNATURE)
    		printf("The file is a PE file! ");
    	else
    		printf("The file is not a PE file! ");
    
    	//关闭视图和句柄。
    	UnmapViewOfFile(ImageBase);
    	CloseHandle(hMapping);
    	CloseHandle(hFile);
    
    	return 0;
    }
    

    三. 映射结果

    以某个 PE 文件 execase.exe 为例,对映射结果进行对比分析。

    3.1 判断结果

    程序判断 execase.exe 为一个正常有效的 PE 文件,如图:
    ImageBase & PE

    3.2 对齐结果

    PE 文件 execase.exe 在磁盘中存储时的结构及大小如下:
    execase.exe 磁盘文件结构

    (1)头部映射对齐: FA = 0x0 映射到 ImageBase = 0x00D30000 。
    磁盘
    内存

    (2)区块 .text 映射对齐: FA = 0x400 映射到 VA = 0x00D31000 。
    磁盘
    内存

    (3)区块 .rdata 映射对齐: FA = 0xE00 映射到 VA = 0x00D32000 。
    磁盘
    内存

    (3)区块 .data 映射对齐: FA = 0x1600 映射到 VA = 0x00D33000 。
    磁盘
    内存

    (4)后续区块以此类推继续往后对齐。


    四. 内容总结

    参考资料:
    https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga
    https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile

    特别感谢:
    Winder (School of software, Yunnan University)

    相关文章:《 PE 文件结构解析》

    如有错误遗漏之处,欢迎补充交流。

    展开全文
  • 色调映射(Tone Mapping)

    千次阅读 2021-10-29 15:00:23
    色调映射(Tone Mapping),即压缩其动态范围至输出设备的动态范围以下,使高动态范围HDR图像能够适应低动态范围LDR显示器。

    一、概述

            虽然HDR 图像有较大的动态范围,能更细致地反映真实场景,但他的缺点也很明显。一是同尺寸的数据比低动态范围图像大,需要更大的存储空间与传输带宽。二是难以输出,目前大多数显示器、打印机等图形输出设备的动态范围要比普通的高动态范围图像小得多。。因此,色调映射技术在这样的背景下,就显得尤为重要。HDR 图像处理的一个重要方面就是色调映射(Tone Mapping),即压缩其动态范围至输出设备的动态范围以下,使高动态范围HDR图像能够适应低动态范围LDR显示器。

            色调映射实质上是一个信息压缩过程。需要将HDR图像的色度,亮度,动态范围等,全部映射到LDR图像的标准范围内。在这个过程中,不能简单使用线性映射,因为这样会丢失原始图像的一些重要信息,例如全局与局部的对比度和重要的细节等,最重要的一点是,简单线性映射产生的图像相比于自然界中的场景,会出现严重的失真情况。因此,色调映射技术需要经过深入的研究与精密的设计,才能使得产生的图像在视觉效果上与实际场景中的效果一致。如何选取有效的信息成分,使低动态图像能保持人类对真实场景的视觉感受,是色调映射应遵循的基本原则。

            色调映射方法可以被分为两大类:

    1. 全局色
    展开全文
  • 高等数学 —— 映射与函数 —— 映射

    万次阅读 多人点赞 2019-02-26 19:09:19
    映射是现代数学中的一个基本概念,而函数是微积分的研究对象,也是映射的一种 1.映射概念 设X、YX、YX、Y是两个非空集合,如果存在一个法则fff,使得对XXX中每个元素xxx,按法则fff,在YYY中有唯一确定的元素yyy与之...
  • 光线跟踪和光子映射

    2015-06-21 22:14:09
    实现了关于图像渲染的五个主流算法: ...光子映射(PM 1995) 渐进光子映射(PPM 2008) 随机渐进光子映射(SPPM 2011) 由于只是一个大作业,所以有设计上的不规范,请多多指教>< 但我还是写得很认真滴
  • 混沌映射分岔图

    千次阅读 2022-01-14 12:33:37
    文章目录一、理论基础1、Logistic映射2、Sine映射3、Neuron映射4、Tent映射5、Chebyshev映射6、Cubic映射7、ICMIC映射二、仿真实验三、参考文献 一、理论基础 1、Logistic映射 定义如下:xn+1=μxn(1−xn),  0<...
  • ---- Cache的容量很小,...为了把信息放到Cache中,必须应用某种函数把主存地址定位到Cache中,这称为地址映射。---- 在信息按这种映射关系装入Cache后,CPU执行程序时,会将程序中的主存地址变换成Cache地址,这个...
  • 有没有什么软件可以映射鼠标键盘的.用OTG手机打开蓝牙,搜索目标,连接Bluetooth Keyoad,手机会出现配对信心。直接在键盘上输入4位数字,并回车就可以连接上。通过小米手机遥控电脑的鼠标、键盘。让您的小米变成...
  • 组相联映射   以两路组相联映射为例 图3 组相联映射   组相联(set-associative)映射实际上是直接映射和全相联映射的折中方案,存储器中的一个数据不单单只能放在一个 Cache line中,而是可以放在多个 Cache ...
  • cache-主存的三种映射方式

    千次阅读 多人点赞 2021-05-04 10:00:23
    cache-主存的三种映射方式2.1 全相联映射2.2 直接映射方式2.3 组相联映射方式3. 三种映射方式例题 1. 基本概念 1. 存储系统的体系结构 图片摘自这篇博客:图片来源 2. cache在存储系统中的位置 cache(缓存)...
  • 容易理解的计算机组成原理中主存与Cache的3种映射方式(直接映射,全相联映射,组相联映射) 一.为了让大家更加方便的理解,我首先设置了两个问题,同时也写了相应的个人所理解的答案 1.为什么引入Cache? 答: ...
  • Mybatis映射文件规则

    千次阅读 多人点赞 2022-03-29 19:36:55
    在说明映射文件规则之前,先来回顾一下ORM相关概念。 1.ORM概念 ORM(Object Relationship Mapping)对象关系映射 对象:Java的实体类对象 关系:关系型数据库 映射:二者之间的对应关系 字段名和属性名要一一对应才...
  • oracle 数据库关系映射

    2011-07-30 17:55:16
    关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.关系映射.
  • 混沌映射程序

    2011-12-14 09:34:44
    混沌映射的matlab仿真程序,包括帐篷映射等等各种映射
  • mmap 内存映射详解

    千次阅读 2020-04-23 20:05:30
    mmap是一种内存映射的方法,这一功能可以用在文件的处理上,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。 在编程时可以使某个磁盘文件的内容...
  • Cache地址映射

    万次阅读 多人点赞 2018-09-01 15:17:35
    理解Cache地址映射之前补充一些基础知识,Cache的地址映射和MMU(内存管理单元)和TLB Cache(转译查找缓存)中的映射是有区别的。Cache、TLB Cache、MMU在CPU中结构如图1所示,图1展现的是Cortex A9 Processor内部...
  • 22.模型的创建与映射

    万次阅读 多人点赞 2021-08-29 16:35:21
    模型的创建与映射 共分三步走: 编写models.py; 对项目调用makemigrationgs命令; 让Django迁移项目。 第一部分——上实操! (1)编写models.py: (music/models.py文件) 模型告诉Django如何处理应用程序中存储...
  • 局域网ip映射到公网工具

    热门讨论 2012-11-02 12:19:12
    应用场景:在局域网的机器运行的程序,想在外网也能访问
  • Arnold映射(猫映射)原理及 matlab源码

    千次阅读 多人点赞 2020-02-19 12:45:37
    映射(Cat映射),也称为Arnold映射,由俄国数学家弗拉基米尔·阿诺德(Vladimir Igorevich Arnold)提出,在Arnold授课的时候经常使用猫的图像作为例子,故称为“猫映射”。这是一种在有限区域内进行反复折叠、...
  • qt的内存映射

    千次阅读 2020-09-30 10:16:15
    uchar *QFileDevice::map...当QFile被销毁或用这个对象打开一个新文件时,任何未被映射映射都将被自动取消映射映射将具有与文件相同的打开模式(读和/或写),除非使用maprivateOption,在这种情况下,始终可以
  • 标题有点绕,问题就是在公网出接口上配置了内网某台服务器的端口映射,内网的普通用户通过内网地址访问正常,但无法通过公网IP进行正常访问,拓扑图如下:上图以出接口地址100.100.100.100:80映射为192.168.1.11:80...
  • Docker目录映射

    千次阅读 2022-03-31 16:01:55
    docker rm $(docker ps -a -q) // remove删除所有容器 docker restart 容器id //重启容器 docker run -d -p 8008:80 --name nginx-name nginx:1.1.1 启动一个新docker实例(nginx:1.1.1是版本号) 目录映射: 实例: ...
  • Docker 文件映射

    千次阅读 2022-03-31 13:45:44
    在创建Docker容器时,想要与本地路径进行映射共享文件,使用docker run -v指令,例如我需要将本地的的/root/code路径映射到容器内的/data/code路径,使用如下指令,冒号前为宿主机路径,冒号后为容器路径,其中xxx为...
  • 本地进行域名映射

    千次阅读 2021-12-16 14:01:26
    2、修改配置文件,对127.0.0.1做一个域名映射activate.navicat.com 3、启动本地项目,原来访问地址为127.0.0.1:8080 做了上述映射后可以通过activate.navicat.com:8080进行访问系统首页 系统首页: ...
  • mmap库:Python内存映射文件操作

    千次阅读 2021-05-05 18:54:27
    内存映射通常可以提高I/O的性能,因为使用内存映射时,不需要对每个访问都建立一个单独的系统调用,也不需要在缓冲区之间复制数据,内核和用户都能很方便的直接访问内存。 本篇,将详细介绍Python内存映射库:mmap。...
  • 使用MapStruct映射集合

    千次阅读 2021-04-25 19:30:40
    1.映射集合 通常,使用MapStruct映射集合的方式与使用简单类型的方式相同。 基本上,必须创建一个简单的接口或抽象类并声明映射方法。 根据声明,MapStruct将自动生成映射代码。 通常,生成的代码将遍历源集合,将每...
  • 高等数学之映射

    千次阅读 2021-12-25 12:41:59
    高等数学之映射 1.映射概念 映射定义:X、Y非空集合 ,有一个法则ƒ,使得X中每一个元素x,有唯一的y与之对应。记作:ƒ:X→Y y称为x的像,记作:y=ƒ(x) x称为y的一个原像 集合X称为映射ƒ的定义域,记作:Dƒ,即Dƒ=...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,302,901
精华内容 921,160
关键字:

映射