From e4ec0a2364b3fe0b5f4b8ca0c7ea1b54ef8e8735 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sat, 28 Feb 2009 13:47:10 +0000 Subject: [PATCH] Added the kernel file map implementation. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29348 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../userlandfs/server/haiku/Jamfile | 2 + .../userlandfs/server/haiku/file_map.cpp | 647 ++++++++++++++++++ .../server/haiku/haiku_kernel_emu.cpp | 21 + .../userlandfs/server/haiku/vfs.h | 23 + 4 files changed, 693 insertions(+) create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku/file_map.cpp create mode 100644 src/add-ons/kernel/file_systems/userlandfs/server/haiku/vfs.h diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/Jamfile b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/Jamfile index f00dfb5926..62162f0776 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/Jamfile +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/Jamfile @@ -33,6 +33,8 @@ SharedLibrary libuserlandfs_haiku_kernel.so HaikuKernelFileSystem.cpp HaikuKernelVolume.cpp + file_map.cpp + : UserlandFSServer ; diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/file_map.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/file_map.cpp new file mode 100644 index 0000000000..97beef8a67 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/file_map.cpp @@ -0,0 +1,647 @@ +/* + * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "haiku_lock.h" +#include "vfs.h" + + +//#define TRACE_FILE_MAP +#ifdef TRACE_FILE_MAP +# define TRACE(x...) dprintf_no_syslog(x) +#else +# define TRACE(x...) ; +#endif + +// TODO: use a sparse array - eventually, the unused BlockMap would be something +// to reuse for this. We could also have an upperbound of memory consumption +// for the whole map. +// TODO: it would be nice if we could free a file map in low memory situations. + + +#define CACHED_FILE_EXTENTS 2 + // must be smaller than MAX_FILE_IO_VECS + // TODO: find out how much of these are typically used + +struct file_extent { + off_t offset; + file_io_vec disk; +}; + +struct file_extent_array { + file_extent* array; + size_t max_count; +}; + +class FileMap +#if DEBUG_FILE_MAP + : public DoublyLinkedListLinkImpl +#endif +{ +public: + FileMap(struct vnode* vnode, off_t size); + ~FileMap(); + + void Invalidate(off_t offset, off_t size); + void SetSize(off_t size); + + status_t Translate(off_t offset, size_t size, + file_io_vec* vecs, size_t* _count, + size_t align); + + file_extent* ExtentAt(uint32 index); + + size_t Count() const { return fCount; } + struct vnode* Vnode() const { return fVnode; } + off_t Size() const { return fSize; } + + status_t SetMode(uint32 mode); + +private: + file_extent* _FindExtent(off_t offset, uint32* _index); + status_t _MakeSpace(size_t count); + status_t _Add(file_io_vec* vecs, size_t vecCount, + off_t& lastOffset); + status_t _Cache(off_t offset, off_t size); + void _InvalidateAfter(off_t offset); + void _Free(); + + union { + file_extent fDirect[CACHED_FILE_EXTENTS]; + file_extent_array fIndirect; + }; + mutex fLock; + size_t fCount; + struct vnode* fVnode; + off_t fSize; + bool fCacheAll; +}; + +#if DEBUG_FILE_MAP +typedef DoublyLinkedList FileMapList; + +static FileMapList sList; +static mutex sLock; +#endif + + +FileMap::FileMap(struct vnode* vnode, off_t size) + : + fCount(0), + fVnode(vnode), + fSize(size), + fCacheAll(false) +{ + mutex_init(&fLock, "file map"); + +#if DEBUG_FILE_MAP + MutexLocker _(sLock); + sList.Add(this); +#endif +} + + +FileMap::~FileMap() +{ + _Free(); + mutex_destroy(&fLock); + +#if DEBUG_FILE_MAP + MutexLocker _(sLock); + sList.Remove(this); +#endif +} + + +file_extent* +FileMap::ExtentAt(uint32 index) +{ + if (index >= fCount) + return NULL; + + if (fCount > CACHED_FILE_EXTENTS) + return &fIndirect.array[index]; + + return &fDirect[index]; +} + + +file_extent* +FileMap::_FindExtent(off_t offset, uint32 *_index) +{ + int32 left = 0; + int32 right = fCount - 1; + + while (left <= right) { + int32 index = (left + right) / 2; + file_extent* extent = ExtentAt(index); + + if (extent->offset > offset) { + // search in left part + right = index - 1; + } else if (extent->offset + extent->disk.length <= offset) { + // search in right part + left = index + 1; + } else { + // found extent + if (_index) + *_index = index; + + return extent; + } + } + + return NULL; +} + + +status_t +FileMap::_MakeSpace(size_t count) +{ + if (count <= CACHED_FILE_EXTENTS) { + // just use the reserved area in the file_cache_ref structure + if (fCount > CACHED_FILE_EXTENTS) { + // the new size is smaller than the minimal array size + file_extent *array = fIndirect.array; + memcpy(fDirect, array, sizeof(file_extent) * count); + free(array); + } + } else { + // resize array if needed + file_extent* oldArray = NULL; + size_t maxCount = CACHED_FILE_EXTENTS; + if (fCount > CACHED_FILE_EXTENTS) { + oldArray = fIndirect.array; + maxCount = fIndirect.max_count; + } + + if (count > maxCount) { + // allocate new array + while (maxCount < count) { + if (maxCount < 32768) + maxCount <<= 1; + else + maxCount += 32768; + } + + file_extent* newArray = (file_extent *)realloc(oldArray, + maxCount * sizeof(file_extent)); + if (newArray == NULL) + return B_NO_MEMORY; + + if (fCount > 0 && fCount <= CACHED_FILE_EXTENTS) + memcpy(newArray, fDirect, sizeof(file_extent) * fCount); + + fIndirect.array = newArray; + fIndirect.max_count = maxCount; + } + } + + fCount = count; + return B_OK; +} + + +status_t +FileMap::_Add(file_io_vec* vecs, size_t vecCount, off_t& lastOffset) +{ + TRACE("FileMap@%p::Add(vecCount = %ld)\n", this, vecCount); + + uint32 start = fCount; + off_t offset = 0; + + status_t status = _MakeSpace(fCount + vecCount); + if (status != B_OK) + return status; + + file_extent* lastExtent = NULL; + if (start != 0) { + lastExtent = ExtentAt(start - 1); + offset = lastExtent->offset + lastExtent->disk.length; + } + + for (uint32 i = 0; i < vecCount; i++) { + if (lastExtent != NULL) { + if (lastExtent->disk.offset + lastExtent->disk.length + == vecs[i].offset + || (lastExtent->disk.offset == -1 && vecs[i].offset == -1)) { + lastExtent->disk.length += vecs[i].length; + offset += vecs[i].length; + start--; + _MakeSpace(fCount - 1); + continue; + } + } + + file_extent* extent = ExtentAt(start + i); + extent->offset = offset; + extent->disk = vecs[i]; + + offset += extent->disk.length; + lastExtent = extent; + } + +#ifdef TRACE_FILE_MAP + for (uint32 i = 0; i < fCount; i++) { + file_extent* extent = ExtentAt(i); + TRACE("[%ld] extent offset %Ld, disk offset %Ld, length %Ld\n", + i, extent->offset, extent->disk.offset, extent->disk.length); + } +#endif + + lastOffset = offset; + return B_OK; +} + + +void +FileMap::_InvalidateAfter(off_t offset) +{ + uint32 index; + file_extent* extent = _FindExtent(offset, &index); + if (extent != NULL) { + _MakeSpace(index + 1); + + if (extent->offset + extent->disk.length > offset) { + extent->disk.length = offset - extent->offset; + if (extent->disk.length == 0) + _MakeSpace(index); + } + } +} + + +/*! Invalidates or removes the specified part of the file map. +*/ +void +FileMap::Invalidate(off_t offset, off_t size) +{ + MutexLocker _(fLock); + + // TODO: honour size, we currently always remove everything after "offset" + if (offset == 0) { + _Free(); + return; + } + + _InvalidateAfter(offset); +} + + +void +FileMap::SetSize(off_t size) +{ + MutexLocker _(fLock); + + if (size < fSize) + _InvalidateAfter(size); + + fSize = size; +} + + +void +FileMap::_Free() +{ + if (fCount > CACHED_FILE_EXTENTS) + free(fIndirect.array); + + fCount = 0; +} + + +status_t +FileMap::_Cache(off_t offset, off_t size) +{ + file_extent* lastExtent = NULL; + if (fCount > 0) + lastExtent = ExtentAt(fCount - 1); + + off_t mapEnd = 0; + if (lastExtent != NULL) + mapEnd = lastExtent->offset + lastExtent->disk.length; + + off_t end = offset + size; + + if (fCacheAll && mapEnd < end) + return B_ERROR; + + status_t status = B_OK; + file_io_vec vecs[8]; + const size_t kMaxVecs = 8; + + while (status == B_OK && mapEnd < end) { + // We don't have the requested extents yet, retrieve them + size_t vecCount = kMaxVecs; + status = vfs_get_file_map(Vnode(), mapEnd, ~0UL, vecs, &vecCount); + if (status == B_OK || status == B_BUFFER_OVERFLOW) + status = _Add(vecs, vecCount, mapEnd); + } + + return status; +} + + +status_t +FileMap::SetMode(uint32 mode) +{ + if (mode != FILE_MAP_CACHE_ALL && mode != FILE_MAP_CACHE_ON_DEMAND) + return B_BAD_VALUE; + + MutexLocker _(fLock); + + if ((mode == FILE_MAP_CACHE_ALL && fCacheAll) + || (mode == FILE_MAP_CACHE_ON_DEMAND && !fCacheAll)) + return B_OK; + + if (mode == FILE_MAP_CACHE_ALL) { + status_t status = _Cache(0, fSize); + if (status != B_OK) + return status; + + fCacheAll = true; + } else + fCacheAll = false; + + return B_OK; +} + + +status_t +FileMap::Translate(off_t offset, size_t size, file_io_vec* vecs, size_t* _count, + size_t align) +{ + MutexLocker _(fLock); + + size_t maxVecs = *_count; + size_t padLastVec = 0; + + if (offset >= Size()) { + *_count = 0; + return B_OK; + } + if (offset + size > fSize) { + if (align > 1) { + off_t alignedSize = (fSize + align - 1) & ~(off_t)(align - 1); + if (offset + size >= alignedSize) + padLastVec = alignedSize - fSize; + } + size = fSize - offset; + } + + // First, we need to make sure that we have already cached all file + // extents needed for this request. + + status_t status = _Cache(offset, size); + if (status != B_OK) + return status; + + // We now have cached the map of this file as far as we need it, now + // we need to translate it for the requested access. + + uint32 index; + file_extent* fileExtent = _FindExtent(offset, &index); + + offset -= fileExtent->offset; + if (fileExtent->disk.offset != -1) + vecs[0].offset = fileExtent->disk.offset + offset; + else + vecs[0].offset = -1; + vecs[0].length = fileExtent->disk.length - offset; + + if (vecs[0].length >= size) { + vecs[0].length = size + padLastVec; + *_count = 1; + return B_OK; + } + + // copy the rest of the vecs + + size -= vecs[0].length; + uint32 vecIndex = 1; + + while (true) { + fileExtent++; + + vecs[vecIndex++] = fileExtent->disk; + + if (size <= fileExtent->disk.length) { + vecs[vecIndex - 1].length = size + padLastVec; + break; + } + + if (vecIndex >= maxVecs) { + *_count = vecIndex; + return B_BUFFER_OVERFLOW; + } + + size -= fileExtent->disk.length; + } + + *_count = vecIndex; + return B_OK; +} + + +// #pragma mark - + + +#if DEBUG_FILE_MAP + +static int +dump_file_map(int argc, char** argv) +{ + if (argc < 2) { + print_debugger_command_usage(argv[0]); + return 0; + } + + bool printExtents = false; + if (argc > 2 && !strcmp(argv[1], "-p")) + printExtents = true; + + FileMap* map = (FileMap*)parse_expression(argv[argc - 1]); + if (map == NULL) { + kprintf("invalid file map!\n"); + return 0; + } + + kprintf("FileMap %p\n", map); + kprintf(" size %Ld\n", map->Size()); + kprintf(" count %lu\n", map->Count()); + + if (!printExtents) + return 0; + + for (uint32 i = 0; i < map->Count(); i++) { + file_extent* extent = map->ExtentAt(i); + + kprintf(" [%lu] offset %Ld, disk offset %Ld, length %Ld\n", + i, extent->offset, extent->disk.offset, extent->disk.length); + } + + return 0; +} + + +static int +dump_file_map_stats(int argc, char** argv) +{ + off_t minSize = 0; + off_t maxSize = -1; + + if (argc == 2) { + maxSize = parse_expression(argv[1]); + } else if (argc > 2) { + minSize = parse_expression(argv[1]); + maxSize = parse_expression(argv[2]); + } + + FileMapList::Iterator iterator = sList.GetIterator(); + off_t size = 0; + off_t mapSize = 0; + uint32 extents = 0; + uint32 count = 0; + uint32 emptyCount = 0; + + while (iterator.HasNext()) { + FileMap* map = iterator.Next(); + + if (minSize > map->Size() || (maxSize != -1 && maxSize < map->Size())) + continue; + + if (map->Count() != 0) { + file_extent* extent = map->ExtentAt(map->Count() - 1); + if (extent != NULL) + mapSize += extent->offset + extent->disk.length; + + extents += map->Count(); + } else + emptyCount++; + + size += map->Size(); + count++; + } + + kprintf("%ld file maps (%ld empty), %Ld file bytes in total, %Ld bytes " + "cached, %lu extents\n", count, emptyCount, size, mapSize, extents); + kprintf("average %lu extents per map for %Ld bytes.\n", + extents / (count - emptyCount), mapSize / (count - emptyCount)); + + return 0; +} + +#endif // DEBUG_FILE_MAP + + +// #pragma mark - private kernel API + + +extern "C" status_t +file_map_init(void) +{ +#if DEBUG_FILE_MAP + add_debugger_command_etc("file_map", &dump_file_map, + "Dumps the specified file map.", + "[-p] \n" + " -p - causes the file extents to be printed as well.\n" + " - pointer to the file map.\n", 0); + add_debugger_command("file_map_stats", &dump_file_map_stats, + "Dumps some file map statistics."); + + mutex_init(&sLock, "file map list"); +#endif + return B_OK; +} + + +// #pragma mark - public FS API + + +extern "C" void* +file_map_create(dev_t mountID, ino_t vnodeID, off_t size) +{ + TRACE("file_map_create(mountID = %ld, vnodeID = %Ld, size = %Ld)\n", + mountID, vnodeID, size); + + // Get the vnode for the object + // (note, this does not grab a reference to the node) + struct vnode* vnode; + if (vfs_lookup_vnode(mountID, vnodeID, &vnode) != B_OK) + return NULL; + + return new(std::nothrow) FileMap(vnode, size); +} + + +extern "C" void +file_map_delete(void* _map) +{ + FileMap* map = (FileMap*)_map; + if (map == NULL) + return; + + TRACE("file_map_delete(map = %p)\n", map); + delete map; +} + + +extern "C" void +file_map_set_size(void* _map, off_t size) +{ + FileMap* map = (FileMap*)_map; + if (map == NULL) + return; + + map->SetSize(size); +} + + +extern "C" void +file_map_invalidate(void* _map, off_t offset, off_t size) +{ + FileMap* map = (FileMap*)_map; + if (map == NULL) + return; + + map->Invalidate(offset, size); +} + + +extern "C" status_t +file_map_set_mode(void* _map, uint32 mode) +{ + FileMap* map = (FileMap*)_map; + if (map == NULL) + return B_BAD_VALUE; + + return map->SetMode(mode); +} + + +extern "C" status_t +file_map_translate(void* _map, off_t offset, size_t size, file_io_vec* vecs, + size_t* _count, size_t align) +{ + TRACE("file_map_translate(map %p, offset %Ld, size %ld)\n", + _map, offset, size); + + FileMap* map = (FileMap*)_map; + if (map == NULL) + return B_BAD_VALUE; + + return map->Translate(offset, size, vecs, _count, align); +} + diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/haiku_kernel_emu.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/haiku_kernel_emu.cpp index 4fd7d5cb63..e579cfab70 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/haiku_kernel_emu.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/haiku_kernel_emu.cpp @@ -13,6 +13,7 @@ #include "HaikuKernelNode.h" #include "HaikuKernelVolume.h" #include "kernel_emu.h" +#include "vfs.h" // #pragma mark - Notifications @@ -226,6 +227,26 @@ volume_for_vnode(fs_vnode *vnode) } +// #pragma mark - VFS private + + +status_t +vfs_get_file_map(struct vnode *vnode, off_t offset, size_t size, + struct file_io_vec *vecs, size_t *_count) +{ + // TODO: Implement! + return B_BAD_VALUE; +} + + +status_t +vfs_lookup_vnode(dev_t mountID, ino_t vnodeID, struct vnode **_vnode) +{ + // TODO: Implement! + return B_BAD_VALUE; +} + + // #pragma mark - Misc diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/haiku/vfs.h b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/vfs.h new file mode 100644 index 0000000000..4ff03e9065 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/haiku/vfs.h @@ -0,0 +1,23 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef USERLAND_FS_HAIKU_VFS_H +#define USERLAND_FS_HAIKU_VFS_H + +#include + + +struct vnode; + +extern "C" { + + +status_t vfs_get_file_map(struct vnode *vnode, off_t offset, size_t size, + struct file_io_vec *vecs, size_t *_count); +status_t vfs_lookup_vnode(dev_t mountID, ino_t vnodeID, struct vnode **_vnode); + + +} // extern "C" + +#endif // USERLAND_FS_HAIKU_VFS_H