基本概念
系统调用
为了保证每一个进程都能安全的执行。现代OS中,CPU运行有两种模式:“用户模式”与“内核模式”。
内核模式下,应用具有对硬件的所有控制权,可以执行所有CPU指令,可以访问任意地址内存,在内核模式下的任何异常都是灾难性的,将会导致整台机器停机。
一些容易发生安全问题的操作都被限制在只有内核模式下才可以执行,例如I/O操作,修改基址寄存器内容等。
用户模式下,应用没有对硬件的直接控制权,也不能直接访问地址的内存,程序是通过调用系统API来达到访问硬件和内存,这种保护模式下,即使应用发生崩溃也是可以恢复的。
应用程序代码运行在用户模式下,当应用程序需要实现内核模式下的指令时,先向系统发送调用请求,操作系统收到请求后,执行系统调用接口,使处理器进入内核模式,当处理器完成系统调用操作后,OS会让处理器返回用户模式,继续执行用户代码。
连接用户模式和内核模式的接口称之为系统调用
应用程序中十大对文件的操作过程就是典型的系统调用过程。
虚拟文件系统 VFS
一个操作系统可以支持多种底层不同的文件系统(比如NTFS, FAT, ext3, ext4),通过使用同一套I/O系统调用即可对Linux中的任意文件进行操作而无需考虑其所在的具体文件系统格式,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS),进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作。
Linux进程的虚拟内存
进程的虚拟地址空间可分为两部分,内核空间和用户空间,内核空间中放的是内核代码和数据,而进程的用户空间中存放的是用户代码和程序。
1 | 1.虚拟的意思是进程以为自己有这么一大块内存,实际上物理内存可能还没有分配给它,等到缺页异常是系统才会分配, |
MMKV
MMKV是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。
原理:
- 内存准备
通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。 - 数据组织
数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。 - 写入优化
考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。 - 空间增长
使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。
mmap
为什么mmap()可以节约IO读写时间?
- 常规文件读写流程
- 读文件
1 | 1、进程调用库函数向内核发起读文件请求; |
- 写文件
1 | 写文件 |
- 内存映射读写流程
具体操作方式是:open一个文件,然后调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做其他的操作,比如memcpy等等。mmap并不分配物理地址空间,它只是占有进程的虚拟地址空间。这跟常规文件读写方式不一样的,常规文件读写方式需要预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。
而内存映射读写方式,当多个进程需要同时访问同一个文件时,每个进程都将文件所存储的内核高速缓冲映射到自己的进程地址空间。当第一个进程访问内核中的缓冲区时候,前面讲过并没有实际拷贝数据,这时MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,就会触发缺页中断。内核将文件的这一页数据读入到内核高速缓冲区中,并更新进程的页表,使页表指向内核缓冲中的这一页。之后有其他的进程再次访问这一页的时候,该页已经在内存中了,内核只需要将进程的页表登记并且指向内核的页高速缓冲区即可。
1 |
|
mmap和常规文件操作的区别
- 常规文件读写
两次拷贝 (磁盘->内核,内核->用户态)
常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这是由OS控制的。这样造成读文件时需要先将
文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数
据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任
务。
写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘
中(延迟写回),也是需要两次数据拷贝。当存在多个进程同时读取同一个文件时,每一个进程中的地址空间都会保存一份副本,这样肯定不是最优方式的,造成了物理内存的浪费
- mmap文件操作
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<—->用户空间两者之间需要大量数据传输等操作的话效率是非常高的
1 | 使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存 |