由于篇幅过长,就分成三篇来分析mmkv这块内容,虽然代码量不多,但集成了一些基础知识,掌握基本知识,学其他的会更快些。
八) 使用文件锁来调度多进程通过共享内存协作
mmkv下面有一些源文件,其中ScopedLock.hpp通过ARII手段来通过构造时lock,离开函数作用域后析构unlock,当然实现中,拷贝构造和赋值函数都用delete修饰;
native-bridge.cpp应该是C++和java之间的通道,在java层面调用C++的逻辑;
ThreadLock.h/ThreadLock.cpp是个封装pthread_mutex_t
的锁,其中在初始化锁时有个属性PTHREAD_MUTEX_RECURSIVE
,表示可递归;
PBUtility.h/PBUtility.cpp如其名,是一些基本的接口类,有些是c++11的知识点,比如constexpr可用于在编译期就计算出来的表达式;类型转换如leveldb中的Varint类型:
23 uint32_t pbRawVarint32Size(int32_t value) {
24 if ((value & (0xffffffff << 7)) == 0) {
25 return 1;
26 } else if ((value & (0xffffffff << 14)) == 0) {
27 return 2;
28 } else if ((value & (0xffffffff << 21)) == 0) {
29 return 3;
30 } else if ((value & (0xffffffff << 28)) == 0) {
31 return 4;
32 }
33 return 5;
34 }
上面实现算出一个int32_t用Varint类型来表示,占用多少字节,每个字节的最高位不存储数据,表示是否还有后续内容;
MMBuffer.h/MMBuffer.cpp封装了void *ptr
和size_t size
的缓冲区,和leveldb中的slice有点区别,区别是否是对源地址内容重新拷贝一份而不简单的指向同一个地址,但与redis中的sds有点类似,不过后者是二进制安全的,分配空间策略也与vector类似;MMBuffer.cpp中也有移动拷贝和赋值相关的接口;
InterProcessLock.h/InterProcessLock.cpp 改造了文件锁,加了类型和读写引用计数,实现原理参考之前的内容;
MMKVLog.h是日志相关的接口实现;
aes目录下有不同的加解密实现;
MMKVMetaInfo.hpp是用于存放在共享内存中的元数据:
28 struct MMKVMetaInfo {
29 uint32_t m_crcDigest = 0;
30 uint32_t m_version = 1; //版本号
31 uint32_t m_sequence = 0; // full write-back count
32 //more code...
42 };
这个结构是在多进程同步数据的时候,用于检查内存地址,大小是否发生改变,具体使用和实现在下面说明;
MmapedFile.h/MmapedFile.cpp封装了共享内存映mmap和munmap相关的实现;这里有映射文件和/dev/shm
,分析的时候会以文件为例,因为有些注意点在里面,可以参考这里认真分析mmap:是什么 为什么 怎么用
40 class MmapedFile {
41 std::string m_name;
42 int m_fd;
43 void *m_segmentPtr;
44 size_t m_segmentSize;
66 };
36 MmapedFile::MmapedFile(const std::string &path, size_t size, bool fileType)
37 : m_name(path), m_fd(-1), m_segmentPtr(nullptr),m_segmentSize(0), m_fileType(fileType) {
38 if (m_fileType == MMAP_FILE) {
39 m_fd = open(m_name.c_str(), O_RDWR | O_CREAT, S_IRWXU);
40 if (m_fd < 0) {
42 } else {
43 struct stat st = {};
44 if (fstat(m_fd, &st) != -1) {
45 m_segmentSize = static_cast(st.st_size);
46 }
47 if (m_segmentSize < DEFAULT_MMAP_SIZE) {
48 m_segmentSize = static_cast(DEFAULT_MMAP_SIZE);
49 if (ftruncate(m_fd, m_segmentSize) != 0 || !zeroFillFile(m_fd, 0, m_segmentSize)) {
51 m_segmentSize, strerror(errno));
52 close(m_fd);
53 m_fd = -1;
54 removeFile(m_name);
55 return;
56 }
57 }
58 m_segmentPtr =
59 (char *) mmap(nullptr, m_segmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
60 if (m_segmentPtr == MAP_FAILED) {
62 close(m_fd);
63 m_fd = -1;
64 m_segmentPtr = nullptr;
65 }
66 }
67 } else {
68 m_fd = open(ASHMEM_NAME_DEF, O_RDWR);
//more code...
91 }
92 }
121 MmapedFile::~MmapedFile() {
122 if (m_segmentPtr != MAP_FAILED && m_segmentPtr != nullptr) {
123 munmap(m_segmentPtr, m_segmentSize);
124 m_segmentPtr = nullptr;
125 }
126 if (m_fd >= 0) {
127 close(m_fd);
128 m_fd = -1;
129 }
130 }
引用“mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。”
CodedInputData.h/CodedInputData.cpp封装了数据的读,可以有一定的格式:
28 class CodedInputData {
29 uint8_t *m_ptr;
30 int32_t m_size;
31 int32_t m_position;
63 };
28 CodedInputData::CodedInputData(const void *oData, int32_t length)
29 : m_ptr((uint8_t *) oData), m_size(length), m_position(0) {
30 assert(m_ptr); //其实从oData中读数据
31 }
87 MMBuffer CodedInputData::readData() {
88 int32_t size = this->readRawVarint32();
89 if (size < 0) {
91 return MMBuffer(0);
92 }
93
94 if (size <= m_size - m_position) {
95 MMBuffer data(((int8_t *) m_ptr) + m_position, size);
96 m_position += size;
97 return data;
98 } else {
100 return MMBuffer(0);
101 }
102 }
162 int8_t CodedInputData::readRawByte() {
163 if (m_position == m_size) {
165 return 0;
166 }
167 int8_t *bytes = (int8_t *) m_ptr;
168 return bytes[m_position++];
169 }
CodedOutputData.h/CodedOutputData.cpp是封装了写数据:
28 class CodedOutputData {
29 uint8_t *m_ptr;
30 size_t m_size;
31 int32_t m_position;
67 };
27 CodedOutputData::CodedOutputData(void *ptr, size_t len)
28 : m_ptr((uint8_t *) ptr), m_size(len), m_position(0) {
29 assert(m_ptr); //写入ptr
30 }
124 void CodedOutputData::writeRawLittleEndian32(int32_t value) {
125 this->writeRawByte(static_cast((value) &0xff));
126 this->writeRawByte(static_cast((value >> 8) & 0xff));
127 this->writeRawByte(static_cast((value >> 16) & 0xff));
128 this->writeRawByte(static_cast((value >> 24) & 0xff));
129 } //把一个int32_t以小端格式写进m_ptr中
100 void CodedOutputData::writeRawVarint32(int32_t value) {
101 while (true) {
102 if ((value & ~0x7f) == 0) {
103 this->writeRawByte(static_cast(value));
104 return;
105 } else {
106 this->writeRawByte(static_cast((value & 0x7F) | 0x80)); //先取七位并或上第八位,表示后面还有数据
107 value = logicalRightShift32(value, 7); //右移掉低七位
108 }
109 }
110 } //以Varint格式写
85 void CodedOutputData::writeRawByte(uint8_t value) {
86 if (m_position == m_size) {
88 return;
89 }
90
91 m_ptr[m_position++] = value;
92 }
以上输入输出的实现还是比较好理解;但是需要注意在把int32_t
有符号的类型转换成Varint
类型时,移位可能补上符号位,这里需要特别处理下,相关的有无符号转换表示等基础知识可以参考下《深入理解计算机系统第二章节》:
69 static inline uint32_t Int32ToUInt32(int32_t v) {
70 Converter converter;
71 converter.first = v;
72 return converter.second;
73 }
26 template
27 union Converter {
28 static_assert(sizeof(T) == sizeof(P), "size not match");
29 T first;
30 P second;
31 };
MiniPBCoder.h/MiniPBCoder.cpp用于Encode/decode处理,举例对string进行Encode/decode:
232 string MiniPBCoder::decodeOneString() {
233 return m_inputData->readString();
234 }
235
273 string MiniPBCoder::decodeString(const MMBuffer &oData) {
274 MiniPBCoder oCoder(&oData);
275 return oCoder.decodeOneString();
276 }
174 MMBuffer MiniPBCoder::getEncodeData(const string &str) {
175 m_encodeItems = new vector();
176 size_t index = prepareObjectForEncode(str);
177 PBEncodeItem *oItem = (index < m_encodeItems->size()) ? &(*m_encodeItems)[index] : nullptr;
178 if (oItem && oItem->compiledSize > 0) {
179 m_outputBuffer = new MMBuffer(oItem->compiledSize);
180 m_outputData = new CodedOutputData(m_outputBuffer->getPtr(), m_outputBuffer->length());
181
182 writeRootObject();
183 }
184
185 return move(*m_outputBuffer);
186 }
90 size_t MiniPBCoder::prepareObjectForEncode(const string &str) {
91 m_encodeItems->push_back(PBEncodeItem());
92 PBEncodeItem *encodeItem = &(m_encodeItems->back());
93 size_t index = m_encodeItems->size() - 1;
94 {
95 encodeItem->type = PBEncodeItemType_String;
96 encodeItem->value.strValue = &str;
97 encodeItem->valueSize = static_cast(str.size());
98 }
99 encodeItem->compiledSize = pbRawVarint32Size(encodeItem->valueSize) + encodeItem->valueSize; //数据大小占用字节数加内存本身占用的内存
100
101 return index;
102 }
66 void MiniPBCoder::writeRootObject() {
67 for (size_t index = 0, total = m_encodeItems->size(); index < total; index++) {
68 PBEncodeItem *encodeItem = &(*m_encodeItems)[index];
69 switch (encodeItem->type) {
70 case PBEncodeItemType_String: {
71 m_outputData->writeString(*(encodeItem->value.strValue));
72 break; //依次把string item 写进m_outputData
73 }
74 case PBEncodeItemType_Data: {
75 m_outputData->writeData(*(encodeItem->value.bufferValue));
76 break;
77 }
78 case PBEncodeItemType_Container: {
79 m_outputData->writeRawVarint32(encodeItem->valueSize);
80 break;
81 }
82 case PBEncodeItemType_None: {
84 break;
85 }
86 }
87 }
88 }
以上所介绍的都是基本的,主要的逻辑在MMKV.h/MMKV.cpp中,剩下的部分会分析重点部分,然后以一个读和写操作来分析工作流程,其他的跳过。
以下是类的声明,数据成员如下:
44 class MMKV {
45 std::unordered_map m_dic;
46 std::string m_mmapID; //共享内存id
47 std::string m_path; //文件名,以它来举例
48 std::string m_crcPath;
49 int m_fd;
50 char *m_ptr;
51 size_t m_size;
52 size_t m_actualSize;
53 CodedOutputData *m_output;
54 MmapedFile *m_ashmemFile;
55
56 bool m_needLoadFromFile;
57
58 uint32_t m_crcDigest;
59 MmapedFile m_metaFile;
60 MMKVMetaInfo m_metaInfo;
61
62 AESCrypt *m_crypter; //加解密相关
63
64 ThreadLock m_lock; //支持递归加锁
65 FileLock m_fileLock; //用于初始化读写锁的文件锁
66 InterProcessLock m_sharedProcessLock; //读锁
67 InterProcessLock m_exclusiveProcessLock; //写锁
217 };
构造:
59 MMKV::MMKV(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey)
60 : m_mmapID(mmapID)
61 , m_path(mappedKVPathWithID(m_mmapID, mode))
62 , m_crcPath(crcPathWithID(m_mmapID, mode))
63 , m_metaFile(m_crcPath, DEFAULT_MMAP_SIZE, (mode & MMKV_ASHMEM) ? MMAP_ASHMEM : MMAP_FILE)
64 , m_crypter(nullptr)
65 , m_fileLock(m_metaFile.getFd())
66 , m_sharedProcessLock(&m_fileLock, SharedLockType)
67 , m_exclusiveProcessLock(&m_fileLock, ExclusiveLockType)
68 , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0) //是否是多进程的
69 , m_isAshmem((mode & MMKV_ASHMEM) != 0) {
70 m_fd = -1;
71 m_ptr = nullptr;
72 m_size = 0;
73 m_actualSize = 0;
74 m_output = nullptr;
75
76 if (m_isAshmem) {
79 } else {
80 m_ashmemFile = nullptr;
81 }
82
87 m_needLoadFromFile = true;
88
89 m_crcDigest = 0;
90
91 m_sharedProcessLock.m_enable = m_isInterProcess;//文件锁是否用于多进程,这样加解锁是否要真正进行
92 m_exclusiveProcessLock.m_enable = m_isInterProcess;//同上 93
94 // sensitive zone
95 {
96 SCOPEDLOCK(m_sharedProcessLock); //加读锁
97 loadFromFile(); //加载数据
98 }
99 }
简化起见,不考虑从/dev/shm
中映射,不考虑cryptKey
相关的逻辑,把里面的相关代码给省略,对m_metaFile
区域初始化文件锁,因为在共享内存中,可能有多个进程操作。
加载数据:
267 void MMKV::loadFromFile() {
268 if (m_isAshmem) {
270 return;
271 }
273 m_metaInfo.read(m_metaFile.getMemory()); //从共享内存中读元数据
274
275 m_fd = open(m_path.c_str(), O_RDWR | O_CREAT, S_IRWXU);
278 } else {
279 m_size = 0;
280 struct stat st = {0};
281 if (fstat(m_fd, &st) != -1) {
282 m_size = static_cast(st.st_size);
283 }
284 // round up to (n * pagesize)
285 if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) {
286 size_t oldSize = m_size;
287 m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
288 if (ftruncate(m_fd, m_size) != 0) {
291 m_size = static_cast(st.st_size);
292 }
293 zeroFillFile(m_fd, oldSize, m_size - oldSize);
294 }
295 m_ptr = (char *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); //以MAP_SHARED进行映射
296 if (m_ptr == MAP_FAILED) {
298 } else {
299 memcpy(&m_actualSize, m_ptr, Fixed32Size);
302 bool loaded = false;
303 if (m_actualSize > 0) {
304 if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
305 if (checkFileCRCValid()) {
308 MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy);
309 if (m_crypter) {
310 decryptBuffer(*m_crypter, inputBuffer);
311 }
312 m_dic = MiniPBCoder::decodeMap(inputBuffer);
313 m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
314 m_size - Fixed32Size - m_actualSize);
315 loaded = true;
316 }
317 }
318 }
代码行273主要是从共享内存中读元数据,里面含有字段信息:m_crcDigest/m_version/m_sequence
,具体作用在后面说。
279到295开始映射,并根据页大小来,然后从oldSize
开始初始化m_size - oldSize
个字节内容为0。当然一开始没有文件的时候,初始化一页都为0的内容,然后进行映射,此时进行读写m_ptr
时就相当于读写文件。
一开始文件中啥都没有,所以不会执行303〜315逻辑:
319 if (!loaded) {
320 SCOPEDLOCK(m_exclusiveProcessLock);
321
322 if (m_actualSize > 0) {
323 writeAcutalSize(0);
324 }
325 m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size);
326 recaculateCRCDigest();
327 }
然后设置m_ptr
开始的Fixed32Size
个字节偏移量处开始写,然后重新计算签名recaculateCRCDigest
,然后写入到共享内存处,这样当其他进入进行写的时候,可能会做些额外处理:
890 void MMKV::updateCRCDigest(const uint8_t *ptr, size_t length, bool increaseSequence) {
891 if (!ptr) {
892 return;
893 }
894 m_crcDigest = (uint32_t) crc32(m_crcDigest, ptr, (uint32_t) length);
895
896 void *crcPtr = m_metaFile.getMemory();
897 if (crcPtr == nullptr || crcPtr == MAP_FAILED) {
898 return;
899 }
900
901 m_metaInfo.m_crcDigest = m_crcDigest;
902 if (increaseSequence) {
903 m_metaInfo.m_sequence++;
904 }
905 if (m_metaInfo.m_version == 0) {
906 m_metaInfo.m_version = 1;
907 }
908 m_metaInfo.write(crcPtr);
909 }
当有文件内容时,先进行校验,通过的话则从文件中建立m_dic表并设置输出:
303 if (m_actualSize > 0) {
304 if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
305 if (checkFileCRCValid()) { //校验
308 MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy) ; //读文件内容
309 if (m_crypter) { //是否加密
310 decryptBuffer(*m_crypter, inputBuffer);
311 }
312 m_dic = MiniPBCoder::decodeMap(inputBuffer);//decode文件中的内容
313 m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
314 m_size - Fixed32Size - m_actualSize); //设置输出
315 loaded = true;
316 }
317 }
318 }
867 bool MMKV::checkFileCRCValid() {
868 if (m_ptr && m_ptr != MAP_FAILED) {
869 constexpr int offset = pbFixed32Size(0);
870 m_crcDigest =
871 (uint32_t) crc32(0, (const uint8_t *) m_ptr + offset, (uint32_t) m_actualSize);
872 m_metaInfo.read(m_metaFile.getMemory());
873 if (m_crcDigest == m_metaInfo.m_crcDigest) {
874 return true;
875 }//从元数据中读出来的值跟文件内容计算值一遍比较
878 }
879 return false;
880 }
以上是加载的过程,从代码实现上看,文件中内容格式是:大小+内容,但是大小只占头4个字节。
以读一个string为例来说明读的过程:
997 bool MMKV::getStringForKey(const std::string &key, std::string &result) {
998 if (key.empty()) {
999 return false;
1000 }
1001 auto &data = getDataForKey(key);
1002 if (data.length() > 0) {
1003 result = MiniPBCoder::decodeString(data); //decode内容
1004 return true;
1005 }
1006 return false;
1007 }
644 const MMBuffer &MMKV::getDataForKey(const std::string &key) {
645 SCOPEDLOCK(m_lock);
646 checkLoadData(); //检查是否要重新load
647 auto itr = m_dic.find(key);
648 if (itr != m_dic.end()) {
649 return itr->second;
650 }
651 static MMBuffer nan(0); //没找到,返回个特殊的
652 return nan;
653 }
下面这部分算是mmkv的核心设计了,当从文件中加载后m_needLoadFromFile
为false
,即不需要在每次查询时是否重新load,但对于多进程来说,可能在load后其他进程更新了内容,所以可能需要重新load:
437 void MMKV::checkLoadData() {
438 if (m_needLoadFromFile) {
439 SCOPEDLOCK(m_sharedProcessLock);
440
441 m_needLoadFromFile = false;
442 loadFromFile(); //从文件加载,并重置状态
443 return;
444 }
445 if (!m_isInterProcess) {
446 return; //单进程直接返回
447 }
448
449 // TODO: atomic lock m_metaFile?
450 MMKVMetaInfo metaInfo;
451 metaInfo.read(m_metaFile.getMemory());
452 if (m_metaInfo.m_sequence != metaInfo.m_sequence) {
455 SCOPEDLOCK(m_sharedProcessLock);
456
457 clearMemoryState(); //重置相关数据结构
458 loadFromFile(); //重新从文件中mmap
459 } else if (m_metaInfo.m_crcDigest != metaInfo.m_crcDigest) {
462 SCOPEDLOCK(m_sharedProcessLock);
463
464 size_t fileSize = 0;
465 if (m_isAshmem) {
466 fileSize = m_size;
467 } else {
468 struct stat st = {0};
469 if (fstat(m_fd, &st) != -1) {
470 fileSize = (size_t) st.st_size;
471 }
472 }
473 if (m_size != fileSize) {
476 clearMemoryState();
477 loadFromFile();
478 } else {
479 partialLoadFromFile();
480 }
481 }
482 }
代码450〜458,从共享内存中读最新的元数据,然后比较序列号,不相等则内存进行了重整。
如果校验码不一致,则内容发生变化,这里检查[文件]是否大小不同,如果不同则重新加载,如果不是则部分加载,因为此时共内存内存中的数据可能还没有msync到文件中去,只是侧面反映共享内存中的数据增加了:
389 void MMKV::partialLoadFromFile() {
390 m_metaInfo.read(m_metaFile.getMemory()); //获取最新的元数据
391
392 size_t oldActualSize = m_actualSize;
393 memcpy(&m_actualSize, m_ptr, Fixed32Size);
397 if (m_actualSize > 0) {
398 if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
399 if (m_actualSize > oldActualSize) {
400 size_t bufferSize = m_actualSize - oldActualSize; //部分加载
401 MMBuffer inputBuffer(m_ptr + Fixed32Size + oldActualSize, bufferSize,
402 MMBufferNoCopy);
403 // incremental update crc digest
404 m_crcDigest = (uint32_t) crc32(m_crcDigest, (const uint8_t *) inputBuffer.getPtr(),
405 static_cast(inputBuffer.length()));
406 if (m_crcDigest == m_metaInfo.m_crcDigest) {
407 if (m_crypter) {
408 decryptBuffer(*m_crypter, inputBuffer);
409 }
410 auto dic = MiniPBCoder::decodeMap(inputBuffer, bufferSize);
411 for (auto &itr : dic) {
412 //m_dic[itr.first] = std::move(itr.second);
413 auto target = m_dic.find(itr.first);
414 if (target == m_dic.end()) {
415 m_dic.emplace(itr.first, std::move(itr.second));
416 } else {
417 target->second = std::move(itr.second);
418 }
419 } //调整m_dic
420 m_output->seek(bufferSize);
424 return;
425 } else {
428 }
429 }
430 }
431 }
432 //到这里,说明上面部分load出错,进行清理状态并重新mmap
433 clearMemoryState();
434 loadFromFile();
435 }
写及内存变化:
913 bool MMKV::setStringForKey(const std::string &value, const std::string &key) {
914 if (key.empty()) {
915 return false;
916 }
917 auto data = MiniPBCoder::encodeDataWithObject(value);
918 return setDataForKey(std::move(data), key);
919 }
655 bool MMKV::setDataForKey(MMBuffer &&data, const std::string &key) {
656 if (data.length() == 0 || key.empty()) {
657 return false;
658 }
659 SCOPEDLOCK(m_lock);
660 SCOPEDLOCK(m_exclusiveProcessLock);
661 checkLoadData();
662
663 // m_dic[key] = std::move(data);
664 auto itr = m_dic.find(key);
665 if (itr == m_dic.end()) {
666 itr = m_dic.emplace(key, std::move(data)).first;
667 } else {
668 itr->second = std::move(data);
669 }
670
671 return appendDataWithKey(itr->second, key);
672 }
688 bool MMKV::appendDataWithKey(const MMBuffer &data, const std::string &key) {
689 size_t keyLength = key.length();
690 // size needed to encode the key
691 size_t size = keyLength + pbRawVarint32Size((int32_t) keyLength);
692 // size needed to encode the value
693 size += data.length() + pbRawVarint32Size((int32_t) data.length());
694
695 SCOPEDLOCK(m_exclusiveProcessLock);//加写锁
696
697 bool hasEnoughSize = ensureMemorySize(size);//检查是否有足够的空间
698
699 if (!hasEnoughSize || !isFileValid()) {
700 return false;
701 }
702 if (m_actualSize == 0) {
703 auto allData = MiniPBCoder::encodeDataWithObject(m_dic);
704 if (allData.length() > 0) {
705 if (m_crypter) {
706 m_crypter->reset();
707 auto ptr = (unsigned char *) allData.getPtr();
708 m_crypter->encrypt(ptr, ptr, allData.length());
709 }
710 writeAcutalSize(allData.length());
711 m_output->writeRawData(allData); // note: don't write size of data
712 recaculateCRCDigest();
713 return true;
714 }
715 return false;
716 } else {
717 writeAcutalSize(m_actualSize + size); //更新文件大小
718 m_output->writeString(key);
719 m_output->writeData(data); // note: write size of data
720
721 auto ptr = (uint8_t *) m_ptr + Fixed32Size + m_actualSize - size;
722 if (m_crypter) {
723 m_crypter->encrypt(ptr, ptr, size);
724 }
725 updateCRCDigest(ptr, size, KeepSequence); //更新元数据的校验码
726
727 return true;
728 }
729 }
636 void MMKV::writeAcutalSize(size_t actualSize) {
640 memcpy(m_ptr, &actualSize, Fixed32Size); //更新文件大小
641 m_actualSize = actualSize;
642 }
先检查是否有足够的空间ensureMemorySize
:
560 bool MMKV::ensureMemorySize(size_t newSize) {
561 if (!isFileValid()) {
563 return false;
564 }
565
566 if (newSize >= m_output->spaceLeft()) { //内存不够
568 static const int offset = pbFixed32Size(0);
569 MMBuffer data = MiniPBCoder::encodeDataWithObject(m_dic); //重写数据,防止过多的修改和删除导致占用过多的内存空间,如redis的rewrite aof
570 size_t lenNeeded = data.length() + offset + newSize;
571 if (m_isAshmem) {
572 if (lenNeeded > m_size) {//重写后还不够
575 return false;
576 }
577 } else {
578 size_t futureUsage = newSize * std::max(8, (m_dic.size() + 1) / 2);
581 if (lenNeeded >= m_size || (lenNeeded + futureUsage) >= m_size) {
582 size_t oldSize = m_size;
583 do {
584 m_size *= 2;
585 } while (lenNeeded + futureUsage >= m_size); //内存空间增长策略
589
590 // if we can't extend size, rollback to old state
591 if (ftruncate(m_fd, m_size) != 0) {
594 m_size = oldSize;
595 return false;
596 } //调整文件大小成功
597 if (!zeroFillFile(m_fd, oldSize, m_size - oldSize)) {
600 m_size = oldSize;
601 return false;
602 } //初始化多出来的内存为0
603
604 if (munmap(m_ptr, oldSize) != 0) {
606 }//munmap的时候把共享内存中的数据写到文件,跟手动调用msync效果一样
607 m_ptr = (char *) mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); //重新mmap
608 if (m_ptr == MAP_FAILED) {
610 }
611
612 // check if we fail to make more space
613 if (!isFileValid()) {
615 return false;
616 }
617 }
618 }
619
620 if (m_crypter) { //校验相关
621 m_crypter->reset();
622 auto ptr = (unsigned char *) data.getPtr();
623 m_crypter->encrypt(ptr, ptr, data.length());
624 }
625
626 writeAcutalSize(data.length());
627
628 delete m_output;
629 m_output = new CodedOutputData(m_ptr + offset, m_size - offset);
630 m_output->writeRawData(data); //写rewrite后的数据
631 recaculateCRCDigest();
632 }
633 return true;
634 }
删除:
1123 void MMKV::removeValueForKey(const std::string &key) {
1124 if (key.empty()) {
1125 return;
1126 }
1127 SCOPEDLOCK(m_lock);
1128 SCOPEDLOCK(m_exclusiveProcessLock); //写锁
1129 checkLoadData(); //数据是否是最新
1130
1131 removeDataForKey(key);
1132 }
674 bool MMKV::removeDataForKey(const std::string &key) {
675 if (key.empty()) {
676 return false;
677 }
678
679 auto deleteCount = m_dic.erase(key);
680 if (deleteCount > 0) {
681 static MMBuffer nan(0); //删除标志
682 return appendDataWithKey(nan, key); ;//append(同写)
683 }
684
685 return false;
686 }
持久化:
1154 void MMKV::sync() {
1155 SCOPEDLOCK(m_lock);
1156 if (m_needLoadFromFile || !isFileValid()) {
1157 return;
1158 }
1159 SCOPEDLOCK(m_exclusiveProcessLock);
1160 if (msync(m_ptr, m_size, MS_SYNC) != 0) {
1162 }
1163 }
这块一方面是由程序手动调用的,但是在MMKV::onExit
的时候。
这里有几处设计思路可以学习下,一是如redis中的aof重写,一开始只管写,当文件达到一定大小后,把当前m_dic快照rewrite下,删除重复的对一个key的操作,以最后一个为准,相当于延迟计算。还有如leveldb中对key的删除,不真正删除,只设置一个特殊标志,当合并的时检查并作删除,这时rewrite的时候以m_dic为准,当然需要保证正常的key中不会含有特殊标志。
另外在recaculateCRCDigest
中会对序列号自增IncreaseSequence=true
,其他进程会重新mmap。在几处可能会发起这个调用:fullWriteback/loadFromFile/ensureMemorySize
:
882 void MMKV::recaculateCRCDigest() {
883 if (m_ptr && m_ptr != MAP_FAILED) {
884 m_crcDigest = 0;
885 constexpr int offset = pbFixed32Size(0);
886 updateCRCDigest((const uint8_t *) m_ptr + offset, m_actualSize, IncreaseSequence);
887 }
888 }
其他的一些实现不多分析了,用到的时候再看看。这里删除和修改只是简单的在文件后面append,之后用后面的覆盖前面的,每次读写和其他接口,都要尝试是否重新load最新的数据。
总结下:这个开源项目涉及到的知识点有多进程通信共享内存,文件锁同步,递归锁,锁的升降级等和一些不错的设计实现。当然里面还有一些注意点,基础知识不扎实可能会出踩坑。
当然最重要的是根据相应的业务需求来选择适合的方案。
参考
msync
MMKV for Android 多进程设计与实现