mmkv框架源碼淺析(下)

由于篇幅過(guò)長(zhǎng),就分成三篇來(lái)分析mmkv這塊內(nèi)容,雖然代碼量不多,但集成了一些基礎(chǔ)知識(shí),掌握基本知識(shí),學(xué)其他的會(huì)更快些。

八) 使用文件鎖來(lái)調(diào)度多進(jìn)程通過(guò)共享內(nèi)存協(xié)作

mmkv下面有一些源文件,其中ScopedLock.hpp通過(guò)ARII手段來(lái)通過(guò)構(gòu)造時(shí)lock,離開(kāi)函數(shù)作用域后析構(gòu)unlock,當(dāng)然實(shí)現(xiàn)中,拷貝構(gòu)造和賦值函數(shù)都用delete修飾;

native-bridge.cpp應(yīng)該是C++和java之間的通道,在java層面調(diào)用C++的邏輯;

ThreadLock.h/ThreadLock.cpp是個(gè)封裝pthread_mutex_t的鎖,其中在初始化鎖時(shí)有個(gè)屬性PTHREAD_MUTEX_RECURSIVE,表示可遞歸;

PBUtility.h/PBUtility.cpp如其名,是一些基本的接口類(lèi),有些是c++11的知識(shí)點(diǎn),比如constexpr可用于在編譯期就計(jì)算出來(lái)的表達(dá)式;類(lèi)型轉(zhuǎn)換如leveldb中的Varint類(lèi)型:

 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 }

上面實(shí)現(xiàn)算出一個(gè)int32_t用Varint類(lèi)型來(lái)表示,占用多少字節(jié),每個(gè)字節(jié)的最高位不存儲(chǔ)數(shù)據(jù),表示是否還有后續(xù)內(nèi)容;

MMBuffer.h/MMBuffer.cpp封裝了void *ptrsize_t size的緩沖區(qū),和leveldb中的slice有點(diǎn)區(qū)別,區(qū)別是否是對(duì)源地址內(nèi)容重新拷貝一份而不簡(jiǎn)單的指向同一個(gè)地址,但與redis中的sds有點(diǎn)類(lèi)似,不過(guò)后者是二進(jìn)制安全的,分配空間策略也與vector類(lèi)似;MMBuffer.cpp中也有移動(dòng)拷貝和賦值相關(guān)的接口;

InterProcessLock.h/InterProcessLock.cpp 改造了文件鎖,加了類(lèi)型和讀寫(xiě)引用計(jì)數(shù),實(shí)現(xiàn)原理參考之前的內(nèi)容;
MMKVLog.h是日志相關(guān)的接口實(shí)現(xiàn);
aes目錄下有不同的加解密實(shí)現(xiàn);

MMKVMetaInfo.hpp是用于存放在共享內(nèi)存中的元數(shù)據(jù):

 28 struct MMKVMetaInfo {
 29     uint32_t m_crcDigest = 0;
 30     uint32_t m_version = 1;  //版本號(hào)
 31     uint32_t m_sequence = 0; // full write-back count
 32     //more code...
 42 };

這個(gè)結(jié)構(gòu)是在多進(jìn)程同步數(shù)據(jù)的時(shí)候,用于檢查內(nèi)存地址,大小是否發(fā)生改變,具體使用和實(shí)現(xiàn)在下面說(shuō)明;

MmapedFile.h/MmapedFile.cpp封裝了共享內(nèi)存映mmap和munmap相關(guān)的實(shí)現(xiàn);這里有映射文件和/dev/shm,分析的時(shí)候會(huì)以文件為例,因?yàn)橛行┳⒁恻c(diǎn)在里面,可以參考這里認(rèn)真分析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<size_t>(st.st_size);
 46             }
 47             if (m_segmentSize < DEFAULT_MMAP_SIZE) {
 48                 m_segmentSize = static_cast<size_t>(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映射區(qū)域大小必須是物理頁(yè)大小(page_size)的整倍數(shù)(32位系統(tǒng)中通常是4k字節(jié))。原因是,內(nèi)存的最小粒度是頁(yè),而進(jìn)程虛擬地址空間和內(nèi)存的映射也是以頁(yè)為單位。為了匹配內(nèi)存的操作,mmap從磁盤(pán)到虛擬地址空間的映射也必須是頁(yè)?!?/p>

CodedInputData.h/CodedInputData.cpp封裝了數(shù)據(jù)的讀,可以有一定的格式:

 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); //其實(shí)從oData中讀數(shù)據(jù)
 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是封裝了寫(xiě)數(shù)據(jù):

 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); //寫(xiě)入ptr
 30 }
124 void CodedOutputData::writeRawLittleEndian32(int32_t value) {
125     this->writeRawByte(static_cast<uint8_t>((value) &0xff));
126     this->writeRawByte(static_cast<uint8_t>((value >> 8) & 0xff));
127     this->writeRawByte(static_cast<uint8_t>((value >> 16) & 0xff));
128     this->writeRawByte(static_cast<uint8_t>((value >> 24) & 0xff));
129 }  //把一個(gè)int32_t以小端格式寫(xiě)進(jìn)m_ptr中

100 void CodedOutputData::writeRawVarint32(int32_t value) {
101     while (true) {
102         if ((value & ~0x7f) == 0) {
103             this->writeRawByte(static_cast<uint8_t>(value));
104             return;
105         } else {
106             this->writeRawByte(static_cast<uint8_t>((value & 0x7F) | 0x80)); //先取七位并或上第八位,表示后面還有數(shù)據(jù)
107             value = logicalRightShift32(value, 7); //右移掉低七位
108         }
109     }
110 }  //以Varint格式寫(xiě)
 85 void CodedOutputData::writeRawByte(uint8_t value) {
 86     if (m_position == m_size) {
 88         return;
 89     }
 90 
 91     m_ptr[m_position++] = value;
 92 }

以上輸入輸出的實(shí)現(xiàn)還是比較好理解;但是需要注意在把int32_t有符號(hào)的類(lèi)型轉(zhuǎn)換成Varint類(lèi)型時(shí),移位可能補(bǔ)上符號(hào)位,這里需要特別處理下,相關(guān)的有無(wú)符號(hào)轉(zhuǎn)換表示等基礎(chǔ)知識(shí)可以參考下《深入理解計(jì)算機(jī)系統(tǒng)第二章節(jié)》:

 69 static inline uint32_t Int32ToUInt32(int32_t v) {
 70     Converter<int32_t, uint32_t> converter;
 71     converter.first = v;
 72     return converter.second;
 73 }

 26 template <typename T, typename P>
 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處理,舉例對(duì)string進(jìn)行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<PBEncodeItem>();
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<int32_t>(str.size());
 98     }
 99     encodeItem->compiledSize = pbRawVarint32Size(encodeItem->valueSize) + encodeItem->valueSize; //數(shù)據(jù)大小占用字節(jié)數(shù)加內(nèi)存本身占用的內(nèi)存
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 寫(xiě)進(jìn)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中,剩下的部分會(huì)分析重點(diǎn)部分,然后以一個(gè)讀和寫(xiě)操作來(lái)分析工作流程,其他的跳過(guò)。
以下是類(lèi)的聲明,數(shù)據(jù)成員如下:

 44 class MMKV {
 45     std::unordered_map<std::string, MMBuffer> m_dic;
 46     std::string m_mmapID;  //共享內(nèi)存id
 47     std::string m_path; //文件名,以它來(lái)舉例
 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; //加解密相關(guān)
 63 
 64     ThreadLock m_lock;  //支持遞歸加鎖
 65     FileLock m_fileLock;  //用于初始化讀寫(xiě)鎖的文件鎖
 66     InterProcessLock m_sharedProcessLock; //讀鎖
 67     InterProcessLock m_exclusiveProcessLock; //寫(xiě)鎖
 217 };

構(gòu)造:

  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) //是否是多進(jìn)程的
  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;//文件鎖是否用于多進(jìn)程,這樣加解鎖是否要真正進(jìn)行
  92     m_exclusiveProcessLock.m_enable = m_isInterProcess;//同上  93 
  94     // sensitive zone
  95     {
  96         SCOPEDLOCK(m_sharedProcessLock); //加讀鎖
  97         loadFromFile(); //加載數(shù)據(jù)
  98     }
  99 }

簡(jiǎn)化起見(jiàn),不考慮從/dev/shm中映射,不考慮cryptKey相關(guān)的邏輯,把里面的相關(guān)代碼給省略,對(duì)m_metaFile區(qū)域初始化文件鎖,因?yàn)樵诠蚕韮?nèi)存中,可能有多個(gè)進(jìn)程操作。

加載數(shù)據(jù):

 267 void MMKV::loadFromFile() {
 268     if (m_isAshmem) {
 270         return;
 271     }
 273     m_metaInfo.read(m_metaFile.getMemory()); //從共享內(nèi)存中讀元數(shù)據(jù)
 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<size_t>(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<size_t>(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進(jìn)行映射
 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主要是從共享內(nèi)存中讀元數(shù)據(jù),里面含有字段信息:m_crcDigest/m_version/m_sequence,具體作用在后面說(shuō)。
279到295開(kāi)始映射,并根據(jù)頁(yè)大小來(lái),然后從oldSize開(kāi)始初始化m_size - oldSize個(gè)字節(jié)內(nèi)容為0。當(dāng)然一開(kāi)始沒(méi)有文件的時(shí)候,初始化一頁(yè)都為0的內(nèi)容,然后進(jìn)行映射,此時(shí)進(jìn)行讀寫(xiě)m_ptr時(shí)就相當(dāng)于讀寫(xiě)文件。
一開(kāi)始文件中啥都沒(méi)有,所以不會(huì)執(zhí)行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             }

然后設(shè)置m_ptr開(kāi)始的Fixed32Size個(gè)字節(jié)偏移量處開(kāi)始寫(xiě),然后重新計(jì)算簽名recaculateCRCDigest,然后寫(xiě)入到共享內(nèi)存處,這樣當(dāng)其他進(jìn)入進(jìn)行寫(xiě)的時(shí)候,可能會(huì)做些額外處理:

 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 }

當(dāng)有文件內(nèi)容時(shí),先進(jìn)行校驗(yàn),通過(guò)的話則從文件中建立m_dic表并設(shè)置輸出:

 303             if (m_actualSize > 0) {
 304                 if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
 305                     if (checkFileCRCValid()) { //校驗(yàn)
 308                         MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy) ; //讀文件內(nèi)容
 309                         if (m_crypter) { //是否加密
 310                             decryptBuffer(*m_crypter, inputBuffer);
 311                         }
 312                         m_dic = MiniPBCoder::decodeMap(inputBuffer);//decode文件中的內(nèi)容
 313                         m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
 314                                                        m_size - Fixed32Size - m_actualSize); //設(shè)置輸出
 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         }//從元數(shù)據(jù)中讀出來(lái)的值跟文件內(nèi)容計(jì)算值一遍比較
 878     }
 879     return false;
 880 }

以上是加載的過(guò)程,從代碼實(shí)現(xiàn)上看,文件中內(nèi)容格式是:大小+內(nèi)容,但是大小只占頭4個(gè)字節(jié)。

以讀一個(gè)string為例來(lái)說(shuō)明讀的過(guò)程:

 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內(nèi)容
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); //沒(méi)找到,返回個(gè)特殊的
 652     return nan;
 653 }

下面這部分算是mmkv的核心設(shè)計(jì)了,當(dāng)從文件中加載后m_needLoadFromFilefalse,即不需要在每次查詢時(shí)是否重新load,但對(duì)于多進(jìn)程來(lái)說(shuō),可能在load后其他進(jìn)程更新了內(nèi)容,所以可能需要重新load:

 437 void MMKV::checkLoadData() {
 438     if (m_needLoadFromFile) {
 439         SCOPEDLOCK(m_sharedProcessLock);
 440 
 441         m_needLoadFromFile = false;
 442         loadFromFile();  //從文件加載,并重置狀態(tài)
 443         return;
 444     }
 445     if (!m_isInterProcess) {
 446         return;  //單進(jìn)程直接返回
 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(); //重置相關(guān)數(shù)據(jù)結(jié)構(gòu)
 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,從共享內(nèi)存中讀最新的元數(shù)據(jù),然后比較序列號(hào),不相等則內(nèi)存進(jìn)行了重整。
如果校驗(yàn)碼不一致,則內(nèi)容發(fā)生變化,這里檢查[文件]是否大小不同,如果不同則重新加載,如果不是則部分加載,因?yàn)榇藭r(shí)共內(nèi)存內(nèi)存中的數(shù)據(jù)可能還沒(méi)有msync到文件中去,只是側(cè)面反映共享內(nèi)存中的數(shù)據(jù)增加了:

 389 void MMKV::partialLoadFromFile() {
 390     m_metaInfo.read(m_metaFile.getMemory()); //獲取最新的元數(shù)據(jù)
 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<uInt>(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                     } //調(diào)整m_dic
 420                     m_output->seek(bufferSize);
 424                     return;
 425                 } else {
 428                 }
 429             }
 430         }
 431     }
 432     //到這里,說(shuō)明上面部分load出錯(cuò),進(jìn)行清理狀態(tài)并重新mmap
 433     clearMemoryState();
 434     loadFromFile();
 435 }

寫(xiě)及內(nèi)存變化:

 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);//加寫(xiě)鎖
 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); //更新元數(shù)據(jù)的校驗(yàn)碼
 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()) { //內(nèi)存不夠
 568         static const int offset = pbFixed32Size(0);
 569         MMBuffer data = MiniPBCoder::encodeDataWithObject(m_dic); //重寫(xiě)數(shù)據(jù),防止過(guò)多的修改和刪除導(dǎo)致占用過(guò)多的內(nèi)存空間,如redis的rewrite aof
 570         size_t lenNeeded = data.length() + offset + newSize;
 571         if (m_isAshmem) {
 572             if (lenNeeded > m_size) {//重寫(xiě)后還不夠
 575                 return false;
 576             }
 577         } else {
 578             size_t futureUsage = newSize * std::max<size_t>(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); //內(nèi)存空間增長(zhǎng)策略
 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                 } //調(diào)整文件大小成功
 597                 if (!zeroFillFile(m_fd, oldSize, m_size - oldSize)) {
 600                     m_size = oldSize;
 601                     return false;
 602                 } //初始化多出來(lái)的內(nèi)存為0
 603 
 604                 if (munmap(m_ptr, oldSize) != 0) {
 606                 }//munmap的時(shí)候把共享內(nèi)存中的數(shù)據(jù)寫(xiě)到文件,跟手動(dòng)調(diào)用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) { //校驗(yàn)相關(guān)
 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); //寫(xiě)rewrite后的數(shù)據(jù)
 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); //寫(xiě)鎖
1129     checkLoadData(); //數(shù)據(jù)是否是最新
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);  //刪除標(biāo)志
 682         return appendDataWithKey(nan, key);  ;//append(同寫(xiě))
 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 }

這塊一方面是由程序手動(dòng)調(diào)用的,但是在MMKV::onExit的時(shí)候。

這里有幾處設(shè)計(jì)思路可以學(xué)習(xí)下,一是如redis中的aof重寫(xiě),一開(kāi)始只管寫(xiě),當(dāng)文件達(dá)到一定大小后,把當(dāng)前m_dic快照rewrite下,刪除重復(fù)的對(duì)一個(gè)key的操作,以最后一個(gè)為準(zhǔn),相當(dāng)于延遲計(jì)算。還有如leveldb中對(duì)key的刪除,不真正刪除,只設(shè)置一個(gè)特殊標(biāo)志,當(dāng)合并的時(shí)檢查并作刪除,這時(shí)rewrite的時(shí)候以m_dic為準(zhǔn),當(dāng)然需要保證正常的key中不會(huì)含有特殊標(biāo)志。

另外在recaculateCRCDigest中會(huì)對(duì)序列號(hào)自增IncreaseSequence=true,其他進(jìn)程會(huì)重新mmap。在幾處可能會(huì)發(fā)起這個(gè)調(diào)用: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 }

其他的一些實(shí)現(xiàn)不多分析了,用到的時(shí)候再看看。這里刪除和修改只是簡(jiǎn)單的在文件后面append,之后用后面的覆蓋前面的,每次讀寫(xiě)和其他接口,都要嘗試是否重新load最新的數(shù)據(jù)。

總結(jié)下:這個(gè)開(kāi)源項(xiàng)目涉及到的知識(shí)點(diǎn)有多進(jìn)程通信共享內(nèi)存,文件鎖同步,遞歸鎖,鎖的升降級(jí)等和一些不錯(cuò)的設(shè)計(jì)實(shí)現(xiàn)。當(dāng)然里面還有一些注意點(diǎn),基礎(chǔ)知識(shí)不扎實(shí)可能會(huì)出踩坑。
當(dāng)然最重要的是根據(jù)相應(yīng)的業(yè)務(wù)需求來(lái)選擇適合的方案。

參考
msync
MMKV for Android 多進(jìn)程設(shè)計(jì)與實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 介紹 MMKV is an efficient, small, easy-to-use mobile key-va...
    土土Edmond木閱讀 1,154評(píng)論 0 1
  • 前言 好久沒(méi)有更新常用的第三方庫(kù)了。讓我們來(lái)聊聊MMKV這個(gè)常用的第三方庫(kù)。MMKV這個(gè)庫(kù)是做什么的呢?他本質(zhì)上的...
    yjy239閱讀 11,585評(píng)論 7 14
  • 這部分主要是分析微信開(kāi)源的mmkv框架,它的介紹在這里github[https://github.com/Tenc...
    fooboo閱讀 2,742評(píng)論 0 7
  • MMKV 簡(jiǎn)介 MMKV——基于 mmap 的高性能通用 key-value 組件MMKV 是基于 mmap 內(nèi)存...
    who_young閱讀 2,452評(píng)論 0 2
  • 這篇是跟mmkv相關(guān)的分析,上篇主要是一些基本知識(shí)介紹。 五) 文件結(jié)構(gòu)和文件鎖 粗略介紹下文件結(jié)構(gòu),引用網(wǎng)上一張...
    fooboo閱讀 906評(píng)論 0 1

友情鏈接更多精彩內(nèi)容