内存映射系统开发
来源:互联网 发布:淘宝发货炫富图片大全 编辑:程序博客网 时间:2024/06/06 23:18
为了使用内存作为数据库的主要存储方式,开发内存数据库,我们需要对内存中的数据进行保证。即可以备份与还原,那么为了将内存中的数据备份到外存中,我们可以采取以下策略:
选取一个外存文件,将其映射到某个内存地址;
当更新内存时,适时地更新外存文件;
系统重启时,从外存中重新读取内存内容。
那么这里就有几个问题,首先是映射问题,起初我尝试了win32api:
createFileMapping, _ = syscall.GetProcAddress(kernel32, "CreateFileMappingW") mapViewOfFile, _ = syscall.GetProcAddress(kernel32, "MapViewOfFile") createFile, _ = syscall.GetProcAddress(kernel32, "CreateFileW") closeHandle, _ = syscall.GetProcAddress(kernel32, "CloseHandle") flushViewOfFile, _ = syscall.GetProcAddress(kernel32, "FlushViewOfFile") unmapViewOfFile, _ = syscall.GetProcAddress(kernel32, "UnmapViewOfFile")
但是实际使用中,发现win32系统为了性能等方面的考虑,映射文件后,不一定就真正给你开辟了内存空间来访问,这时访问会出现异常,windows捕获异常后才会再次加载这些文件,导致测试时时好时坏。由此,我决定自己写一个文件映射的库。
当创建一个文件映射时,我们使用malloc申请一块内存,然后创建一个对应大小的文件,并将地址与文件路径的对应关系存入map中:
var ImageTable = make(map[uintptr]string)var commonBuffer = make([]byte, 1024 * 1024) // To clear the file quicklyvar count = 0 // Windows only support 100ns levelvar DataBlockList list.List// CreateImage creates a image file and returns the addressfunc CreateImage(size int) (ip *DataBlock, err error) { defer signalBackup() filename := common.COMMON_DIR + "\\image\\" + strconv.Itoa(count) count++ ip = &DataBlock { RawPtr: uintptr(C.malloc(C.size_t(size))), Size: size, } file, err := os.Create(filename) defer file.Close() for i := size;i > 0;i -= 1024 * 1024 { if i < 1024 * 1024 { file.Write(commonBuffer[:i]) } else { file.Write(commonBuffer) } } ImageTable[ip.RawPtr] = filename DataBlockList.PushBack(ip) return}
commonBuffer数组是一个比较大的0数组,为了快速刷到文件中,而不用每次创建文件都创建一个buffer。count变量的使用,是由于windows系统最多支持到100ns级的时间记录,为了让文件名序列化不受干扰,设置count变量,每次创建镜像都会自增。同时,该变量和映射表都会被备份在文件中,便于以后的恢复(后文会提及)。DataBlockList是一个DataBlock的链表,为了备份时方便遍历而设置,DataBlock是为了封装Read,Write函数而实现的数据类型。Read,Write函数用于读写内存,为什么不直接让使用者读写呢,因为数据库经常是多个会话同时操作,并行访问需要对资源加锁。以下便是DataBlock的实现:
type DataBlock struct { RawPtr uintptr Size int RWMutex sync.RWMutex}func (b *DataBlock) read(offset, size int) ([]byte, error) { if offset + size > b.Size { return nil, OUT_OF_SIZE } var header reflect.SliceHeader header.Data = uintptr(b.RawPtr + uintptr(offset)) header.Len = size header.Cap = size return *(*[]byte)(unsafe.Pointer(&header)), nil}func (b *DataBlock) Read(offset, size int) ([]byte, error) { b.RWMutex.RLock() defer b.RWMutex.RUnlock() return b.read(offset, size)}func (b *DataBlock) write(offset int, data []byte) (int, error) { var header reflect.SliceHeader size := len(data) header.Data = uintptr(b.RawPtr + uintptr(offset)) header.Len = size header.Cap = size d := *(*[]byte)(unsafe.Pointer(&header)) var n int if offset + size > b.Size { n = b.Size - offset } else { n = size } copy(d, data[:n]) return n, nil}func (b *DataBlock) Write(offset int, data []byte) (int, error) { b.RWMutex.Lock() defer b.RWMutex.Unlock() var copies *DataBlock copies, ok := CopyTable[b] if !ok { return b.write(offset, data) } copies.Write(offset, data) return b.Write(offset, data)}DataBlock结构有一个读写锁做并发控制,允许多个线程同时读,但不允许写和任何其他写或者读操作同时进行,保证线程安全。
同时这段代码用到了CopyTable,CopyTable是一个记录正在复制的表,因为我们申请的空间是固定的,一旦需要扩容,就需要复制操作,而复制是一个很耗时的操作,在此过程中,其他线程可能操作/改变正在复制的数据,所以在write函数中加入复制表的判断,如果该块正在被复制,那么对该块的操作要同时写入两个副本。对CopyTable表的操作见copy函数:
func Copy(dst, src *DataBlock) (int, error) { CopyTable[src] = dst data, err := src.Read(0, src.Size) if err != nil { return 0, err } delete(CopyTable, src) return dst.Write(0, data)}有了Copy函数,重新分配内存的ReallocImage函数也是水到渠成,重新创建一个文件,大小为新的大小,申请一块对应的空间,然后建立映射表,删除原来的映射表:
// ReallocImage creates a new bigger image file and returns the new address with copying datafunc ReallocImage(ip *DataBlock, size int) (*DataBlock, error) { defer signalBackup() filename := common.COMMON_DIR + "\\image\\" + strconv.Itoa(count) count++ os.Remove(ImageTable[ip.RawPtr]) ipNew := &DataBlock { RawPtr: uintptr(C.malloc(C.size_t(size))), Size: size, } file, err := os.Create(filename) defer file.Close() if err != nil { return nil, err } for i := size;i > 0;i -= 1024 * 1024 { if i < 1024 * 1024 { file.Write(commonBuffer[:i]) } else { file.Write(commonBuffer) } } Copy(ipNew, ip) delete(ImageTable, ip.RawPtr) C.free(unsafe.Pointer(ip.RawPtr)) ImageTable[ipNew.RawPtr] = filename RemoveBlock(ip) DataBlockList.PushBack(ipNew) return ipNew, nil}至于ReleaseImage函数,只需要释放资源,对应修改映射关系即可,接下来说明备份与恢复系统,备份系统由一个单独的线程控制,该线程在收到备份命令前阻塞,收到信号后开始备份,将映射表和count写入文件,然后分别写入每个镜像:
func signalBackup() { startBackup <- true}func BackupRoutine() { for { <- startBackup SaveImageTable() SyncAllImageToFile() close(startBackup) startBackup = make(chan bool, MAX_BAK_CHAN_SIZE) }}signalBackup函数负责发送一个备份信号,该函数会在用户进行文件镜像相关操作时触发,详情见上文内存镜像相关函数。
func (b *DataBlock) SyncToFile() error { data, err := b.Read(0, b.Size) if err != nil { return err } filename, ok := ImageTable[b.RawPtr] if !ok { return NOT_FOUND_ADDRESS } log.WriteLog("sys", "Sync " + strconv.Itoa(int(b.RawPtr)) + " to file.") return ioutil.WriteFile(filename, data, 0666)}
func SaveImageTable() { tempTable := make(map[string]string) for k, v := range ImageTable { tempTable[strconv.Itoa(int(k))] = v } data, _ := json.Marshal(tempTable) ioutil.WriteFile(common.COMMON_DIR + "\\image\\imageTable.json", data, 0666) ioutil.WriteFile(common.COMMON_DIR + "\\image\\count", []byte(strconv.Itoa(count)), 0666) log.WriteLog("sys", "Save image table to file.")}
本系统可以配合内存池优化技术:传送门 来实现以下论文讲述的DCBST索引:传送门
0 0
- 内存映射系统开发
- 内存映射(mmap系统调用)
- FC系统的内存映射
- [Solaris内存管理]系统内存映射
- 内存映射文件-Java I/O系统
- 【VS开发】内存映射文件3
- Linux开发--mmap映射/dev/mem内存
- 共享内存:使用内存映射接口mmap系统调用
- 【VS开发】内存映射文件进程间共享内存
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 内存映射
- 压缩感知重构算法之OMP算法python实现
- Http网络连接框架工具类(基于volley.jar和Xutils.jar)
- 字节流与字符流的区别详解
- asp:Button 事件,点击事件 html Button runat="sever"
- Android实际开发问题08------自动换行容器
- 内存映射系统开发
- 添加头文件apue.h
- MyEclipse下使用maven创建web项目
- sublime 插件 收集记录
- Android AsyncTask完全解析,带你从源码的角度彻底理解
- android Graphics(一):概述及基本几何图形绘制
- hibernate缓存机制详细分析
- VC:CString的用法
- 开发者必备的网站-你都收藏了么?