首页 > 技术文章 > MiniSQL设计 - Buffer Manager模块

BirdCage 2017-07-16 15:42 原文


这一块是在助教给的参考代码的基础上进行了一些小范围的修改。

感觉最麻烦的是一个是文件的管理。一开始认定一定要(读一行表示一块),发现这样的话要循环调用getline这个函数,感觉并没有达到好的一个效率。后来查找了蛮多资料然后才熟悉了具体的存储方法……因为写的文件会用txt打开,无法显示的内容都会以为丢失了。

这一块我和写record的同学协商之后打算用强制类型转换的方法来读写,就是,比如int和float都是4个字节,就用四个字节的char来表示,相当于256进制。然后在后期拼合代码的时候……发现如果用这种方法来记录的话,字符串什么的类型都没法用了因为,默认0是字符串的结尾。所以后来扩充了那个类型,为了代码的容易理解最后是,用5个字节来表示int(所以int没法完全表达,比如负数和五位以上的正数)和8个字节表示float(128进制,这样可以避开之前让我们困扰的0的问题而且让大部分落在’可见’也就是能显示的区域)。

有些问题还是集中在个人的水平上,比如,因为内存分配什么的出过很多问题……

还是需要协调……比如说,我的buffer这一块需要建立的文件节点的链表,在队友写的时候是有一个头节点,而我是直接用第一个文件的节点,这样的话在释放的时候可能会有未考虑到的部分。(所以总觉得该用C++啊摔)。而且好像我写的两块独立测试都比较麻烦……而且测试看不出来……



一、BufferManager负责缓冲区的管理,主要功能有:

1.   根据需要,读取指定的数据到系统缓冲区或将缓冲区中的数据写出到文件;

2.   实现缓冲区的替换算法,当缓冲区满时选择合适的页进行替换;

3.   记录缓冲区中各页的状态,如是否被修改过等;

4.   提供缓冲区页的pin功能,及锁定缓冲区的页,不允许替换出去。

为提高磁盘I/O操作的效率,缓冲区与文件系统交互的单位是块,块的大小应为文件系统与磁盘交互单位的整数倍,一般可定为4KB或8KB。

 

二、模块总体设计思路

记录管理模块(RecordManager)和索引管理模块(IndexManager)向缓冲区管理申请所要的数据,缓冲区管理器首先在缓冲区中查看数据是否存在,若存在,直接返回,否则,从磁盘中将数据读入缓冲区,然后返回。

最近最少使用(LRU)算法:每次访问一个块的时候,如果是重新读取的话把该块的iTime置为1,如果是已经有的话把它的使用次数加一。采用这种方式记录主要是因为对于Index部分,比如根节点的使用率一直保持非常高。

换出脏块时需要写出块。

三、具体实现

1、宏  

#defineBLOCK_LEN                 4096      // the size of one block

#defineMAX_BLOCK                40          // the max number of the blocks

 

2、结构体

1)、文件头结构体(structfileInfo) :

包含文件的基本信息:类型(数据文件或索引文件)、文件名、记录数、空块号、记录长度、指向下一个文件的指针、文件所指向的第一个块。

structfileInfo  {

       int type;                       //0-> data file, 1 -> index file

       CString fileName;        // the name of the file

       int recordAmount;              // the number of record in the file

       int freeNum;                // the free block number which could be used for thefile

       int recordLength;        // the length of the record in the file

       fileInfo *next;                     // the pointer points to the next file

       blockInfo *firstBlock;   // point to the first block within the file

};    

      

2)、块信息结构体(struct blockInfo):

    包含块的基本信息:块号、脏位、指向下一个块的指针、块中的字符数、存放信息的字符型数组、年龄(用于LRU算法)、锁。

struct blockInfo  {

       intblockNum;       // the block number of theblock,

// which indicate itwhen it be newed

       booldirtyBit;     // 0 -> flase, 1 -> indicate dirty, write back

       blockInfo*next;         //the pointer point to next block

       fileInfo*file;     // the pointer point to the file, which the block belongs to

       intcharNum;     // the number of chars in theblock

       char*cBlock;     // the array space for storingthe records in the block in buffer

       intiTime;              // it indicate the ageof the block in use

       intlock;                // prevent the blockfrom replacing

};

 

3)、主要函数及其功能描述如下:

1. fileInfo *findFileInfo(CStringDB_Name)

返回该数据库对应的头节点。

2.fileInfo* get_file_info(CStringDB_Name,CString fileName, int m_fileType)

在内存中查找该文件,返回该文件头。

3. blockInfo*get_block_inbuffer(const fileInfo *file_temp, const int blockNum)

在该文件节点下所有的块节点中查找特定编号的块,没有返回NULL.

4. blockInfo*remove_block_inbuffer(blockInfo *m_blockInfo)

将块从文件节点下移除。

5. blockInfo *findBlock(CStringDB_Name)

函数实现获取用于替换的内存块。

首先遍历文件节点,判断每个文件节点下是否有dullNode,有的话返回。

其次如果块数未达到MAX_BLOCK,申请内存。

如没有可用块,遍历所有的块节点使用MRU算法找到没有使用的时间最长的块(itime最大),从文件节点下移去,itime置为1;并将该块返回。

6. void replace(fileInfo*m_fileInfo, blockInfo *m_blockInfo)

把block节点连到file节点下

7. blockInfo *get_file_block(CStringDB_Name, CString Table_Name, const int fileType, const int blockNum)

根据文件名,文件类型查找该文件

根据文件的块号,从内存中查询该块;

如该块已经在内存,并且不为空节点,判断是否加锁,未加锁则返回该块的指针,同时调用次数++;

如该块已经在内存,但是为空节点,调用readBlock读取内容,返回该块的指针,调用次数置为1;

如果该快没有存在内存,同样调用readBlock读取所需内容,返回。

8. blockInfo* readBlock(fileInfo*file_temp,CString DB_Name,  const intm_blockNum, blockInfo *block_temp)

如果传入参数中块为NULL,则调用findBlock来申请一个可用的块。

申请空间,读取文件。

9. void writeBlock(CString DB_Name,blockInfo * block)

把block指针所指向的块的内容写回磁盘。

10. void closeDatabase(CString DB_Name,bool m_flag)

先调用closeFile释放块节点。

再逐个释放文件节点。

11. void closeFile(CStringDB_Name,struct fileInfo* file_temp)

释放该文件节点下所有的块节点

 

4)主要操作实现过程

 

关闭数据库:

调用closeDatabase。函数内部对于每个文件节点,调用closeFile逐个关闭block,判断如果是脏块,调用writeBlock写回文件。再通过一次循环释放掉file节点。

 

获得某个数据块:

调用get_file_block,先找对应文件get_file_info,再调用get_block_inbuffer找文件下的块。如果不为空块直接返回,如果这个块是空块调用readBlock,找不到也调用readBlock,这个函数会申请缓存,如果申请不到缓存的话,调用findBlock去找一块空的内存。

findBlock的工作机制是先遍历所有的文件,找有没有freeNum的,需要 get_block_inbuffer再判断是不是到了申请的上限,再考虑释放时间最久的块。这里需要用到removeBlockinBuffer这个函数。

 

初始化:

BlockInfo数据初始化在read里,读取的时候进行初始化。

 

由其他模块直接作用维持的结构:

当需要其他的模块对该页加锁时,get_file_block将输出错误并且返回NULL指针。

 

4、内存中维护的结构

1)、单个数据库结构



fileInfo代表一个数据库下所有文件名。在命令use database的时候由Catalog Manager建立。

每个文件记录自己的freeNum对应第几个文件的数据是空的(基本上由数据删除产生),多个freeBlock,每个freeBlock里value指向下一个空的文件。


四、代码参考

////////////////////////////////////////////////////////////
//------------------------------------------------------///
//       Module: Index_Manager.cpp				    
//       Produced by: Birdy & C						     
//       Description: buffer			
//       date:2017/5/22-2017/6/16                   
//------------------------------------------------------///
///////////////////////////////////////////////////////////
#define MAX_BLOCK 500
//#include"MiniSQL.h"
#include"API_Module.h"
#include"Buffer_Manager.h"
#include"Catalog_Manager.h"
#include"Record_Manager.h"
#include"Index_Manager.h"
#include"Interpreter.h"
using namespace std;
//fileInfo *ptr_to_files;


//返回一个数据库的起始file的地址
fileInfo *findFileInfo(CString DB_Name)
{
	if (head_of_files == NULL)
		return NULL;
	return head_of_files->next;
}

//返回fileInfo
fileInfo *get_file_info(const CString DB_Name, const CString fileName, const int m_fileType)
{
	fileInfo* file_temp = findFileInfo(DB_Name);
	while (NULL != file_temp)				//遍历所有的file节点
	{
		if (file_temp->fileName == fileName && file_temp->type == m_fileType)
		{
			return file_temp;
		}
		file_temp = file_temp->next;
	}
	//没有找到这个文件
	cout << "buffer::get_file_info::can't find the file" << endl;
	return NULL;
}

//在内存中查找这个数据块
blockInfo *get_block_inbuffer(const fileInfo *file_temp, const int blockNum)
{
	if (NULL == file_temp)
		return NULL;
	blockInfo *block_temp = file_temp->firstBlock;
	//遍历该file节点下所有block
	while (NULL != block_temp)
	{
		if (block_temp->blockNum == blockNum)
		{
			return block_temp;
		}
		block_temp = block_temp->next;
	}
	return NULL;
}

//将块从文件节点下移除
blockInfo *remove_block_inbuffer(blockInfo *m_blockInfo)
{
	fileInfo *file_temp = m_blockInfo->fileName;
	int blockNum = m_blockInfo->blockNum;
	blockInfo *block_last = NULL;
	blockInfo *block_temp = file_temp->firstBlock;
	while (NULL != block_temp)
	{
		if (block_temp == m_blockInfo)
		{
			//find the node wanted
			break;
		}
		block_last = block_temp;
		block_temp = block_temp->next;
	}
	if (NULL != block_temp)
	{
		if (NULL == block_last)
		{
			file_temp->firstBlock = block_temp->next;//头节点
		}
		else
		{
			block_last->next = block_temp->next;//中间节点&叶节点
		}
		//返回前一个节点
		return block_last;
	}
	//没找着
	return NULL;
}

//返回可用的块
blockInfo *findBlock(CString DB_Name)
{
	static int i = 0;//记录现有block数字
					 //搜索空块
	fileInfo *ptr = findFileInfo(DB_Name);
	blockInfo *newblock;
	while (NULL != ptr)
	{
		int freeBlock = ptr->freeNum;
		if (freeBlock > 0)
		{
			blockInfo *block_temp = get_block_inbuffer(ptr, freeBlock);
			if (NULL != block_temp)
			{
				blockInfo *block_last = remove_block_inbuffer(block_temp);//从链表中移除
																		  //更新freeNum
				CString freeNum_rec(block_temp->cBlock);
				ptr->freeNum = _ttoi(freeNum_rec.Left(3));
				return block_temp;
			}
			//file记录了freeNUM 但是找不到这个block
			cout << "WANING  buffer::findBlock:the freeNum in record may be wrong" << endl;
		}
		ptr = ptr->next;
	}
	if (i < MAX_BLOCK)
	{
		//申请内存
		newblock = new blockInfo;
		i++;
		if (NULL != newblock)
		{
			newblock->cBlock = new char[BLOCK_SIZE];
			if (NULL != newblock->cBlock) return newblock;

		}
	}
	//释放内存
	//哇这里写的MRU
	int time = -1;
	blockInfo *temp = NULL;
	ptr = findFileInfo(DB_Name);
	while (NULL != ptr)
	{
		blockInfo *block_ptr = ptr->firstBlock;
		while (NULL != block_ptr)
		{
			if (block_ptr->iTime > time)
			{
				time = block_ptr->iTime;
				temp = block_ptr;
			}
			block_ptr = block_ptr->next;
		}
		ptr = ptr->next;
	}
	temp->iTime = 1;
	remove_block_inbuffer(temp);
	return temp;
}

//把block连到file下
void replace(fileInfo *m_fileInfo, blockInfo *m_blockInfo)
{
	//加在最前面
	m_blockInfo->fileName = m_fileInfo;
	m_blockInfo->next = m_fileInfo->firstBlock;
	m_fileInfo->firstBlock = m_blockInfo;
}


//返回数据
blockInfo *get_file_block(CString DB_Name, CString Table_Name, const int fileType, const int blockNum)
{
	blockInfo *block_temp;
	fileInfo *file_temp = get_file_info(DB_Name, Table_Name, fileType);

	if (NULL != (block_temp = get_block_inbuffer(file_temp, fileType)))
	{
		if (1 == block_temp->lock)//判断这个块有没有被锁住
			cout << "buffer::the block has been locked" << endl;
		return block_temp;
	}
	if (NULL == file_temp)//找不到这个文件
	{
		cout << "buffer::get_file_block::wrong information" << endl;
		return NULL;
	}
	block_temp = file_temp->firstBlock;
	while (NULL != block_temp)
	{
		if (block_temp->blockNum == blockNum)
		{
			if (0 != block_temp->charNum)	//如果不是空块
			{
				block_temp->iTime++;		//调用次数++
				return block_temp;
			}
			else
			{
				block_temp = readBlock(file_temp, DB_Name, blockNum, block_temp);
				block_temp->iTime = 1;		//调用次数初始化
				return block_temp;
			}
		}
		block_temp = block_temp->next;
	}
	cout << "buffer::get_file_block::can't find this block >>> begin read" << endl;
	block_temp = readBlock(file_temp, DB_Name, blockNum, NULL);
	replace(file_temp, block_temp);			//将该block增加到头节点下
	block_temp->iTime = 1;					//调用次数初始化
	return block_temp;
}

//已经判断内存中不存在这一个块、 读取数据
blockInfo* readBlock(fileInfo *file_temp, CString DB_Name, const int m_blockNum, blockInfo *block_temp)
{
	//如果没有提供块,则需要自己找一个块
	if (block_temp == NULL)
	{
		block_temp = findBlock(DB_Name);
		block_temp->blockNum = m_blockNum;
		block_temp->dirtyBit = 0;
	}
	//读取文件
	CString fpath;
	if (0 == file_temp->type)
	{
		fpath = "..\\" + DB_Name + "\\table\\" + file_temp->fileName + ".txt";
	}
	else
	{
		fpath = "..\\" + DB_Name + "\\index\\" + file_temp->fileName + ".txt";
	}
	fstream file(fpath, ios::in | ios::out);
	if (!file.is_open())							//检查打开是否成功
	{
		cout << "buffer::readBlock::can't find the file" + fpath << endl;
		return NULL;
	}
	int offset = (m_blockNum - 1) * BLOCK_SIZE;		//计算偏移量
	file.seekp(offset, ios::beg);					//从起始位置开始
	block_temp->cBlock = new char[BLOCK_SIZE];		//申请内存
	file.read(block_temp->cBlock, BLOCK_SIZE);		//读取文件
	file.close();
	return block_temp;
}

//把block指针所指向的块的内容写回磁盘。
void writeBlock(CString DB_Name, blockInfo * block)
{
	if (!block->dirtyBit) return;

	fileInfo *file_temp = block->fileName;
	CString m_fileName = block->fileName->fileName;
	int m_blockNum = block->blockNum;
	CString fpath;
	if (0 == file_temp->type)
	{
		fpath = "..\\" + DB_Name + "\\table\\" + m_fileName + ".txt";
	}
	else
	{
		fpath = "..\\" + DB_Name + "\\index\\" + m_fileName + ".txt";
	}
	fstream file(fpath, ios::in | ios::out);
	if (!file.is_open())							//检查打开是否成功
	{
		cout << "buffer::writeBlock::can't find the file" + fpath << endl;
		return;
	}
	int offset = (m_blockNum - 1) * BLOCK_SIZE;
	file.seekp(offset, ios::beg);
	CString temp = block->cBlock;
	file.write(block->cBlock, temp.GetLength());
	file.close();
}


//调用closeFile(DB_Name,filename,fileType,m_flag),逐个关闭文件。
void closeDatabase(CString DB_Name, bool m_flag)
{
	//release Block_Info
	fileInfo *ptr = findFileInfo(DB_Name);
	while (NULL != ptr)
	{
		closeFile(DB_Name, ptr);
		ptr = ptr->next;
	}

	//release File_info
	ptr = findFileInfo(DB_Name);
	while (NULL != ptr)
	{
		fileInfo *ptr_next = ptr->next;
		delete ptr;
		ptr = ptr_next;
	}
}

//关闭文件
void closeFile(CString DB_Name, struct fileInfo* file_temp)
{
	if (NULL == file_temp)
	{
		cout << "buffer::closeFile:can't find the file" << endl;
		return;
	}
	blockInfo* block_temp = file_temp->firstBlock;
	blockInfo * block_delete;

	//依次删除该file下的block
	while (NULL != block_temp)
	{
		writeBlock(DB_Name, block_temp);
		block_delete = block_temp;
		block_temp = block_temp->next;//指向下一个节点
		delete[] block_delete->cBlock;//释放内存
		delete block_delete;//删除节点
	}

}



推荐阅读