Octopus 源码阅读(一)
Octopus 源码阅读——fs部分
开源代码
bitmap.cpp
bitmap中的代码基本上没啥好说的,比较清楚。不过不解的是为什么在初始化的时候要统计freecount,理论上buffer不是应该可以直接初始化成全零吗?
Bitmap::Bitmap(uint64_t count, char *buffer) /* Constructor of bitmap. */
{if (count % 8 == 0) {if (buffer != NULL) { /* Judge if buffer is null or not. */bytes = (uint8_t *)buffer; /* Assign byte array of bitmap. */varCountTotal = count; /* Initialize count of total bits. */varCountFree = 0; /* Initialize count of free bits. */for (unsigned int index = 0; index < varCountTotal / 8; index++) { /* Search in all bytes in array. */for (int offset = 0; offset <= 7; offset++) { /* Search in all offsets in byte. */if ((bytes[index] & (1 << (7 - offset))) == 0) { /* Judge if bit is cleared. */varCountFree++; /* Increment of free bit. */}}}} else {fprintf(stderr, "Bitmap: buffer pointer is null.\n");exit(EXIT_FAILURE); /* Fail due to null buffer pointer. */}} else {fprintf(stderr, "Bitmap: count should be times of eight.\n");exit(EXIT_FAILURE); /* Fail due to count alignment. */}
}
dmfs.cpp
入口
filesystem.cpp
这个2k+的代码量看着就刺激。。。正式开始:
分布式事务实现
这部分相关的函数是Tx开头的一些,具体的实现在RPCServer中实现(因为要先找到TxManager),这个实现主要是因为要保证metadata的一致性,所以在mknod之类的时候需要分布式事务的支持。
FileSystem
首先比较建议和RPCServer的代码一起看,会比较清晰。
有些比较直观的函数就不贴出来了。
updateRemoteMeta
更新remote Metadata,分为两种情况:
需要更新的metadata的存储位置在本地:直接在本地memcpy即可
需要更新的metadata的存储位置不在本地:使用RDMA的write_with_imm来进行处理,RDMA write + read
void FileSystem::updateRemoteMeta(uint16_t parentNodeID, DirectoryMeta *meta, uint64_t parentMetaAddress, uint64_t parentHashAddress) {Debug::debugTitle("updateRemoteMeta");/* Prepare imm data. */uint32_t imm, temp;/*| 12b | 20b |+-------+------------+| 0XFFF | HashAdress |+-------+------------+*/temp = 0XFFF;imm = (temp << 20);imm += (uint32_t)parentHashAddress;/* Remote write with imm. */uint64_t SendBuffer;/* 得到发送端的buffer地址(根据NodeID) */server->getMemoryManagerInstance()->getServerSendAddress(parentNodeID, &SendBuffer);uint64_t RemoteBuffer = parentMetaAddress;Debug::debugItem("imm = %x, SendBuffer = %lx, RemoteBuffer = %lx", imm, SendBuffer, RemoteBuffer);if (parentNodeID == server->getRdmaSocketInstance()->getNodeID()) {/* 如果在本地,那么从参数meta的地址直接memcpy到RemoteBuffer为基址的相应位置即可 */memcpy((void *)(RemoteBuffer + server->getMemoryManagerInstance()->getDmfsBaseAddress()), (void *)meta, sizeof(DirectoryMeta));//unlockWriteHashItem(0, parentNodeID, parentHashAddress);return;}/* 如果更新的双方不在一台机器上,先把meta中的数据复制到sendbuffer */uint64_t size = sizeof(DirectoryMeta) - sizeof(DirectoryMetaTuple) * (MAX_DIRECTORY_COUNT - meta->count);memcpy((void *)SendBuffer, (void *)meta, size);server->getRdmaSocketInstance()->RdmaWrite(parentNodeID, SendBuffer, RemoteBuffer, size, -1, 1);server->getRdmaSocketInstance()->RdmaRead(parentNodeID, SendBuffer, RemoteBuffer, size, 1);/* Data will be written to the remote address, and lock will be released with the assist of imm data. *//* WRITE READ will be send after that, flushing remote data. */
}
RDMA call
server得到client,执行RDMACall(细节待补充)
getParentDirectory
/* Get parent directory. Examples: "/parent/file" -> "/parent" return true"/file" -> "/" return true"/" -> return false@param path Path.@param parent Buffer to hold parent path.@return If succeed return true, otherwise return false. */
getNameFromPath
/* Get file name from path. Examples: '/parent/file' -> 'file' return true'/file' -> 'file' return true'/' -> return false@param path Path.@param name Buffer to hold file name.@return If succeed return true, otherwise return false. */
parseMessage
根据sendMessage的不同执行不同的动作,所以会看到主体结构是一个switch case。代码里接下来对这些用到的功能函数进行了实现。
addMetaToDirectory
根据hashUnique来进行区分,如果hashNode在本地,那么直接更新本地的DirectoryTable,然后再更新分布式事务即可。
如果hashNode不在本地,那么需要给对应的Node发送一个ADDMETATODIRECTORY类型的message。
removeMetaToDirectory
同样的,remove也需要分成本地和远端,如果是本地,就直接重新整理chainItems,把需要remove的Meta覆盖掉,之后更新分布式事务log即可。
如果是远端,那么就需要给对应的hashNode发送一个REMOVEMETATODIRECTORY类型的message。
updateDirectoryMeta
本地:直接memcpy对应的metadata即可。
远端:发送一个对应类型的message
mknodWithMeta
创建实际的文件时使用。
实际上mknod只有本地方式,Octopus保证了一个file的data block和inode在一个机器上,用户可以在机器A上创建文件a,但是文件a实际存储在机器B上,而且文件a的data blocks和inode都存储在机器B上,并不会说inode存在A上,data blocks存在B上这种情况。(个人理解:这是比较显然的,不然还会需要不止一次hash)。
(个人理解:也就是说,实际上分布式文件系统并不能做到把整个集群看作一个机器,其中还是存在一定的限制)
直接通过实例化的storage在inode table中创建一个inode即可,具体的create操作在template table中实现。
mknod(WithoutMeta)
创建空文件时使用。
如标题所示,实际上这里的mknod是没有添加有效的metadata的,我们可以暂时理解为一个空的inode。
分成2pc和collect-dispatch两种方式,分别对应mknod2pc和mknodcd两个函数。
mknod2pc:
(这个暂时不是我们研究的重点,暂且跳过)
mknodcd:
我们其实会发现,Octopus中并没有用到太多的VFS层面类似inode,dentry之类的说法,因为目前的实现中,并没有类似VFS的思想(个人理解本文中实现的FileMeta有点类似于inode加dentry结构的混合体)。但是实际上代码中的parentHashAddress和parentMetaAddress就类似于dentry的作用,就是先找到这个inode的父目录,把父目录的inode对应的孩子数加一,之后更新到本地的分布式事务中,更新结束后调用updateRemoteData
即可。因为updateRemoteData
区分本地和远端,所以这里并不用判断parentNodeID是否在本地。update之后就正常的在本地create inode然后把这个inode放到hashTable中即可。
需要注意的是这里是先更新分布式事务,再update parent-inode的Data,之后在本地创建inode,并放到hashTable中,最后提交本地事务。
getattr
从FileMeta的Table中得到对应的FileMeta。(根据path得到hash值)。
access
注释给的比较明确,就是根据path判断一下这个file或者dir是否存在。
mkdir
同样的也分成了mkdircd和mkdir2pc,我们依旧不关注2pc的实现,来看mkdircd的实现:
会发现mkdircd和mknodcd的代码基本一致,除了使用的结构体不同,所以也侧面反映出,实际上创建文件和创建目录实际上区别不大。
readdir
得到某一个目录下面的文件以及目录的名字,类似的,根据path得到metadata,之后遍历metadata中的name,复制到一个list中,在函数体外可以通过list指针访问。
recursivereaddir
递归读取,读取当前目录下的全部目录以及文件名。
具体实现就是在readdir的时候,根据name和parentDirectory拼接成的新path继续readdir。
readDirectoryMeta
就像我们之前分析的,directory的meta可以在不同的机器上访问,因此这里也需要区分本地和远端。
Lock相关
lockWriteHashItem
unlockWriteHashItem
lockReadHashItem
unlockReadHashItem
基本是用到lock.cpp中的函数(不过那个读写锁我是真的理解不了)
构造函数
hashtable.cpp
构造函数
构造函数没啥可说的,就是初始化一些items的地址等。最后可以发现使用链表来保存bitmap中的freebit,避免了遍历的开销。
FreeBit *currentFreeBit;
for (uint64_t i = 1; i < bitmapChainedItems->countTotal(); i++) {currentFreeBit = (FreeBit *)malloc(sizeof(FreeBit));currentFreeBit->position = i; currentFreeBit->nextFreeBit = headFreeBit; headFreeBit = currentFreeBit;
}
(这里使用头插法构造链表,自己写代码从来没写过这种,记录一下。)
HashTable::get
步骤:
- 通过path得到一个 unique hash
- unique hash 的后20位作为 address hash
- 使用 address hash 作为 hash items 的索引,找到合适的item;
- 之后从hash item 中拿出chained item 的 index,因为可能出现hash冲突,所以从index开始向下依次遍历,直到找到相同的unique hash
其实就是开放链地址法解决hash冲突。(所以有一个do while)
bool HashTable::get(const char *path, uint64_t *indexMeta, bool *isDirectory)
{if ((path == NULL) || (indexMeta == NULL) || (isDirectory == NULL)) {return false; /* Fail due to null parameters. */} else {UniqueHash hashUnique;HashTable::getUniqueHash(path, strlen(path), &hashUnique); /* Get unique hash. */// printf("%016x%016x%016x%016x\n", hashUnique.value[3], hashUnique.value[2], hashUnique.value[1], hashUnique.value[0]);AddressHash hashAddress = HashTable::getAddressHash(&hashUnique); /* Get address hash by unique hash. */// getAddressHash(path, strlen(path), &hashAddress); /* Get address hash. */bool result;mutexBitmapChainedItems.lock(); /* Though currently there is no bitmap reading or writing, other operations such as delete might affect hash item reading. */{uint64_t indexHead = itemsHash[hashAddress].indexHead;if (indexHead == 0) {result = false; /* Fail due to no hash item. */} else {// UniqueHash hashUnique;// getUniqueHash(path, strlen(path), &hashUnique); /* Get unique hash. */uint64_t indexCurrent = indexHead; /* Index of current chained item. */bool found = false;do { /* Traverse every chained item. */if (memcmp(&(itemsChained[indexCurrent].hashUnique), &(hashUnique), sizeof(UniqueHash)) == 0) {*indexMeta = itemsChained[indexCurrent].indexMeta; /* Save meta index. */*isDirectory = itemsChained[indexCurrent].isDirectory; /* Save state of directory. */found = true; /* Found one matched. */break; /* Jump out. */} else {indexCurrent = itemsChained[indexCurrent].indexNext; /* Move to next chained item. */}} while (indexCurrent != 0); /* If current item is over last chained item then jump out. */if (found == true) {result = true; /* Succeed. Find one matched. */} else {result = false; /* Fail due to no chained item matched. */}}}mutexBitmapChainedItems.unlock(); /* Unlock hash table bitmap. */return result; /* Return specific result. */}
}
HashTable::put
基本的hash开放链地址法实现(跟我们用数组模拟的方式有些不同,使用FreeBit来维护空闲item,但是本质上还是开放链地址法)。
HashTable::del
删除一个hash item 和 chained item,与链表中删除节点非常类似。
剩下的方法基本是统计hash item 和 chained item等这些item的数量长度等。没什么需要专门分析的。总结一下hashtable的基本数据结构和操作:
lock.cpp
读写锁的原理比较容易懂,但是代码中存在着一堆magic number,比如LockAddress + 4
的4之类,猜测可能和metadata的结构有关,这里暂时跳过。
这里是一个读写锁实现,在另一篇文章中实现了一个python版本的,这里的实现基本一致。
但是代码里还是存在一些问题:
uint64_t LockService::WriteLock(uint16_t NodeID, uint64_t Address) {/* Address是相对地址,相对 hash item table 基址的位移 */int workerid = server->getIDbyTID();uint16_t ID = __sync_fetch_and_add(&WriteID, 1ULL);uint64_t key = (uint64_t)NodeID;uint64_t LockAddress = MetaDataBaseAddress + Address;key = key << 16;key += ID;key = key << 32;while (true) {if (__sync_bool_compare_and_swap((uint64_t *)LockAddress, 0ULL, key))break;//if (workerid == 0) {server->RequestPoller(workerid);//}}return key;
}bool LockService::WriteUnlock(uint64_t key, uint16_t NodeID, uint64_t Address) {uint64_t LockAddress = MetaDataBaseAddress + Address;uint32_t *p = (uint32_t *)(LockAddress + 4);*p = 0;return true;
}
这个写解锁的操作为什么要加4呢?理论上这里执行完,写加锁这里__sync_bool_compare_and_swap((uint64_t *)LockAddress, 0ULL, key)
LockAddress的内容完全没变啊,解完锁之后依旧无法执行新的写操作。
storage.cpp
storage.cpp的构造函数主要是进行内存分配的工作(count是自己输入的,所以个人感觉sizeBufferUsed中的Used可能不太准确。。。),分别是Hashtable,file metadata的table,file block的table,directory metadata的table。
Storage::Storage(char *buffer, char* bufferBlock, uint64_t countFile, uint64_t countDirectory, uint64_t countBlock, uint64_t countNode)
{if ((buffer == NULL) || (bufferBlock == NULL) || (countFile == 0) || (countDirectory == 0) ||(countBlock == 0) || (countNode == 0)) {fprintf(stderr, "Storage::Storage: parameter error.\n");exit(EXIT_FAILURE); /* Exit due to parameter error. */} else {hashtable = new HashTable(buffer, countDirectory + countFile); /* Initialize hash table. */Debug::notifyInfo("sizeof Hash Table = %d MB", hashtable->sizeBufferUsed / 1024 / 1024);tableFileMeta = new Table<FileMeta>(buffer + hashtable->sizeBufferUsed, countFile); /* Initialize file meta table. */Debug::notifyInfo("sizeof File Meta Size = %d MB", tableFileMeta->sizeBufferUsed / 1024 / 1024);tableDirectoryMeta = new Table<DirectoryMeta>(buffer + hashtable->sizeBufferUsed + tableFileMeta->sizeBufferUsed, countDirectory); /* Initialize directory meta table. */Debug::notifyInfo("sizeof Directory Meta Size = %d MB", tableDirectoryMeta->sizeBufferUsed / 1024 / 1024);tableBlock = new Table<Block>(bufferBlock, countBlock); /* Initialize block table. */this->countNode = countNode; /* Assign count of nodes. */sizeBufferUsed = hashtable->sizeBufferUsed + tableFileMeta->sizeBufferUsed + tableDirectoryMeta->sizeBufferUsed + tableBlock->sizeBufferUsed; /* Size of used bytes in buffer. */}
}
还有一个getNodeHash函数,应该是得到server node的编号,等到遇到的时候再详细看。
TxManager.cpp
最关键的函数是:
void TxManager::TxDistributedPrepare(uint64_t TxID, bool action) {DistributedLogEntry *log = (DistributedLogEntry *)DistributedLogAddress;log[TxID].prepare = action;FlushData((uint64_t)&log[TxID].prepare, CACHELINE_SIZE);
}
emmm,我是没看出来这里涉及了什么collect-dispatch操作。。。
Octopus 源码阅读(一)相关推荐
- 应用监控CAT之cat-client源码阅读(一)
CAT 由大众点评开发的,基于 Java 的实时应用监控平台,包括实时应用监控,业务监控.对于及时发现线上问题非常有用.(不知道大家有没有在用) 应用自然是最初级的,用完之后,还想了解下其背后的原理, ...
- centos下将vim配置为强大的源码阅读器
每日杂事缠身,让自己在不断得烦扰之后终于有了自己的清静时光来熟悉一下我的工具,每次熟悉源码都需要先在windows端改好,拖到linux端,再编译.出现问题,还得重新回到windows端,这个过程太耗 ...
- 源码阅读:AFNetworking(十六)——UIWebView+AFNetworking
该文章阅读的AFNetworking的版本为3.2.0. 这个分类提供了对请求周期进行控制的方法,包括进度监控.成功和失败的回调. 1.接口文件 1.1.属性 /**网络会话管理者对象*/ @prop ...
- 源码阅读:SDWebImage(六)——SDWebImageCoderHelper
该文章阅读的SDWebImage的版本为4.3.3. 这个类提供了四个方法,这四个方法可分为两类,一类是动图处理,一类是图像方向处理. 1.私有函数 先来看一下这个类里的两个函数 /**这个函数是计算 ...
- mybatis源码阅读
说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...
- 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
24 UsageEnvironment使用环境抽象基类--Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类--Live555源码阅读 ...
- Transformers包tokenizer.encode()方法源码阅读笔记
Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode
- 源码阅读笔记 BiLSTM+CRF做NER任务 流程图
源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...
- 源码阅读:AFNetworking(八)——AFAutoPurgingImageCache
该文章阅读的AFNetworking的版本为3.2.0. AFAutoPurgingImageCache该类是用来管理内存中图片的缓存. 1.接口文件 1.1.AFImageCache协议 这个协议定 ...
最新文章
- 数据中心机房消防演练方案
- k8s pod镜像拉取策略:Always、IfNotPresent、Never
- python基础----python的使用(四)
- java cssselector类_CSS 元素选择器
- vijos P1740 聪明的质检员
- mysql注入单引号被过滤_证明过滤单引号的ORDER BY可以注入
- Python使用组合、排列和动态规划算法求解0-1背包问题
- VSCode打开终端的快捷键
- 大学数学实验习题--统计推断 (附答案)判断alpha,n与mu,sigma的估计区间长度的关系
- Jmeter录制脚本时消息体数据乱码
- ssh框架原理及工作流程
- Ra-08系列开发板入门教程,标准LoRaWAN对接私有服务器。
- 大学计算机专业那些课 --左飞
- 计算机硬盘没显示,移动硬盘不显示在我的电脑
- 洛谷 P1007独木桥 C语言
- KNN+Dense SIFT+手势识别
- 找出数组中符合某些条件的对象数组
- 工作中常用的学习总结套路
- 使用veracrypt工具将文件或磁盘加密
- 必读干货丨这项技能玩不转,职场终生当菜鸟