mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:52:45 +00:00 
			
		
		
		
	Kernel: Split BlockBasedFileSystem off FileBackedFileSystem
FileBackedFileSystem is one that's backed by (mounted from) a file, in other
words one that has a "source" of the mount; that doesn't mean it deals in
blocks. The hierarchy now becomes:
* FS
  * ProcFS
  * DevPtsFS
  * TmpFS
  * FileBackedFS
    * (future) Plan9FS
    * BlockBasedFS
      * Ext2FS
			
			
This commit is contained in:
		
							parent
							
								
									0c72a9eda7
								
							
						
					
					
						commit
						187b785a05
					
				
					 7 changed files with 371 additions and 294 deletions
				
			
		
							
								
								
									
										299
									
								
								Kernel/FileSystem/BlockBasedFileSystem.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								Kernel/FileSystem/BlockBasedFileSystem.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,299 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <Kernel/FileSystem/BlockBasedFileSystem.h> | ||||
| #include <Kernel/Process.h> | ||||
| 
 | ||||
| //#define BBFS_DEBUG
 | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| struct CacheEntry { | ||||
|     time_t timestamp { 0 }; | ||||
|     u32 block_index { 0 }; | ||||
|     u8* data { nullptr }; | ||||
|     bool has_data { false }; | ||||
|     bool is_dirty { false }; | ||||
| }; | ||||
| 
 | ||||
| class DiskCache { | ||||
| public: | ||||
|     explicit DiskCache(BlockBasedFS& fs) | ||||
|         : m_fs(fs) | ||||
|         , m_cached_block_data(KBuffer::create_with_size(m_entry_count * m_fs.block_size())) | ||||
|         , m_entries(KBuffer::create_with_size(m_entry_count * sizeof(CacheEntry))) | ||||
|     { | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) { | ||||
|             entries()[i].data = m_cached_block_data.data() + i * m_fs.block_size(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ~DiskCache() { } | ||||
| 
 | ||||
|     bool is_dirty() const { return m_dirty; } | ||||
|     void set_dirty(bool b) { m_dirty = b; } | ||||
| 
 | ||||
|     CacheEntry& get(u32 block_index) const | ||||
|     { | ||||
|         auto now = kgettimeofday().tv_sec; | ||||
| 
 | ||||
|         CacheEntry* oldest_clean_entry = nullptr; | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) { | ||||
|             auto& entry = const_cast<CacheEntry&>(entries()[i]); | ||||
|             if (entry.block_index == block_index) { | ||||
|                 entry.timestamp = now; | ||||
|                 return entry; | ||||
|             } | ||||
|             if (!entry.is_dirty) { | ||||
|                 if (!oldest_clean_entry) | ||||
|                     oldest_clean_entry = &entry; | ||||
|                 else if (entry.timestamp < oldest_clean_entry->timestamp) | ||||
|                     oldest_clean_entry = &entry; | ||||
|             } | ||||
|         } | ||||
|         if (!oldest_clean_entry) { | ||||
|             // Not a single clean entry! Flush writes and try again.
 | ||||
|             // NOTE: We want to make sure we only call FileBackedFS flush here,
 | ||||
|             //       not some FileBackedFS subclass flush!
 | ||||
|             m_fs.flush_writes_impl(); | ||||
|             return get(block_index); | ||||
|         } | ||||
| 
 | ||||
|         // Replace the oldest clean entry.
 | ||||
|         auto& new_entry = *oldest_clean_entry; | ||||
|         new_entry.timestamp = now; | ||||
|         new_entry.block_index = block_index; | ||||
|         new_entry.has_data = false; | ||||
|         new_entry.is_dirty = false; | ||||
|         return new_entry; | ||||
|     } | ||||
| 
 | ||||
|     const CacheEntry* entries() const { return (const CacheEntry*)m_entries.data(); } | ||||
|     CacheEntry* entries() { return (CacheEntry*)m_entries.data(); } | ||||
| 
 | ||||
|     template<typename Callback> | ||||
|     void for_each_entry(Callback callback) | ||||
|     { | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) | ||||
|             callback(entries()[i]); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     BlockBasedFS& m_fs; | ||||
|     size_t m_entry_count { 10000 }; | ||||
|     KBuffer m_cached_block_data; | ||||
|     KBuffer m_entries; | ||||
|     bool m_dirty { false }; | ||||
| }; | ||||
| 
 | ||||
| BlockBasedFS::BlockBasedFS(FileDescription& file_description) | ||||
|     : FileBackedFS(file_description) | ||||
| { | ||||
|     ASSERT(file_description.file().is_seekable()); | ||||
| } | ||||
| 
 | ||||
| BlockBasedFS::~BlockBasedFS() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::write_block(unsigned index, const u8* data, size_t count, size_t offset, bool allow_cache) | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     ASSERT(offset + count <= block_size()); | ||||
| #ifdef BBFS_DEBUG | ||||
|     klog() << "BlockBasedFileSystem::write_block " << index << ", size=" << data.size(); | ||||
| #endif | ||||
| 
 | ||||
|     if (!allow_cache) { | ||||
|         flush_specific_block_if_needed(index); | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()) + offset; | ||||
|         file_description().seek(base_offset, SEEK_SET); | ||||
|         auto nwritten = file_description().write(data, count); | ||||
|         if (nwritten < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nwritten) == count); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     auto& entry = cache().get(index); | ||||
|     if (count < block_size()) { | ||||
|         // Fill the cache first.
 | ||||
|         read_block(index, nullptr, block_size()); | ||||
|     } | ||||
|     memcpy(entry.data + offset, data, count); | ||||
|     entry.is_dirty = true; | ||||
|     entry.has_data = true; | ||||
| 
 | ||||
|     cache().set_dirty(true); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::raw_read(unsigned index, u8* buffer) | ||||
| { | ||||
|     u32 base_offset = static_cast<u32>(index) * static_cast<u32>(m_logical_block_size); | ||||
|     file_description().seek(base_offset, SEEK_SET); | ||||
|     auto nread = file_description().read(buffer, m_logical_block_size); | ||||
|     ASSERT((size_t)nread == m_logical_block_size); | ||||
|     return true; | ||||
| } | ||||
| bool BlockBasedFS::raw_write(unsigned index, const u8* buffer) | ||||
| { | ||||
|     u32 base_offset = static_cast<u32>(index) * static_cast<u32>(m_logical_block_size); | ||||
|     file_description().seek(base_offset, SEEK_SET); | ||||
|     auto nwritten = file_description().write(buffer, m_logical_block_size); | ||||
|     ASSERT((size_t)nwritten == m_logical_block_size); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::raw_read_blocks(unsigned index, size_t count, u8* buffer) | ||||
| { | ||||
|     for (unsigned block = index; block < (index + count); block++) { | ||||
|         if (!raw_read(block, buffer)) | ||||
|             return false; | ||||
|         buffer += logical_block_size(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| bool BlockBasedFS::raw_write_blocks(unsigned index, size_t count, const u8* buffer) | ||||
| { | ||||
|     for (unsigned block = index; block < (index + count); block++) { | ||||
|         if (!raw_write(block, buffer)) | ||||
|             return false; | ||||
|         buffer += logical_block_size(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::write_blocks(unsigned index, unsigned count, const u8* data, bool allow_cache) | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
| #ifdef BBFS_DEBUG | ||||
|     klog() << "BlockBasedFileSystem::write_blocks " << index << " x" << count; | ||||
| #endif | ||||
|     for (unsigned i = 0; i < count; ++i) | ||||
|         write_block(index + i, data + i * block_size(), block_size(), 0, allow_cache); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::read_block(unsigned index, u8* buffer, size_t count, size_t offset, bool allow_cache) const | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     ASSERT(offset + count <= block_size()); | ||||
| #ifdef BBFS_DEBUG | ||||
|     klog() << "BlockBasedFileSystem::read_block " << index; | ||||
| #endif | ||||
| 
 | ||||
|     if (!allow_cache) { | ||||
|         const_cast<BlockBasedFS*>(this)->flush_specific_block_if_needed(index); | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()) + static_cast<u32>(offset); | ||||
|         file_description().seek(base_offset, SEEK_SET); | ||||
|         auto nread = file_description().read(buffer, count); | ||||
|         if (nread < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nread) == count); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     auto& entry = cache().get(index); | ||||
|     if (!entry.has_data) { | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()); | ||||
|         file_description().seek(base_offset, SEEK_SET); | ||||
|         auto nread = file_description().read(entry.data, block_size()); | ||||
|         if (nread < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nread) == block_size()); | ||||
|         entry.has_data = true; | ||||
|     } | ||||
|     if (buffer) | ||||
|         memcpy(buffer, entry.data + offset, count); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlockBasedFS::read_blocks(unsigned index, unsigned count, u8* buffer, bool allow_cache) const | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     if (!count) | ||||
|         return false; | ||||
|     if (count == 1) | ||||
|         return read_block(index, buffer, block_size(), 0, allow_cache); | ||||
|     u8* out = buffer; | ||||
| 
 | ||||
|     for (unsigned i = 0; i < count; ++i) { | ||||
|         if (!read_block(index + i, out, block_size(), 0, allow_cache)) | ||||
|             return false; | ||||
|         out += block_size(); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void BlockBasedFS::flush_specific_block_if_needed(unsigned index) | ||||
| { | ||||
|     LOCKER(m_lock); | ||||
|     if (!cache().is_dirty()) | ||||
|         return; | ||||
|     cache().for_each_entry([&](CacheEntry& entry) { | ||||
|         if (entry.is_dirty && entry.block_index == index) { | ||||
|             u32 base_offset = static_cast<u32>(entry.block_index) * static_cast<u32>(block_size()); | ||||
|             file_description().seek(base_offset, SEEK_SET); | ||||
|             file_description().write(entry.data, block_size()); | ||||
|             entry.is_dirty = false; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void BlockBasedFS::flush_writes_impl() | ||||
| { | ||||
|     LOCKER(m_lock); | ||||
|     if (!cache().is_dirty()) | ||||
|         return; | ||||
|     u32 count = 0; | ||||
|     cache().for_each_entry([&](CacheEntry& entry) { | ||||
|         if (!entry.is_dirty) | ||||
|             return; | ||||
|         u32 base_offset = static_cast<u32>(entry.block_index) * static_cast<u32>(block_size()); | ||||
|         file_description().seek(base_offset, SEEK_SET); | ||||
|         file_description().write(entry.data, block_size()); | ||||
|         ++count; | ||||
|         entry.is_dirty = false; | ||||
|     }); | ||||
|     cache().set_dirty(false); | ||||
|     dbg() << class_name() << ": Flushed " << count << " blocks to disk"; | ||||
| } | ||||
| 
 | ||||
| void BlockBasedFS::flush_writes() | ||||
| { | ||||
|     flush_writes_impl(); | ||||
| } | ||||
| 
 | ||||
| DiskCache& BlockBasedFS::cache() const | ||||
| { | ||||
|     if (!m_cache) | ||||
|         m_cache = make<DiskCache>(const_cast<BlockBasedFS&>(*this)); | ||||
|     return *m_cache; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										66
									
								
								Kernel/FileSystem/BlockBasedFileSystem.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Kernel/FileSystem/BlockBasedFileSystem.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <Kernel/FileSystem/FileBackedFileSystem.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class BlockBasedFS : public FileBackedFS { | ||||
| public: | ||||
|     virtual ~BlockBasedFS() override; | ||||
| 
 | ||||
|     size_t logical_block_size() const { return m_logical_block_size; }; | ||||
| 
 | ||||
|     virtual void flush_writes() override; | ||||
|     void flush_writes_impl(); | ||||
| 
 | ||||
| protected: | ||||
|     explicit BlockBasedFS(FileDescription&); | ||||
| 
 | ||||
|     bool read_block(unsigned index, u8* buffer, size_t count, size_t offset = 0, bool allow_cache = true) const; | ||||
|     bool read_blocks(unsigned index, unsigned count, u8* buffer, bool allow_cache = true) const; | ||||
| 
 | ||||
|     bool raw_read(unsigned index, u8* buffer); | ||||
|     bool raw_write(unsigned index, const u8* buffer); | ||||
| 
 | ||||
|     bool raw_read_blocks(unsigned index, size_t count, u8* buffer); | ||||
|     bool raw_write_blocks(unsigned index, size_t count, const u8* buffer); | ||||
| 
 | ||||
|     bool write_block(unsigned index, const u8* buffer, size_t count, size_t offset = 0, bool allow_cache = true); | ||||
|     bool write_blocks(unsigned index, unsigned count, const u8*, bool allow_cache = true); | ||||
| 
 | ||||
|     size_t m_logical_block_size { 512 }; | ||||
| 
 | ||||
| private: | ||||
|     DiskCache& cache() const; | ||||
|     void flush_specific_block_if_needed(unsigned index); | ||||
| 
 | ||||
|     mutable OwnPtr<DiskCache> m_cache; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | @ -70,7 +70,7 @@ NonnullRefPtr<Ext2FS> Ext2FS::create(FileDescription& file_description) | |||
| } | ||||
| 
 | ||||
| Ext2FS::Ext2FS(FileDescription& file_description) | ||||
|     : FileBackedFS(file_description) | ||||
|     : BlockBasedFS(file_description) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -548,7 +548,7 @@ void Ext2FS::flush_writes() | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     FileBackedFS::flush_writes(); | ||||
|     BlockBasedFS::flush_writes(); | ||||
| 
 | ||||
|     // Uncache Inodes that are only kept alive by the index-to-inode lookup cache.
 | ||||
|     // We don't uncache Inodes that are being watched by at least one InodeWatcher.
 | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ | |||
| 
 | ||||
| #include <AK/Bitmap.h> | ||||
| #include <AK/HashMap.h> | ||||
| #include <Kernel/FileSystem/FileBackedFileSystem.h> | ||||
| #include <Kernel/FileSystem/BlockBasedFileSystem.h> | ||||
| #include <Kernel/FileSystem/Inode.h> | ||||
| #include <Kernel/FileSystem/ext2_fs.h> | ||||
| #include <Kernel/KBuffer.h> | ||||
|  | @ -89,7 +89,7 @@ private: | |||
|     ext2_inode m_raw_inode; | ||||
| }; | ||||
| 
 | ||||
| class Ext2FS final : public FileBackedFS { | ||||
| class Ext2FS final : public BlockBasedFS { | ||||
|     friend class Ext2FSInode; | ||||
| 
 | ||||
| public: | ||||
|  |  | |||
|  | @ -24,281 +24,17 @@ | |||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <AK/StringView.h> | ||||
| #include <Kernel/Arch/i386/CPU.h> | ||||
| #include <Kernel/Devices/BlockDevice.h> | ||||
| #include <Kernel/FileSystem/FileBackedFileSystem.h> | ||||
| #include <Kernel/FileSystem/FileDescription.h> | ||||
| #include <Kernel/KBuffer.h> | ||||
| #include <Kernel/Process.h> | ||||
| 
 | ||||
| //#define FBFS_DEBUG
 | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| struct CacheEntry { | ||||
|     time_t timestamp { 0 }; | ||||
|     u32 block_index { 0 }; | ||||
|     u8* data { nullptr }; | ||||
|     bool has_data { false }; | ||||
|     bool is_dirty { false }; | ||||
| }; | ||||
| 
 | ||||
| class DiskCache { | ||||
| public: | ||||
|     explicit DiskCache(FileBackedFS& fs) | ||||
|         : m_fs(fs) | ||||
|         , m_cached_block_data(KBuffer::create_with_size(m_entry_count * m_fs.block_size())) | ||||
|         , m_entries(KBuffer::create_with_size(m_entry_count * sizeof(CacheEntry))) | ||||
|     { | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) { | ||||
|             entries()[i].data = m_cached_block_data.data() + i * m_fs.block_size(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ~DiskCache() { } | ||||
| 
 | ||||
|     bool is_dirty() const { return m_dirty; } | ||||
|     void set_dirty(bool b) { m_dirty = b; } | ||||
| 
 | ||||
|     CacheEntry& get(u32 block_index) const | ||||
|     { | ||||
|         auto now = kgettimeofday().tv_sec; | ||||
| 
 | ||||
|         CacheEntry* oldest_clean_entry = nullptr; | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) { | ||||
|             auto& entry = const_cast<CacheEntry&>(entries()[i]); | ||||
|             if (entry.block_index == block_index) { | ||||
|                 entry.timestamp = now; | ||||
|                 return entry; | ||||
|             } | ||||
|             if (!entry.is_dirty) { | ||||
|                 if (!oldest_clean_entry) | ||||
|                     oldest_clean_entry = &entry; | ||||
|                 else if (entry.timestamp < oldest_clean_entry->timestamp) | ||||
|                     oldest_clean_entry = &entry; | ||||
|             } | ||||
|         } | ||||
|         if (!oldest_clean_entry) { | ||||
|             // Not a single clean entry! Flush writes and try again.
 | ||||
|             // NOTE: We want to make sure we only call FileBackedFS flush here,
 | ||||
|             //       not some FileBackedFS subclass flush!
 | ||||
|             m_fs.flush_writes_impl(); | ||||
|             return get(block_index); | ||||
|         } | ||||
| 
 | ||||
|         // Replace the oldest clean entry.
 | ||||
|         auto& new_entry = *oldest_clean_entry; | ||||
|         new_entry.timestamp = now; | ||||
|         new_entry.block_index = block_index; | ||||
|         new_entry.has_data = false; | ||||
|         new_entry.is_dirty = false; | ||||
|         return new_entry; | ||||
|     } | ||||
| 
 | ||||
|     const CacheEntry* entries() const { return (const CacheEntry*)m_entries.data(); } | ||||
|     CacheEntry* entries() { return (CacheEntry*)m_entries.data(); } | ||||
| 
 | ||||
|     template<typename Callback> | ||||
|     void for_each_entry(Callback callback) | ||||
|     { | ||||
|         for (size_t i = 0; i < m_entry_count; ++i) | ||||
|             callback(entries()[i]); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     FileBackedFS& m_fs; | ||||
|     size_t m_entry_count { 10000 }; | ||||
|     KBuffer m_cached_block_data; | ||||
|     KBuffer m_entries; | ||||
|     bool m_dirty { false }; | ||||
| }; | ||||
| 
 | ||||
| FileBackedFS::FileBackedFS(FileDescription& file_description) | ||||
|     : m_file_description(file_description) | ||||
| { | ||||
|     ASSERT(m_file_description->file().is_seekable()); | ||||
| } | ||||
| 
 | ||||
| FileBackedFS::~FileBackedFS() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::write_block(unsigned index, const u8* data, size_t count, size_t offset, bool allow_cache) | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     ASSERT(offset + count <= block_size()); | ||||
| #ifdef FBFS_DEBUG | ||||
|     klog() << "FileBackedFileSystem::write_block " << index << ", size=" << data.size(); | ||||
| #endif | ||||
| 
 | ||||
|     if (!allow_cache) { | ||||
|         flush_specific_block_if_needed(index); | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()) + offset; | ||||
|         m_file_description->seek(base_offset, SEEK_SET); | ||||
|         auto nwritten = m_file_description->write(data, count); | ||||
|         if (nwritten < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nwritten) == count); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     auto& entry = cache().get(index); | ||||
|     if (count < block_size()) { | ||||
|         // Fill the cache first.
 | ||||
|         read_block(index, nullptr, block_size()); | ||||
|     } | ||||
|     memcpy(entry.data + offset, data, count); | ||||
|     entry.is_dirty = true; | ||||
|     entry.has_data = true; | ||||
| 
 | ||||
|     cache().set_dirty(true); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::raw_read(unsigned index, u8* buffer) | ||||
| { | ||||
|     u32 base_offset = static_cast<u32>(index) * static_cast<u32>(m_logical_block_size); | ||||
|     m_file_description->seek(base_offset, SEEK_SET); | ||||
|     auto nread = m_file_description->read(buffer, m_logical_block_size); | ||||
|     ASSERT((size_t)nread == m_logical_block_size); | ||||
|     return true; | ||||
| } | ||||
| bool FileBackedFS::raw_write(unsigned index, const u8* buffer) | ||||
| { | ||||
|     u32 base_offset = static_cast<u32>(index) * static_cast<u32>(m_logical_block_size); | ||||
|     m_file_description->seek(base_offset, SEEK_SET); | ||||
|     auto nwritten = m_file_description->write(buffer, m_logical_block_size); | ||||
|     ASSERT((size_t)nwritten == m_logical_block_size); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::raw_read_blocks(unsigned index, size_t count, u8* buffer) | ||||
| { | ||||
|     for (unsigned block = index; block < (index + count); block++) { | ||||
|         if (!raw_read(block, buffer)) | ||||
|             return false; | ||||
|         buffer += logical_block_size(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| bool FileBackedFS::raw_write_blocks(unsigned index, size_t count, const u8* buffer) | ||||
| { | ||||
|     for (unsigned block = index; block < (index + count); block++) { | ||||
|         if (!raw_write(block, buffer)) | ||||
|             return false; | ||||
|         buffer += logical_block_size(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::write_blocks(unsigned index, unsigned count, const u8* data, bool allow_cache) | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
| #ifdef FBFS_DEBUG | ||||
|     klog() << "FileBackedFileSystem::write_blocks " << index << " x" << count; | ||||
| #endif | ||||
|     for (unsigned i = 0; i < count; ++i) | ||||
|         write_block(index + i, data + i * block_size(), block_size(), 0, allow_cache); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::read_block(unsigned index, u8* buffer, size_t count, size_t offset, bool allow_cache) const | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     ASSERT(offset + count <= block_size()); | ||||
| #ifdef FBFS_DEBUG | ||||
|     klog() << "FileBackedFileSystem::read_block " << index; | ||||
| #endif | ||||
| 
 | ||||
|     if (!allow_cache) { | ||||
|         const_cast<FileBackedFS*>(this)->flush_specific_block_if_needed(index); | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()) + static_cast<u32>(offset); | ||||
|         m_file_description->seek(base_offset, SEEK_SET); | ||||
|         auto nread = m_file_description->read(buffer, count); | ||||
|         if (nread < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nread) == count); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     auto& entry = cache().get(index); | ||||
|     if (!entry.has_data) { | ||||
|         u32 base_offset = static_cast<u32>(index) * static_cast<u32>(block_size()); | ||||
|         m_file_description->seek(base_offset, SEEK_SET); | ||||
|         auto nread = m_file_description->read(entry.data, block_size()); | ||||
|         if (nread < 0) | ||||
|             return false; | ||||
|         ASSERT(static_cast<size_t>(nread) == block_size()); | ||||
|         entry.has_data = true; | ||||
|     } | ||||
|     if (buffer) | ||||
|         memcpy(buffer, entry.data + offset, count); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool FileBackedFS::read_blocks(unsigned index, unsigned count, u8* buffer, bool allow_cache) const | ||||
| { | ||||
|     ASSERT(m_logical_block_size); | ||||
|     if (!count) | ||||
|         return false; | ||||
|     if (count == 1) | ||||
|         return read_block(index, buffer, block_size(), 0, allow_cache); | ||||
|     u8* out = buffer; | ||||
| 
 | ||||
|     for (unsigned i = 0; i < count; ++i) { | ||||
|         if (!read_block(index + i, out, block_size(), 0, allow_cache)) | ||||
|             return false; | ||||
|         out += block_size(); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void FileBackedFS::flush_specific_block_if_needed(unsigned index) | ||||
| { | ||||
|     LOCKER(m_lock); | ||||
|     if (!cache().is_dirty()) | ||||
|         return; | ||||
|     cache().for_each_entry([&](CacheEntry& entry) { | ||||
|         if (entry.is_dirty && entry.block_index == index) { | ||||
|             u32 base_offset = static_cast<u32>(entry.block_index) * static_cast<u32>(block_size()); | ||||
|             m_file_description->seek(base_offset, SEEK_SET); | ||||
|             m_file_description->write(entry.data, block_size()); | ||||
|             entry.is_dirty = false; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void FileBackedFS::flush_writes_impl() | ||||
| { | ||||
|     LOCKER(m_lock); | ||||
|     if (!cache().is_dirty()) | ||||
|         return; | ||||
|     u32 count = 0; | ||||
|     cache().for_each_entry([&](CacheEntry& entry) { | ||||
|         if (!entry.is_dirty) | ||||
|             return; | ||||
|         u32 base_offset = static_cast<u32>(entry.block_index) * static_cast<u32>(block_size()); | ||||
|         m_file_description->seek(base_offset, SEEK_SET); | ||||
|         m_file_description->write(entry.data, block_size()); | ||||
|         ++count; | ||||
|         entry.is_dirty = false; | ||||
|     }); | ||||
|     cache().set_dirty(false); | ||||
|     dbg() << class_name() << ": Flushed " << count << " blocks to disk"; | ||||
| } | ||||
| 
 | ||||
| void FileBackedFS::flush_writes() | ||||
| { | ||||
|     flush_writes_impl(); | ||||
| } | ||||
| 
 | ||||
| DiskCache& FileBackedFS::cache() const | ||||
| { | ||||
|     if (!m_cache) | ||||
|         m_cache = make<DiskCache>(const_cast<FileBackedFS&>(*this)); | ||||
|     return *m_cache; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ | |||
| 
 | ||||
| #include <Kernel/FileSystem/FileDescription.h> | ||||
| #include <Kernel/FileSystem/FileSystem.h> | ||||
| #include <Kernel/Forward.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
|  | @ -39,39 +38,15 @@ public: | |||
|     File& file() { return m_file_description->file(); } | ||||
|     FileDescription& file_description() { return *m_file_description; } | ||||
|     const File& file() const { return m_file_description->file(); } | ||||
|     const FileDescription& file_description() const { return *m_file_description; } | ||||
| 
 | ||||
|     virtual void flush_writes() override; | ||||
| 
 | ||||
|     void flush_writes_impl(); | ||||
| 
 | ||||
|     size_t logical_block_size() const { return m_logical_block_size; }; | ||||
|     FileDescription& file_description() const { return *m_file_description; } | ||||
| 
 | ||||
| protected: | ||||
|     explicit FileBackedFS(FileDescription&); | ||||
| 
 | ||||
|     bool read_block(unsigned index, u8* buffer, size_t count, size_t offset = 0, bool allow_cache = true) const; | ||||
|     bool read_blocks(unsigned index, unsigned count, u8* buffer, bool allow_cache = true) const; | ||||
| 
 | ||||
|     bool raw_read(unsigned index, u8* buffer); | ||||
|     bool raw_write(unsigned index, const u8* buffer); | ||||
| 
 | ||||
|     bool raw_read_blocks(unsigned index, size_t count, u8* buffer); | ||||
|     bool raw_write_blocks(unsigned index, size_t count, const u8* buffer); | ||||
| 
 | ||||
|     bool write_block(unsigned index, const u8* buffer, size_t count, size_t offset = 0, bool allow_cache = true); | ||||
|     bool write_blocks(unsigned index, unsigned count, const u8*, bool allow_cache = true); | ||||
| 
 | ||||
|     size_t m_logical_block_size { 512 }; | ||||
| 
 | ||||
| private: | ||||
|     virtual bool is_file_backed() const override { return true; } | ||||
| 
 | ||||
|     DiskCache& cache() const; | ||||
|     void flush_specific_block_if_needed(unsigned index); | ||||
| 
 | ||||
|     mutable NonnullRefPtr<FileDescription> m_file_description; | ||||
|     mutable OwnPtr<DiskCache> m_cache; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sergey Bugaev
						Sergey Bugaev