之前的一通insert、select的操作范围还仅仅只停留在程序运行时的内存中。当结束运行后,我们的insert数据就会丢失。这节就是将我们的数据写到文件中。
之前已经完成了Core部分,然后是Backend部分。就像sqlite一样,我们将整个数据库保存到文件中。
我们已经将记录serialize到内存页中,为了持久化,我们的思路如下:
- 将这些内存页写入到文件中
- 在程序启动时,将内容从文件中读出来。
方便起见,我们定义(抽象)一个结构pager。get_page: 从pager中可以获得page number x,pager页存储相应的内存页。读取的时候会先查看cache,cache中没有的时候则从磁盘中读取。
(系统设计两大思想:cache和抽象)
-
前置知识:lseek系统调用
lseek:
lseek函数lseek用于显式地为一个已打开的文件设置其偏移量
每个打开的文件都有一个与其相关联的“当前文件偏移量”(current file offset)。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
off_t lseek(int fd, off_t offset, int whence);//打开一个文件的下一次读写的开始位置
参数:
fd 表示要操作的文件描述符offset是相对于whence(基准)的偏移量
whence 可以是SEEK_SET(文件指针开始),SEEK_CUR(文件指针当前位置) ,SEEK_END为文件指针尾
返回值:文件读写指针距文件开头的字节大小,出错,返回-1
三个参数:这里我们用第一个
-
SEEK_SET:从文件头部开始偏移offset个字节。
-
SEEK_CUR:从文件当前读写的指针位置开始,增加offset个字节的偏移量。
-
SEEK_END:文件偏移量设置为文件的大小加上偏移量字节。
-
-
Pager结构定义
typedef struct{
int file_descriptor;
uint32_t file_length;
void* pages[MAX_PAGE_NUM];
}Pager;
- page页读取
传入pager和page_num,如果对应的page为空,那么就从文件中加载该页面(lseek确定文件从哪里开始读取,read函数负责读取);否则直接读取。
//返回最后一个page的开始位置
void* get_page(Pager *pager,uint32_t page_num){
if(page_num>MAX_PAGE_NUM){
printf("Tried to fetch page number out of bounds\n");
exit(EXIT_FAILURE);
}
if(pager->pages[page_num]==NULL){
void* page= malloc(PAGE_SIZE);
uint32_t num_pages=pager->file_length/PAGE_SIZE;
if(pager->file_length%PAGE_SIZE!=0){
num_pages++;
}
if(page_num<=num_pages){
lseek(pager->file_descriptor,page_num*PAGE_SIZE,SEEK_SET);
ssize_t read_bytes=read(pager->file_descriptor,page,PAGE_SIZE);
if(read_bytes==-1){
printf("Error reading file\n");
exit(EXIT_FAILURE);
}
}
pager->pages[page_num]=page;
}
return pager->pages[page_num];
}
//获取到一页,再定位行的位置
void* read_slot(Table* table,uint32_t row_num){
uint32_t page_num=row_num/ROW_NUM_PER_PAGE;
void* page=get_page(table->pager,page_num);
uint32_t row_offset=row_num%ROW_NUM_PER_PAGE;
uint32_t offset=row_offset*ROW_SIZE;
return offset+page;
}
- 从new_table()到db_open()
Table* db_open(const char* filename)
db_open()要求在程序开始运行初始阶段,把文件中的数据读出来
table中存pager,num_rows
Pager *pager_open(const char * filename){
int fd= open(filename,
O_RDWR |
O_CREAT,
S_IWUSR |
S_IRUSR
);
if(fd==-1){
printf("Unable to open file\n");
exit(EXIT_FAILURE);
}
off_t file_length=lseek(fd,0,SEEK_END);
Pager *pager=malloc(sizeof(Pager));
pager->file_descriptor=fd;
pager->file_length=file_length;
for(uint32_t i=0;i<MAX_PAGE_NUM;i++){
pager->pages[i]=NULL;
}
return pager;
}
Table *db_open(const char* filename){
// 1. 打开文件,初始化Pager;注意打开table≠读取文件中数据,只有需要数据时候才会将页面加载到内存中
Pager* pager=pager_open(filename);
uint32_t row_num=pager->file_length/ROW_SIZE;
//printf("test:row_num %d\n",pager->file_length);
// printf("%d\n",pager->file_length);
//2. 初始化table
Table *table=(Table*) malloc(sizeof (table));
table->row_num=row_num;
table->pager=pager;
return table;
}
- db_close()
退出时进行刷盘操作,将内存中的数据写到磁盘中
void pager_flush(Pager* pager,uint32_t page_num,uint32_t size){
//printf("test\n");
if(pager->pages[page_num]==NULL){
printf("Tried to flush null page!\n");
exit(EXIT_FAILURE);
}
off_t offset=lseek(pager->file_descriptor,page_num*PAGE_SIZE,SEEK_SET);
if(offset==-1){
printf("Error seeking!\n");
exit(EXIT_FAILURE);
}
int write_bytes= write(pager->file_descriptor,pager->pages[page_num],size);
// printf("test\n");
// printf("%d\n",write_bytes);
if(write_bytes==-1){
printf("write failed!\n");
exit(EXIT_FAILURE);
}
}
void db_close(Table* table){
Pager * pager=table->pager;
uint32_t row_num=table->row_num;
uint32_t full_page_num=row_num/ROW_NUM_PER_PAGE;
for(uint32_t i=0;i<full_page_num;i++){
if(pager->pages[i]==NULL){
continue;
}
pager_flush(pager,i,PAGE_SIZE);//如果内存页有数据,需要刷到盘上
free(pager->pages[i]);
pager->pages[i]=NULL;
}
uint32_t additional_rows=row_num%ROW_NUM_PER_PAGE;
//最后一页可能不完整,刷的数据大小是additional_rows*ROW_SIZE
if(additional_rows!=0){
if(pager->pages[full_page_num]!=NULL){
pager_flush(pager,full_page_num,additional_rows*ROW_SIZE);
free(pager->pages[full_page_num]);
pager->pages[full_page_num]=NULL;
}
}
int close_result= close(pager->file_descriptor);
if(close_result==-1){
printf("Close file failed!\n");
exit(EXIT_FAILURE);
}
for(uint32_t i=0;i<MAX_PAGE_NUM;i++){
if(pager->pages[i]){
free(pager->pages[i]);
pager->pages[i]=NULL;
}
}
free(pager);
free(table);
- 至此完成了一个miniminiDB的雏形