├── .gitignore ├── Makefile ├── README.md ├── include ├── CL │ └── debugcl.hpp ├── entry.hpp ├── memory.hpp ├── util.hpp └── vramfs.hpp ├── mount.sh ├── src ├── dir.cpp ├── entry.cpp ├── file.cpp ├── memory.cpp ├── symlink.cpp ├── util.cpp └── vramfs.cpp └── umount.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | bin/ 3 | build/ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = g++ 2 | CFLAGS = -Wall -Wpedantic -Werror -std=c++11 $(shell pkg-config fuse3 --cflags) -I include/ 3 | LDFLAGS = -flto $(shell pkg-config fuse3 --libs) -l OpenCL 4 | 5 | ifeq ($(DEBUG), 1) 6 | CFLAGS += -g -DDEBUG -Wall -Werror -std=c++11 7 | else 8 | CFLAGS += -march=native -O2 -flto 9 | endif 10 | 11 | bin/vramfs: build/util.o build/memory.o build/entry.o build/file.o build/dir.o build/symlink.o build/vramfs.o | bin 12 | $(CC) -o $@ $^ $(LDFLAGS) 13 | 14 | build bin: 15 | @mkdir -p $@ 16 | 17 | build/%.o: src/%.cpp | build 18 | $(CC) $(CFLAGS) -c -o $@ $< 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -rf build/ bin/ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vramfs 2 | ====== 3 | 4 | Unused RAM is wasted RAM, so why not put some of that VRAM in your graphics card 5 | to work? 6 | 7 | vramfs is a utility that uses the [FUSE library](http://fuse.sourceforge.net/) 8 | to create a file system in VRAM. The idea is pretty much the same as a ramdisk, 9 | except that it uses the video RAM of a discrete graphics card to store 10 | files. It is not intented for serious use, but it does actually work fairly 11 | well, especially since consumer GPUs with 4GB or more VRAM are now available. 12 | 13 | On the developer's system, the continuous read performance is ~2.4 GB/s and 14 | write performance 2.0 GB/s, which is about 1/3 of what is achievable with a 15 | ramdisk. That is already decent enough for a device not designed for large data 16 | transfers to the host, but future development should aim to get closer to the 17 | PCI-e bandwidth limits. See the *benchmarks* section for more info. 18 | 19 | #### Requirements 20 | 21 | - Linux with kernel 2.6+ 22 | - FUSE development files 23 | - A graphics card with support for OpenCL 1.2 24 | 25 | #### Building 26 | 27 | First, install the OpenCL driver for your graphics card and verify that it's 28 | recognized as an OpenCL device by running `clinfo`. Then install the `libfuse3-dev` 29 | package or build it from source. You will also need `pkg-config` and OpenCL 30 | development files, (`opencl-dev`, `opencl-clhpp-headers` package or equivalent), 31 | with version 1.2 of the OpenCL headers at least. 32 | 33 | Just run `make` to build `vramfs`. 34 | 35 | If you want to debug with valgrind, you should compile with the minimal fake 36 | OpenCL implementation to avoid filling your screen with warnings caused by the 37 | OpenCL driver: 38 | 39 | * **valgrind:** `make DEBUG=1` 40 | 41 | #### Mounting 42 | 43 | Mount a disk by running `bin/vramfs `. The `mountdir` can be 44 | any empty directory. The `size` is the disk size in bytes. For more information, 45 | run `bin/vramfs` without arguments. 46 | 47 | The recommended maximum size of a vramdisk is 50% of your VRAM. If you go over 48 | that, your driver or system may become unstable because it has to start 49 | swapping. For example, webpages in Chrome will stop rendering properly. 50 | 51 | If the disk has been inactive for a while, the graphics card will likely lower 52 | its memory clock, which means it'll take a second to get up to speed again. 53 | 54 | Implementation 55 | -------------- 56 | 57 | The FUSE library is used to implement vramfs as a user space file system. This 58 | eases development and makes working with APIs such as OpenCL straightforward. 59 | 60 | #### Basic architecture 61 | 62 | ![Architecture overview](http://i.imgur.com/e8tQ168.png) 63 | 64 | When the program is started, it checks for an OpenCL capable GPU and attempts to 65 | allocate the specified amount of memory. Once the memory has been allocated, the 66 | root entry object is created and a global reference to it is stored. 67 | 68 | FUSE then forwards calls like `stat`, `readdir` and `write` to the file system 69 | functions. These will then locate the entry through the root entry using the 70 | specified path. The required operations will then be performed on the entry 71 | object. If the entry is a file object, the operation may lead to OpenCL 72 | `cvEnqueueReadBuffer` or `cvEnqueueWriteBuffer` calls to manipulate the data. 73 | 74 | When a file is created or opened, a `file_session` object is created to store 75 | the reference to the file object and any other data that is persistent between 76 | an `fopen` and `fclose` call. 77 | 78 | #### VRAM block allocation 79 | 80 | OpenCL is used to allocate blocks of memory on the graphics card by creating 81 | buffer objects. When a new disk is mounted, a pool of `disk size / block size` 82 | buffers is created and initialised with zeros. That is not just a good practice, 83 | but it's also required with some OpenCL drivers to check if the VRAM required 84 | for the block is actually available. Unfortunately Nvidia cards don't support 85 | OpenCL 1.2, which means the `cvEnqueueFillBuffer` call has to be simulated by 86 | copying from a preallocated buffer filled with zeros. Somewhat interestingly, it 87 | doesn't seem to make a difference in performance on cards that support both. 88 | 89 | Writes to blocks are generally asynchronous, whereas reads are synchronous. 90 | Luckily, OpenCL guarantees in-order execution of commands by default, which 91 | means reads of a block will wait for the writes to complete. OpenCL 1.1 is 92 | completely thread safe, so no special care is required when sending commands. 93 | 94 | Block objects are managed using a `shared_ptr` so that they can automatically 95 | reinsert themselves into the pool on deconstruction. 96 | 97 | #### File system 98 | 99 | The file system is a tree of `entry_t` objects with members for attributes like 100 | the parent directory, mode and access time. Each type of entry has its own 101 | subclass that derives from it: `file_t`, `dir_t` and `symlink_t`. The main file 102 | that implements all of the FUSE callbacks has a permanent reference to the root 103 | directory entry. 104 | 105 | The `file_t` class contains extra `write`, `read` and `size` methods and manages 106 | the blocks to store the file data. 107 | 108 | The `dir_t` class has an extra `unordered_map` that maps names to `entry_t` 109 | references for quick child lookup using its member function `find`. 110 | 111 | Finally, the `symlink_t` class has an extra `target` string member that stores 112 | the pointer of the symlink. 113 | 114 | All of the entry objects are also managed using `shared_ptr` so that an object 115 | and its data (e.g. file blocks) are automatically deallocated when they're 116 | unlinked and no process holds a file handle to them anymore. This can also be 117 | used to easily implement hard links later on. 118 | 119 | The classes use getter/setter functions to automatically update the access, 120 | modification and change times at the appropriate moment. For example, calling 121 | the `children` member function of `dir_t` changes the access time and change 122 | time of the directory. 123 | 124 | #### Thread safety 125 | 126 | Unfortunately most of the operations are not thread safe, so all of the FUSE 127 | callbacks share a mutex to ensure that only one thread is mutating the file 128 | system at a time. The exceptions are `read` and `write`, which will temporarily 129 | release the lock while waiting for a read or write to complete. 130 | 131 | Benchmarks 132 | ---------- 133 | 134 | The system used for testing has the following specifications: 135 | 136 | * **OS:** Ubuntu 14.04.01 LTS (64 bit) 137 | * **CPU:** Intel Core i5-2500K @ 4.0 Ghz 138 | * **RAM:** 8GB DDR3-1600 139 | * **GPU:** AMD R9 290 4GB (Sapphire Tri-X) 140 | 141 | Performance of continuous read, write and write+sync has been measured for 142 | different block allocation sizes by creating a new 2GiB disk for each new size 143 | and reading/writing a 2GiB file. 144 | 145 | The disk is created using: 146 | 147 | bin/vramfs /tmp/vram 2G 148 | 149 | And the file is written and read using the `dd` command: 150 | 151 | # write 152 | dd if=/dev/zero of=/tmp/vram/test bs=128K count=16000 153 | 154 | # write+sync 155 | dd if=/dev/zero of=/tmp/vram/test bs=128K count=16000 conv=fdatasync 156 | 157 | # read 158 | dd if=/tmp/vram/test of=/dev/null bs=128K count=16000 159 | 160 | These commands were repeated 5 times for each block size and then averaged to 161 | produce the results shown in the graph. No block sizes lower than 32KiB could 162 | be tested because the driver would fail to allocate that many OpenCL buffers. 163 | This may be solved in the future by using subbuffers. 164 | 165 | ![Performance for different block sizes](http://i.imgur.com/93UNs1u.png) 166 | 167 | Although 128KiB blocks offers the highest performance, 64KiB may be preferable 168 | because of the lower space overhead. 169 | 170 | Future ideas 171 | ------------ 172 | 173 | - Implement RAID-0 for SLI/Crossfire setups 174 | 175 | License 176 | ------- 177 | 178 | The MIT License (MIT) 179 | 180 | Copyright (c) 2014 Alexander Overvoorde 181 | 182 | Permission is hereby granted, free of charge, to any person obtaining a copy 183 | of this software and associated documentation files (the "Software"), to 184 | deal in the Software without restriction, including without limitation the 185 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 186 | sell copies of the Software, and to permit persons to whom the Software is 187 | furnished to do so, subject to the following conditions: 188 | 189 | The above copyright notice and this permission notice shall be included in 190 | all copies or substantial portions of the Software. 191 | 192 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 193 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 194 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 195 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 196 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 197 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 198 | IN THE SOFTWARE. 199 | -------------------------------------------------------------------------------- /include/CL/debugcl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VRAM_DEBUGCL_HPP 2 | #define VRAM_DEBUGCL_HPP 3 | 4 | /* 5 | * Minimal OpenCL implementation for better debugging with valgrind 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define CL_CALLBACK 13 | 14 | const int CL_MEM_READ_WRITE = (1 << 0); 15 | const int CL_MEM_READ_ONLY = (1 << 2); 16 | const int CL_MEM_COPY_HOST_PTR = (1 << 5); 17 | const int CL_SUCCESS = 0; 18 | const int CL_DEVICE_TYPE_GPU = 0; 19 | const int CL_COMPLETE = 0; 20 | 21 | const int CL_DEVICE_NAME = 0x102B; 22 | 23 | typedef int cl_event; 24 | typedef int cl_int; 25 | typedef unsigned int cl_uint; 26 | 27 | typedef CL_CALLBACK void (*callback_fn)(cl_event, cl_int, void*); 28 | 29 | namespace cl { 30 | class Device { 31 | public: 32 | template 33 | const char* getInfo() { 34 | return "DEBUG DEVICE"; 35 | } 36 | }; 37 | 38 | class Platform { 39 | public: 40 | static void getDevices(int type, std::vector* devices) { 41 | devices->push_back(Device()); 42 | } 43 | 44 | static void get(std::vector* platforms) { 45 | platforms->push_back(Platform()); 46 | } 47 | 48 | void* operator()() { 49 | return nullptr; 50 | } 51 | }; 52 | 53 | class Context { 54 | public: 55 | Context() {} 56 | Context(std::vector& devices) {} 57 | }; 58 | 59 | class Buffer { 60 | public: 61 | Buffer() { 62 | data = std::make_shared>(); 63 | } 64 | 65 | Buffer(Context& ctx, int flags, int64_t size, void* host_ptr = nullptr, int* err = nullptr) { 66 | data = std::make_shared>(); 67 | data->resize(size); 68 | if (err) *err = CL_SUCCESS; 69 | 70 | if (flags & CL_MEM_COPY_HOST_PTR) { 71 | memcpy(data->data(), host_ptr, size); 72 | } 73 | } 74 | 75 | std::shared_ptr> data; 76 | }; 77 | 78 | class Event { 79 | public: 80 | void setCallback(int flag, callback_fn cb, void* userdata) { 81 | cb(0, 0, userdata); 82 | } 83 | 84 | void wait() {} 85 | }; 86 | 87 | class CommandQueue { 88 | public: 89 | CommandQueue() {} 90 | CommandQueue(Context& ctx, Device& device) {} 91 | 92 | int enqueueFillBuffer(const Buffer& buf, int pattern, int off, int size, std::vector* events, cl::Event* event) { 93 | memset(&buf.data->operator[](off), 0, size); 94 | return CL_SUCCESS; 95 | } 96 | 97 | int enqueueCopyBuffer(const Buffer& src, Buffer& dst, int offSrc, int offDst, int size, std::vector* events, cl::Event* event) { 98 | memcpy(&dst.data->operator[](offDst), &src.data->operator[](offSrc), size); 99 | return CL_SUCCESS; 100 | } 101 | 102 | int enqueueReadBuffer(const Buffer& buf, bool block, int off, int size, void* out, std::vector* events, cl::Event* event) { 103 | memcpy(out, &buf.data->operator[](off), size); 104 | return CL_SUCCESS; 105 | } 106 | 107 | int enqueueWriteBuffer(const Buffer& buf, bool block, int off, int size, const void* in, std::vector* events, cl::Event* event) { 108 | memcpy(&buf.data->operator[](off), in, size); 109 | return CL_SUCCESS; 110 | } 111 | 112 | int finish() { 113 | return CL_SUCCESS; 114 | } 115 | }; 116 | 117 | namespace detail { 118 | inline cl_uint getPlatformVersion(void* platform) { 119 | return 0; 120 | } 121 | } 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /include/entry.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VRAM_ENTRY_HPP 2 | #define VRAM_ENTRY_HPP 3 | 4 | /* 5 | * Entry types 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "memory.hpp" 15 | #include "util.hpp" 16 | 17 | using std::string; 18 | using std::shared_ptr; 19 | 20 | namespace vram { 21 | namespace entry { 22 | // Types of references to entries 23 | class entry_t; 24 | class file_t; 25 | class dir_t; 26 | class symlink_t; 27 | 28 | typedef shared_ptr entry_ref; 29 | typedef shared_ptr file_ref; 30 | typedef shared_ptr dir_ref; 31 | typedef shared_ptr symlink_ref; 32 | 33 | typedef entry_t* entry_ptr; 34 | typedef file_t* file_ptr; 35 | typedef dir_t* dir_ptr; 36 | typedef symlink_t* symlink_ptr; 37 | 38 | // Type of entry, can be combined for filtering in find() 39 | namespace type { 40 | enum type_t { 41 | none = 0, 42 | file = 1, 43 | dir = 2, 44 | symlink = 4 45 | }; 46 | 47 | const int all = file | dir | symlink; 48 | } 49 | 50 | // Total entry count 51 | int count(); 52 | 53 | // Full description of entry 54 | class entry_t : public std::enable_shared_from_this { 55 | public: 56 | entry_t(const entry_t& other) = delete; 57 | 58 | dir_ptr parent() const; 59 | 60 | const string& name() const; 61 | 62 | virtual ~entry_t(); 63 | 64 | virtual type::type_t type() const = 0; 65 | 66 | virtual size_t size() const = 0; 67 | 68 | // Change file attributes (automatically updates change time) 69 | timespec atime() const; 70 | timespec mtime() const; 71 | timespec ctime() const; 72 | 73 | mode_t mode() const; 74 | uid_t user() const; 75 | gid_t group() const; 76 | 77 | void atime(timespec t); 78 | void mtime(timespec t); 79 | void ctime(timespec t); 80 | 81 | void mode(mode_t mode); 82 | void user(uid_t user); 83 | void group(gid_t group); 84 | 85 | // Remove link with parent directory 86 | void unlink(); 87 | 88 | // Move entry 89 | void move(dir_ptr new_parent, const string& new_name); 90 | 91 | protected: 92 | entry_t(); 93 | 94 | // Associate entry with a parent directory after construction 95 | void link(dir_ptr parent, const string& name); 96 | 97 | private: 98 | // Non-owning pointer, parent is guaranteed to exist if entry exists 99 | dir_ptr _parent = nullptr; 100 | 101 | string _name; 102 | 103 | mode_t _mode = 0; 104 | uid_t _user = 0; 105 | gid_t _group = 0; 106 | 107 | timespec _atime; 108 | timespec _mtime; 109 | timespec _ctime; 110 | }; 111 | 112 | // File entry 113 | class file_t : public entry_t { 114 | public: 115 | // Constructor that takes care of memory management 116 | static file_ref make(dir_ptr parent, const string& name); 117 | 118 | type::type_t type() const; 119 | 120 | size_t size() const; 121 | 122 | // Blocks beyond the new file size are immediately deallocated 123 | void size(size_t new_size); 124 | 125 | // Read data from file, returns total bytes read <= size 126 | // 127 | // The specified mutex is unlocked while blocking to read, because 128 | // that's a non-critical section. 129 | int read(off_t off, size_t size, char* data, std::mutex& wait_mutex); 130 | 131 | // Write data to file, returns -error or total bytes written 132 | int write(off_t off, size_t size, const char* data, bool async = true); 133 | 134 | // Sync writes to file 135 | void sync(); 136 | 137 | private: 138 | // Data blocks if this entry is a file 139 | std::map file_blocks; 140 | 141 | // Last block touched by write() 142 | memory::block_ref last_written_block; 143 | 144 | // File size 145 | size_t _size = 0; 146 | 147 | file_t(); 148 | 149 | // Get the OpenCL buffer of the block if it exists or a nullptr 150 | memory::block_ref get_block(off_t off) const; 151 | 152 | // Allocate new block, buf is only set on success (returning true) 153 | memory::block_ref alloc_block(off_t off); 154 | 155 | // Delete all blocks with a starting offset >= *off* 156 | void free_blocks(off_t off = 0); 157 | }; 158 | 159 | // Directory entry 160 | class dir_t : public entry_t { 161 | friend class entry_t; 162 | 163 | public: 164 | // Constructor that takes care of memory management 165 | static dir_ref make(dir_ptr parent, const string& name); 166 | 167 | type::type_t type() const; 168 | 169 | // Always returns size of 4096 170 | size_t size() const; 171 | 172 | // Not const, because it changes access time 173 | const std::unordered_map children(); 174 | 175 | // Find entry by path relative to this entry 176 | int find(const string& path, entry_ref& entry, int filter = type::all) const; 177 | 178 | protected: 179 | std::unordered_map _children; 180 | 181 | private: 182 | dir_t(); 183 | }; 184 | 185 | // Symlink entry 186 | class symlink_t : public entry_t { 187 | public: 188 | // Target of symlink 189 | const string target; 190 | 191 | // Constructor that takes care of memory management 192 | static symlink_ref make(dir_ptr parent, const string& name, const string& target); 193 | 194 | type::type_t type() const; 195 | 196 | size_t size() const; 197 | 198 | private: 199 | symlink_t(const string& target); 200 | }; 201 | } 202 | } 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /include/memory.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VRAM_MEMORY_HPP 2 | #define VRAM_MEMORY_HPP 3 | #define CL_HPP_TARGET_OPENCL_VERSION 120 4 | #define CL_HPP_MINIMUM_OPENCL_VERSION 110 5 | /* 6 | * VRAM block allocation 7 | */ 8 | 9 | 10 | #ifdef DEBUG 11 | // Use minimal OpenCL implementation for better debugging with valgrind 12 | #include "CL/debugcl.hpp" 13 | #else 14 | #include 15 | #endif 16 | 17 | #include 18 | 19 | namespace vram { 20 | namespace memory { 21 | class block; 22 | typedef std::shared_ptr block_ref; 23 | 24 | // Check if current machine supports VRAM allocation 25 | bool is_available(); 26 | 27 | // Set the device to use 28 | void set_device(size_t num); 29 | 30 | // Returns a list of device names 31 | std::vector list_devices(); 32 | 33 | // Total blocks and blocks currently free 34 | int pool_size(); 35 | int pool_available(); 36 | 37 | // Allocate pool of memory blocks, returns actual amount allocated (in bytes) 38 | size_t increase_pool(size_t size); 39 | 40 | // Get a new block of memory from the pool, returns nullptr if pool is empty 41 | block_ref allocate(); 42 | 43 | /* 44 | * Block of allocated VRAM 45 | */ 46 | 47 | class block : public std::enable_shared_from_this { 48 | friend block_ref allocate(); 49 | 50 | public: 51 | // Best performance/size balance 52 | static const size_t size = 128 * 1024; 53 | 54 | block(const block& other) = delete; 55 | 56 | ~block(); 57 | 58 | void read(off_t offset, size_t size, void* data) const; 59 | 60 | // Data may be freed afterwards, even if called with async = true 61 | void write(off_t offset, size_t size, const void* data, bool async = false); 62 | 63 | // Wait for all writes to this block to complete 64 | void sync(); 65 | 66 | private: 67 | cl::Buffer buffer; 68 | cl::Event last_write; 69 | 70 | // True until first write (until then it contains leftover data from last use) 71 | bool dirty = true; 72 | 73 | block(); 74 | }; 75 | } 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /include/util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VRAM_UTIL_HPP 2 | #define VRAM_UTIL_HPP 3 | 4 | /* 5 | * Utility functions 6 | */ 7 | 8 | #define FUSE_USE_VERSION 30 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using std::string; 15 | 16 | namespace vram { 17 | namespace util { 18 | // Error function that can be combined with a return statement to return *ret* 19 | template 20 | T fatal_error(const string& error, T ret) { 21 | std::cerr << "error: " << error << std::endl; 22 | fuse_exit(fuse_get_context()->fuse); 23 | return ret; 24 | } 25 | 26 | // Get current time with nanosecond precision 27 | timespec time(); 28 | 29 | // Split path/to/file.txt into "path/to" and "file.txt" 30 | void split_file_path(const string& path, string& dir, string& file); 31 | } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /include/vramfs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VRAM_TYPES_HPP 2 | #define VRAM_TYPES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util.hpp" 9 | #include "memory.hpp" 10 | #include "entry.hpp" 11 | 12 | using std::lock_guard; 13 | using std::mutex; 14 | using std::string; 15 | using std::dynamic_pointer_cast; 16 | 17 | namespace vram { 18 | // Data persistent in an open() and release() session 19 | struct file_session { 20 | entry::file_ref file; 21 | 22 | file_session(entry::file_ref file) : file(file) {} 23 | }; 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /mount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p /tmp/vram 3 | bin/vramfs /tmp/vram 256MB -f 4 | -------------------------------------------------------------------------------- /src/dir.cpp: -------------------------------------------------------------------------------- 1 | #include "entry.hpp" 2 | #include "util.hpp" 3 | 4 | #include 5 | 6 | namespace vram { 7 | namespace entry { 8 | dir_ref dir_t::make(dir_ptr parent, const string& name) { 9 | auto dir = dir_ref(new dir_t()); 10 | dir->link(parent, name); 11 | return dir; 12 | } 13 | 14 | dir_t::dir_t() { 15 | mode(0755); 16 | } 17 | 18 | type::type_t dir_t::type() const { 19 | return type::dir; 20 | } 21 | 22 | size_t dir_t::size() const { 23 | return 4096; 24 | } 25 | 26 | const std::unordered_map dir_t::children() { 27 | atime(util::time()); 28 | 29 | return _children; 30 | } 31 | 32 | int dir_t::find(const string& path, entry_ref& entry, int filter) const { 33 | // If filter is empty, no entry will ever match 34 | if ((filter & type::all) == 0) return -ENOENT; 35 | 36 | // Traverse file system by hierarchically, starting from this entry 37 | entry = std::const_pointer_cast(shared_from_this()); 38 | 39 | std::stringstream stream(path.substr(1)); 40 | string part; 41 | 42 | // If the path is empty, assume the root directory 43 | while (getline(stream, part, '/')) { 44 | // If current entry isn't a directory, abort 45 | if (entry->type() != type::dir) return -ENOTDIR; 46 | 47 | // Navigate to next entry 48 | auto dir = std::dynamic_pointer_cast(entry); 49 | auto it = dir->_children.find(part); 50 | 51 | if (it != dir->_children.end()) { 52 | entry = it->second; 53 | } else { 54 | return -ENOENT; 55 | } 56 | } 57 | 58 | // If an undesired type of entry was found, return an appropriate error 59 | if (!(entry->type() & filter)) { 60 | if (entry->type() == type::file) { 61 | if (filter & type::symlink) return -ENOENT; 62 | if (filter & type::dir) return -EISDIR; 63 | } else if (entry->type() == entry::type::dir) { 64 | if (filter & type::file) return -ENOTDIR; 65 | if (filter & type::symlink) return -EPERM; 66 | } else { 67 | return -EPERM; 68 | } 69 | } 70 | 71 | return 0; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/entry.cpp: -------------------------------------------------------------------------------- 1 | #include "entry.hpp" 2 | #include "util.hpp" 3 | 4 | namespace vram { 5 | namespace entry { 6 | int entry_count = 0; 7 | 8 | int count() { 9 | return entry_count; 10 | } 11 | 12 | entry_t::entry_t() { 13 | auto t = util::time(); 14 | 15 | _atime = t; 16 | _mtime = t; 17 | _ctime = t; 18 | 19 | entry_count++; 20 | } 21 | 22 | entry_t::~entry_t() { 23 | entry_count--; 24 | } 25 | 26 | void entry_t::link(dir_ptr parent, const string& name) { 27 | _parent = parent; 28 | _name = name; 29 | 30 | if (parent) { 31 | parent->_children[name] = shared_from_this(); 32 | parent->mtime(util::time()); 33 | } 34 | } 35 | 36 | dir_ptr entry_t::parent() const { 37 | return _parent; 38 | } 39 | 40 | const string& entry_t::name() const { 41 | return _name; 42 | } 43 | 44 | timespec entry_t::atime() const { 45 | return _atime; 46 | } 47 | 48 | timespec entry_t::mtime() const { 49 | return _mtime; 50 | } 51 | 52 | timespec entry_t::ctime() const { 53 | return _ctime; 54 | } 55 | 56 | mode_t entry_t::mode() const { 57 | return _mode; 58 | } 59 | 60 | uid_t entry_t::user() const { 61 | return _user; 62 | } 63 | 64 | gid_t entry_t::group() const { 65 | return _group; 66 | } 67 | 68 | void entry_t::atime(timespec t) { 69 | _atime = t; 70 | ctime(util::time()); 71 | } 72 | 73 | void entry_t::mtime(timespec t) { 74 | _mtime = t; 75 | ctime(util::time()); 76 | } 77 | 78 | void entry_t::ctime(timespec t) { 79 | _ctime = t; 80 | } 81 | 82 | void entry_t::mode(mode_t mode) { 83 | _mode = mode; 84 | ctime(util::time()); 85 | } 86 | 87 | void entry_t::user(uid_t user) { 88 | _user = user; 89 | ctime(util::time()); 90 | } 91 | 92 | void entry_t::group(gid_t group) { 93 | _group = group; 94 | ctime(util::time()); 95 | } 96 | 97 | void entry_t::unlink() { 98 | if (_parent) { 99 | _parent->_children.erase(_name); 100 | _parent->mtime(util::time()); 101 | } 102 | } 103 | 104 | void entry_t::move(dir_ptr new_parent, const string& new_name) { 105 | if (_parent) { 106 | _parent->_children.erase(_name); 107 | _parent->mtime(util::time()); 108 | } 109 | 110 | _parent = new_parent; 111 | _name = new_name; 112 | 113 | ctime(util::time()); 114 | 115 | new_parent->_children[new_name] = shared_from_this(); 116 | new_parent->mtime(util::time()); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/file.cpp: -------------------------------------------------------------------------------- 1 | #include "entry.hpp" 2 | #include "util.hpp" 3 | 4 | namespace vram { 5 | namespace entry { 6 | file_ref file_t::make(dir_ptr parent, const string& name) { 7 | auto file = file_ref(new file_t()); 8 | file->link(parent, name); 9 | return file; 10 | } 11 | 12 | file_t::file_t() { 13 | mode(0644); 14 | } 15 | 16 | type::type_t file_t::type() const { 17 | return type::file; 18 | } 19 | 20 | size_t file_t::size() const { 21 | return _size; 22 | } 23 | 24 | void file_t::size(size_t new_size) { 25 | if (new_size < _size) { 26 | free_blocks(new_size); 27 | } 28 | 29 | _size = new_size; 30 | 31 | mtime(util::time()); 32 | } 33 | 34 | int file_t::read(off_t off, size_t size, char* data, std::mutex& wait_mutex) { 35 | if ((size_t) off >= _size) return 0; 36 | size = std::min(_size - off, size); 37 | 38 | // Walk over blocks in read region 39 | off_t end_pos = off + size; 40 | size_t total_read = size; 41 | 42 | while (off < end_pos) { 43 | // Find block corresponding to current offset 44 | off_t block_start = (off / memory::block::size) * memory::block::size; 45 | off_t block_off = off - block_start; 46 | size_t read_size = std::min(memory::block::size - block_off, size); 47 | 48 | auto block = get_block(block_start); 49 | 50 | // Allow multiple threads to block for reading simultaneously 51 | wait_mutex.unlock(); 52 | if (block) { 53 | block->read(block_off, read_size, data); 54 | } else { 55 | // Non-written part of file 56 | memset(data, 0, read_size); 57 | } 58 | wait_mutex.lock(); 59 | 60 | data += read_size; 61 | off += read_size; 62 | size -= read_size; 63 | } 64 | 65 | atime(util::time()); 66 | 67 | return total_read; 68 | } 69 | 70 | int file_t::write(off_t off, size_t size, const char* data, bool async) { 71 | // Walk over blocks in write region 72 | off_t end_pos = off + size; 73 | size_t total_write = size; 74 | 75 | while (off < end_pos) { 76 | // Find block corresponding to current offset 77 | off_t block_start = (off / memory::block::size) * memory::block::size; 78 | off_t block_off = off - block_start; 79 | size_t write_size = std::min(memory::block::size - block_off, size); 80 | 81 | auto block = get_block(block_start); 82 | 83 | if (!block) { 84 | block = alloc_block(block_start); 85 | 86 | // Failed to allocate buffer, likely out of VRAM 87 | if (!block) break; 88 | } 89 | 90 | block->write(block_off, write_size, data, async); 91 | 92 | last_written_block = block; 93 | 94 | data += write_size; 95 | off += write_size; 96 | size -= write_size; 97 | } 98 | 99 | if (_size < (size_t) off) { 100 | _size = off; 101 | } 102 | mtime(util::time()); 103 | 104 | if (off < end_pos) { 105 | return -ENOSPC; 106 | } else { 107 | return total_write; 108 | } 109 | } 110 | 111 | void file_t::sync() { 112 | // Waits for all asynchronous writes to finish, because they must 113 | // complete before the last write does (OpenCL guarantee) 114 | last_written_block->sync(); 115 | } 116 | 117 | memory::block_ref file_t::get_block(off_t off) const { 118 | auto it = file_blocks.find(off); 119 | 120 | if (it != file_blocks.end()) { 121 | return it->second; 122 | } else { 123 | return nullptr; 124 | } 125 | } 126 | 127 | memory::block_ref file_t::alloc_block(off_t off) { 128 | auto block = memory::allocate(); 129 | 130 | if (block) { 131 | file_blocks[off] = block; 132 | } 133 | 134 | return block; 135 | } 136 | 137 | void file_t::free_blocks(off_t off) { 138 | // Determine first block just beyond the range 139 | off_t start_off = (off / memory::block::size) * memory::block::size; 140 | if (off % memory::block::size != 0) start_off += memory::block::size; 141 | 142 | for (auto it = file_blocks.find(start_off); it != file_blocks.end();) { 143 | it = file_blocks.erase(it); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/memory.cpp: -------------------------------------------------------------------------------- 1 | #include "memory.hpp" 2 | 3 | namespace vram { 4 | namespace memory { 5 | // Connection with OpenCL 6 | bool ready = false; 7 | bool has_fillbuffer = false; // supports the FillBuffer API (platform is version 1.2 or higher) 8 | cl::Context context; 9 | cl::Device device; 10 | cl::CommandQueue queue; 11 | 12 | cl::Buffer zero_buffer; // used to clear buffers on pre-1.2 platforms 13 | 14 | std::vector pool; 15 | int total_blocks = 0; 16 | 17 | size_t device_num; 18 | 19 | // Fill buffer with zeros 20 | static int clear_buffer(cl::Buffer& buf) { 21 | if (has_fillbuffer) 22 | return queue.enqueueFillBuffer(buf, 0, 0, block::size, nullptr, nullptr); 23 | else 24 | return queue.enqueueCopyBuffer(zero_buffer, buf, 0, 0, block::size, nullptr, nullptr); 25 | } 26 | 27 | // Find platform with OpenCL capable GPU 28 | static bool init_opencl() { 29 | if (ready) return true; 30 | 31 | std::vector platforms; 32 | cl::Platform::get(&platforms); 33 | if (platforms.size() == 0) return false; 34 | 35 | auto index = device_num; 36 | for (auto& platform : platforms) { 37 | std::vector gpu_devices; 38 | platform.getDevices(CL_DEVICE_TYPE_GPU, &gpu_devices); 39 | if (index >= gpu_devices.size()) 40 | { 41 | index -= gpu_devices.size(); 42 | continue; 43 | } 44 | 45 | device = gpu_devices[index]; 46 | context = cl::Context(gpu_devices); 47 | queue = cl::CommandQueue(context, device); 48 | 49 | cl_uint version = cl::detail::getPlatformVersion(platform()); 50 | 51 | if (version >= (1 << 16 | 2)) 52 | has_fillbuffer = true; 53 | 54 | if (!has_fillbuffer) { 55 | char zero_data[block::size] = {}; 56 | int r; 57 | zero_buffer = cl::Buffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, block::size, zero_data, &r); 58 | if (r != CL_SUCCESS) return false; 59 | } 60 | 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | // Called for asynchronous writes to clean up the data copy 68 | static CL_CALLBACK void async_write_dealloc(cl_event, cl_int, void* data) { 69 | delete [] reinterpret_cast(data); 70 | } 71 | 72 | bool is_available() { 73 | return (ready = init_opencl()); 74 | } 75 | 76 | void set_device(size_t device) { 77 | device_num = device; 78 | } 79 | 80 | std::vector list_devices() { 81 | std::vector device_names; 82 | 83 | std::vector platforms; 84 | cl::Platform::get(&platforms); 85 | 86 | for (auto& platform : platforms) { 87 | std::vector gpu_devices; 88 | platform.getDevices(CL_DEVICE_TYPE_GPU, &gpu_devices); 89 | 90 | for (auto& device : gpu_devices) { 91 | device_names.push_back(device.getInfo()); 92 | } 93 | } 94 | 95 | return device_names; 96 | } 97 | 98 | int pool_size() { 99 | return total_blocks; 100 | } 101 | 102 | int pool_available() { 103 | return pool.size(); 104 | } 105 | 106 | size_t increase_pool(size_t size) { 107 | int block_count = 1 + (size - 1) / block::size; 108 | int r; 109 | 110 | for (int i = 0; i < block_count; i++) { 111 | cl::Buffer buf(context, CL_MEM_READ_WRITE, block::size, nullptr, &r); 112 | 113 | if (r == CL_SUCCESS && clear_buffer(buf) == CL_SUCCESS) { 114 | pool.push_back(buf); 115 | total_blocks++; 116 | } else { 117 | return i * block::size; 118 | } 119 | } 120 | 121 | return block_count * block::size; 122 | } 123 | 124 | block_ref allocate() { 125 | if (pool.size() != 0) { 126 | return block_ref(new block()); 127 | } else { 128 | return nullptr; 129 | } 130 | } 131 | 132 | block::block() { 133 | buffer = pool.back(); 134 | pool.pop_back(); 135 | } 136 | 137 | block::~block() { 138 | pool.push_back(buffer); 139 | } 140 | 141 | void block::read(off_t offset, size_t size, void* data) const { 142 | if (dirty) { 143 | memset(data, 0, size); 144 | } else { 145 | // Queue is configured for in-order execution, so writes before this 146 | // are guaranteed to be completed first 147 | queue.enqueueReadBuffer(buffer, true, offset, size, data, nullptr, nullptr); 148 | } 149 | } 150 | 151 | void block::write(off_t offset, size_t size, const void* data, bool async) { 152 | // If this block has not been written to yet, and this call doesn't 153 | // overwrite the entire block, clear with zeros first 154 | if (dirty && size != block::size) { 155 | clear_buffer(buffer); 156 | } 157 | 158 | if (async) { 159 | char* data_copy = new char[size]; 160 | memcpy(data_copy, data, size); 161 | data = data_copy; 162 | } 163 | 164 | cl::Event event; 165 | queue.enqueueWriteBuffer(buffer, !async, offset, size, data, nullptr, &event); 166 | 167 | if (async) { 168 | event.setCallback(CL_COMPLETE, async_write_dealloc, const_cast(data)); 169 | } 170 | 171 | last_write = event; 172 | dirty = false; 173 | } 174 | 175 | void block::sync() { 176 | last_write.wait(); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/symlink.cpp: -------------------------------------------------------------------------------- 1 | #include "entry.hpp" 2 | #include "util.hpp" 3 | 4 | namespace vram { 5 | namespace entry { 6 | symlink_ref symlink_t::make(dir_ptr parent, const string& name, const string& target) { 7 | auto symlink = symlink_ref(new symlink_t(target)); 8 | symlink->link(parent, name); 9 | return symlink; 10 | } 11 | 12 | symlink_t::symlink_t(const string& target) : target(target) {} 13 | 14 | type::type_t symlink_t::type() const { 15 | return type::symlink; 16 | } 17 | 18 | size_t symlink_t::size() const { 19 | return target.size(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | 3 | namespace vram { 4 | namespace util { 5 | timespec time() { 6 | timespec tv; 7 | clock_gettime(CLOCK_REALTIME_COARSE, &tv); 8 | return tv; 9 | } 10 | 11 | void split_file_path(const string& path, string& dir, string& file) { 12 | size_t p = path.rfind("/"); 13 | 14 | if (p == string::npos) { 15 | dir = ""; 16 | file = path; 17 | } else { 18 | dir = path.substr(0, p); 19 | file = path.substr(p + 1); 20 | } 21 | 22 | if (dir.size() == 0) dir = "/"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/vramfs.cpp: -------------------------------------------------------------------------------- 1 | // Third-party libraries 2 | #define FUSE_USE_VERSION 30 3 | #include 4 | #include 5 | 6 | // Standard library 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // Internal dependencies 15 | #include "vramfs.hpp" 16 | 17 | using namespace vram; 18 | 19 | /* 20 | * Globals 21 | */ 22 | 23 | // Lock to prevent multiple threads from manipulating the file system index and 24 | // OpenCL buffers simultaneously. The tiny overhead is worth not having to deal 25 | // with the uncountable amount of race conditions that would otherwise occur. 26 | static std::mutex fsmutex; 27 | 28 | // File system root that links to the rest 29 | static entry::dir_ref root_entry; 30 | 31 | /* 32 | * Initialisation 33 | */ 34 | 35 | static void* vram_init(fuse_conn_info* conn, fuse_config*) { 36 | root_entry = entry::dir_t::make(nullptr, ""); 37 | root_entry->user(geteuid()); 38 | root_entry->group(getegid()); 39 | 40 | std::cout << "mounted." << std::endl; 41 | 42 | return nullptr; 43 | } 44 | 45 | /* 46 | * File system info 47 | */ 48 | 49 | static int vram_statfs(const char*, struct statvfs* vfs) { 50 | vfs->f_bsize = memory::block::size; 51 | vfs->f_blocks = memory::pool_size(); 52 | vfs->f_bfree = memory::pool_available(); 53 | vfs->f_bavail = memory::pool_available(); 54 | vfs->f_files = entry::count(); 55 | vfs->f_ffree = std::numeric_limits::max(); 56 | vfs->f_namemax = std::numeric_limits::max(); 57 | 58 | return 0; 59 | } 60 | 61 | /* 62 | * Entry attributes 63 | */ 64 | 65 | static int vram_getattr(const char* path, struct stat* stbuf, fuse_file_info*) { 66 | lock_guard local_lock(fsmutex); 67 | 68 | // Look up entry 69 | entry::entry_ref entry; 70 | int err = root_entry->find(path, entry); 71 | if (err != 0) return err; 72 | 73 | memset(stbuf, 0, sizeof(struct stat)); 74 | 75 | if (entry->type() == entry::type::dir) { 76 | stbuf->st_mode = S_IFDIR | entry->mode(); 77 | stbuf->st_nlink = 2; 78 | } else if (entry->type() == entry::type::file) { 79 | stbuf->st_mode = S_IFREG | entry->mode(); 80 | stbuf->st_nlink = 1; 81 | stbuf->st_blksize = memory::block::size; 82 | 83 | if (entry->size() > 0) { 84 | stbuf->st_blocks = 1 + (entry->size() - 1) / 512; // man 2 stat 85 | } 86 | } else { 87 | stbuf->st_mode = S_IFLNK | 0777; 88 | stbuf->st_nlink = 1; 89 | } 90 | 91 | stbuf->st_uid = entry->user(); 92 | stbuf->st_gid = entry->group(); 93 | stbuf->st_size = entry->size(); 94 | stbuf->st_atim = entry->atime(); 95 | stbuf->st_mtim = entry->mtime(); 96 | stbuf->st_ctim = entry->ctime(); 97 | 98 | return 0; 99 | } 100 | 101 | /* 102 | * Get target of link 103 | */ 104 | 105 | static int vram_readlink(const char* path, char* buf, size_t size) { 106 | lock_guard local_lock(fsmutex); 107 | 108 | entry::entry_ref entry; 109 | int err = root_entry->find(path, entry, entry::type::symlink); 110 | if (err != 0) return err; 111 | 112 | auto symlink = dynamic_pointer_cast(entry); 113 | strncpy(buf, symlink->target.c_str(), size); 114 | 115 | return 0; 116 | } 117 | 118 | /* 119 | * Set the mode bits of an entry 120 | */ 121 | 122 | static int vram_chmod(const char* path, mode_t mode, fuse_file_info*) { 123 | lock_guard local_lock(fsmutex); 124 | 125 | entry::entry_ref entry; 126 | int err = root_entry->find(path, entry, entry::type::file | entry::type::dir); 127 | if (err != 0) return err; 128 | 129 | entry->mode(mode); 130 | 131 | return 0; 132 | } 133 | 134 | /* 135 | * Change the owner/group of an entry 136 | */ 137 | 138 | static int vram_chown(const char* path, uid_t user, gid_t group, fuse_file_info*) { 139 | lock_guard lock_lock(fsmutex); 140 | 141 | entry::entry_ref entry; 142 | int err = root_entry->find(path, entry, entry::type::file | entry::type::dir); 143 | if (err != 0) return err; 144 | 145 | entry->user(user); 146 | entry->group(group); 147 | 148 | return 0; 149 | } 150 | 151 | /* 152 | * Set the last access and last modified times of an entry 153 | */ 154 | 155 | static int vram_utimens(const char* path, const timespec tv[2], fuse_file_info*) { 156 | lock_guard local_lock(fsmutex); 157 | 158 | entry::entry_ref entry; 159 | int err = root_entry->find(path, entry, entry::type::file | entry::type::dir); 160 | if (err != 0) return err; 161 | 162 | entry->atime(tv[0]); 163 | entry->mtime(tv[1]); 164 | 165 | return 0; 166 | } 167 | 168 | /* 169 | * Directory listing 170 | */ 171 | 172 | static int vram_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t, fuse_file_info*, fuse_readdir_flags) { 173 | lock_guard local_lock(fsmutex); 174 | 175 | // Look up directory 176 | entry::entry_ref entry; 177 | int err = root_entry->find(path, entry, entry::type::dir); 178 | if (err != 0) return err; 179 | auto dir = dynamic_pointer_cast(entry); 180 | 181 | // Required default entries 182 | filler(buf, ".", nullptr, 0, FUSE_FILL_DIR_PLUS); 183 | filler(buf, "..", nullptr, 0, FUSE_FILL_DIR_PLUS); 184 | 185 | for (auto& pair : dir->children()) { 186 | filler(buf, pair.second->name().c_str(), nullptr, 0, FUSE_FILL_DIR_PLUS); 187 | } 188 | 189 | return 0; 190 | } 191 | 192 | /* 193 | * Create file 194 | */ 195 | 196 | static int vram_create(const char* path, mode_t, struct fuse_file_info* fi) { 197 | lock_guard local_lock(fsmutex); 198 | 199 | // Truncate any existing file entry or fail if it's another type 200 | entry::entry_ref entry; 201 | int err = root_entry->find(path, entry, entry::type::file); 202 | if (err == -EISDIR) return err; 203 | else if (err == 0) entry->unlink(); 204 | 205 | string dir, name; 206 | util::split_file_path(path, dir, name); 207 | 208 | // Check if parent directory exists 209 | err = root_entry->find(dir, entry, entry::type::dir); 210 | if (err != 0) return err; 211 | auto parent = dynamic_pointer_cast(entry); 212 | 213 | // Create new entry with appropriate owner/group 214 | auto file = entry::file_t::make(parent.get(), name); 215 | 216 | auto context = fuse_get_context(); 217 | file->user(context->uid); 218 | file->group(context->gid); 219 | 220 | // Open it by assigning new file handle 221 | fi->fh = reinterpret_cast(new file_session(file)); 222 | 223 | return 0; 224 | } 225 | 226 | /* 227 | * Create directory 228 | */ 229 | 230 | static int vram_mkdir(const char* path, mode_t) { 231 | lock_guard local_lock(fsmutex); 232 | 233 | // Fail if entry with that name already exists 234 | entry::entry_ref entry; 235 | int err = root_entry->find(path, entry); 236 | if (err == 0) return -EEXIST; 237 | 238 | string dir, name; 239 | util::split_file_path(path, dir, name); 240 | 241 | // Check if parent directory exists 242 | err = root_entry->find(dir, entry, entry::type::dir); 243 | if (err != 0) return err; 244 | auto parent = dynamic_pointer_cast(entry); 245 | 246 | // Create new directory with appropriate owner/group 247 | auto new_dir = entry::dir_t::make(parent.get(), name); 248 | 249 | auto context = fuse_get_context(); 250 | new_dir->user(context->uid); 251 | new_dir->group(context->gid); 252 | 253 | return 0; 254 | } 255 | 256 | /* 257 | * Create symlink 258 | */ 259 | 260 | static int vram_symlink(const char* target, const char* path) { 261 | lock_guard local_lock(fsmutex); 262 | 263 | // Fail if an entry with that name already exists 264 | entry::entry_ref entry; 265 | int err = root_entry->find(path, entry); 266 | if (err == 0) return -EEXIST; 267 | 268 | // Split path in parent directory and new symlink name 269 | string dir, name; 270 | util::split_file_path(path, dir, name); 271 | 272 | // Check if parent directory exists 273 | err = root_entry->find(dir, entry, entry::type::dir); 274 | if (err != 0) return err; 275 | auto parent = dynamic_pointer_cast(entry); 276 | 277 | // Create new symlink with appropriate owner/group 278 | auto symlink = entry::symlink_t::make(parent.get(), name, target); 279 | 280 | auto context = fuse_get_context(); 281 | symlink->user(context->uid); 282 | symlink->group(context->gid); 283 | 284 | return 0; 285 | } 286 | 287 | /* 288 | * Delete file 289 | */ 290 | 291 | static int vram_unlink(const char* path) { 292 | lock_guard local_lock(fsmutex); 293 | 294 | entry::entry_ref entry; 295 | int err = root_entry->find(path, entry, entry::type::symlink | entry::type::file); 296 | if (err != 0) return err; 297 | 298 | entry->unlink(); 299 | 300 | return 0; 301 | } 302 | 303 | /* 304 | * Delete directory 305 | */ 306 | 307 | static int vram_rmdir(const char* path) { 308 | lock_guard local_lock(fsmutex); 309 | 310 | // Fail if entry doesn't exist or is not a directory 311 | entry::entry_ref entry; 312 | int err = root_entry->find(path, entry, entry::type::dir); 313 | if (err != 0) return err; 314 | auto dir = dynamic_pointer_cast(entry); 315 | 316 | // Check if directory is empty 317 | if (dir->children().size() != 0) { 318 | return -ENOTEMPTY; 319 | } 320 | 321 | dir->unlink(); 322 | 323 | return 0; 324 | } 325 | 326 | /* 327 | * Rename entry 328 | */ 329 | 330 | static int vram_rename(const char* path, const char* new_path, unsigned int) { 331 | lock_guard local_lock(fsmutex); 332 | 333 | // Look up entry 334 | entry::entry_ref entry; 335 | int err = root_entry->find(path, entry); 336 | if (err != 0) return err; 337 | 338 | // Check if destination directory exists 339 | string dir, new_name; 340 | util::split_file_path(new_path, dir, new_name); 341 | 342 | entry::entry_ref parent_entry; 343 | err = root_entry->find(dir, parent_entry, entry::type::dir); 344 | if (err != 0) return err; 345 | auto parent = dynamic_pointer_cast(parent_entry); 346 | 347 | // If the destination entry already exists, then delete it 348 | entry::entry_ref dest_entry; 349 | err = root_entry->find(new_path, dest_entry); 350 | if (err == 0) dest_entry->unlink(); 351 | 352 | entry->move(parent.get(), new_name); 353 | 354 | return 0; 355 | } 356 | 357 | /* 358 | * Open file 359 | */ 360 | 361 | static int vram_open(const char* path, fuse_file_info* fi) { 362 | lock_guard local_lock(fsmutex); 363 | 364 | entry::entry_ref entry; 365 | int err = root_entry->find(path, entry, entry::type::file); 366 | if (err != 0) return err; 367 | auto file = dynamic_pointer_cast(entry); 368 | 369 | fi->fh = reinterpret_cast(new file_session(file)); 370 | 371 | return 0; 372 | } 373 | 374 | /* 375 | * Read file 376 | */ 377 | 378 | static int vram_read(const char* path, char* buf, size_t size, off_t off, fuse_file_info* fi) { 379 | lock_guard local_lock(fsmutex); 380 | 381 | file_session* session = reinterpret_cast(fi->fh); 382 | return session->file->read(off, size, buf, fsmutex); 383 | } 384 | 385 | /* 386 | * Write file 387 | */ 388 | 389 | static int vram_write(const char* path, const char* buf, size_t size, off_t off, fuse_file_info* fi) { 390 | lock_guard local_lock(fsmutex); 391 | 392 | file_session* session = reinterpret_cast(fi->fh); 393 | return session->file->write(off, size, buf); 394 | } 395 | 396 | /* 397 | * Sync writes to file 398 | */ 399 | 400 | static int vram_fsync(const char* path, int, fuse_file_info* fi) { 401 | lock_guard local_lock(fsmutex); 402 | 403 | file_session* session = reinterpret_cast(fi->fh); 404 | session->file->sync(); 405 | 406 | return 0; 407 | } 408 | 409 | /* 410 | * Close file 411 | */ 412 | 413 | static int vram_release(const char* path, fuse_file_info* fi) { 414 | lock_guard local_lock(fsmutex); 415 | 416 | delete reinterpret_cast(fi->fh); 417 | 418 | return 0; 419 | } 420 | 421 | /* 422 | * Change file size 423 | */ 424 | 425 | static int vram_truncate(const char* path, off_t size, fuse_file_info*) { 426 | lock_guard local_lock(fsmutex); 427 | 428 | entry::entry_ref entry; 429 | int err = root_entry->find(path, entry, entry::type::file); 430 | if (err != 0) return err; 431 | auto file = dynamic_pointer_cast(entry); 432 | 433 | file->size(size); 434 | 435 | return 0; 436 | } 437 | 438 | /* 439 | * FUSE setup 440 | */ 441 | 442 | static struct vram_operations : fuse_operations { 443 | vram_operations() { 444 | init = vram_init; 445 | statfs = vram_statfs; 446 | getattr = vram_getattr; 447 | readlink = vram_readlink; 448 | utimens = vram_utimens; 449 | chmod = vram_chmod; 450 | chown = vram_chown; 451 | readdir = vram_readdir; 452 | create = vram_create; 453 | mkdir = vram_mkdir; 454 | symlink = vram_symlink; 455 | unlink = vram_unlink; 456 | rmdir = vram_rmdir; 457 | rename = vram_rename; 458 | open = vram_open; 459 | read = vram_read; 460 | write = vram_write; 461 | fsync = vram_fsync; 462 | release = vram_release; 463 | truncate = vram_truncate; 464 | } 465 | } operations; 466 | 467 | static int print_help() { 468 | std::cerr << 469 | "usage: vramfs [-d ] [-f]\n\n" 470 | " mountdir - directory to mount file system, must be empty\n" 471 | " size - size of the disk in bytes\n" 472 | " -d - specifies identifier of device to use\n" 473 | " -f - flag that forces mounting, with a smaller size if needed\n\n" 474 | "The size may be followed by one of the following multiplicative suffixes: " 475 | "K=1024, KB=1000, M=1024*1024, MB=1000*1000, G=1024*1024*1024, GB=1000*1000*1000. " 476 | "It's rounded up to the nearest multiple of the block size.\n" 477 | << std::endl; 478 | 479 | auto devices = memory::list_devices(); 480 | 481 | if (!devices.empty()) { 482 | std::cerr << "device list: \n"; 483 | 484 | for (size_t i = 0; i < devices.size(); ++i) { 485 | std::cerr << " " << i << ": " << devices[i] << "\n"; 486 | } 487 | std::cerr << std::endl; 488 | } else { 489 | std::cerr << "No suitable devices found." << std::endl; 490 | } 491 | 492 | 493 | return 1; 494 | } 495 | 496 | static std::regex size_regex("^([0-9]+)([KMG]B?)?$"); 497 | 498 | static size_t parse_size(const string& param) { 499 | std::smatch groups; 500 | std::regex_search(param, groups, size_regex); 501 | 502 | size_t size = std::stoul(groups[1]); 503 | 504 | if (groups[2] == "K") size *= 1024UL; 505 | else if (groups[2] == "KB") size *= 1000UL; 506 | else if (groups[2] == "M") size *= 1024UL * 1024UL; 507 | else if (groups[2] == "MB") size *= 1000UL * 1000UL; 508 | else if (groups[2] == "G") size *= 1024UL * 1024UL * 1024UL; 509 | else if (groups[2] == "GB") size *= 1000UL * 1000UL * 1000UL; 510 | 511 | return size; 512 | } 513 | 514 | int main(int argc, char* argv[]) { 515 | // Check parameter and parse parameters 516 | if (argc < 3 || argc > 6) return print_help(); 517 | if (!std::regex_match(argv[2], size_regex)) return print_help(); 518 | if (argc == 4 && strcmp(argv[3], "-f") != 0) return print_help(); 519 | if (argc == 5 && strcmp(argv[3], "-d") != 0) return print_help(); 520 | if (argc == 6) { 521 | if (strcmp(argv[3], "-d") != 0 && strcmp(argv[5], "-f") != 0) { 522 | return print_help(); 523 | } 524 | } 525 | 526 | size_t disk_size = parse_size(argv[2]); 527 | bool force_allocate = (argc == 4 || argc == 6); 528 | 529 | if (argc == 5 || argc == 6) { 530 | memory::set_device(atoi(argv[4])); 531 | } 532 | 533 | // Lock process pages in memory to prevent them from being swapped 534 | if (mlockall(MCL_CURRENT | MCL_FUTURE)) { 535 | std::cerr << "failed to lock process pages in memory, vramfs may freeze if swapped" << std::endl; 536 | } 537 | 538 | // Check for OpenCL supported GPU and allocate memory 539 | if (!memory::is_available()) { 540 | std::cerr << "no opencl capable gpu found" << std::endl; 541 | return 1; 542 | } else { 543 | std::cout << "allocating vram..." << std::endl; 544 | 545 | size_t actual_size = memory::increase_pool(disk_size); 546 | 547 | if (actual_size < disk_size) { 548 | if (force_allocate) { 549 | std::cerr << "warning: only allocated " << actual_size << " bytes" << std::endl; 550 | } else { 551 | std::cerr << "error: could not allocate more than " << actual_size << " bytes" << std::endl; 552 | std::cerr << "cleaning up..." << std::endl; 553 | return 1; 554 | } 555 | } 556 | } 557 | 558 | // Pass mount point parameter to FUSE 559 | struct fuse_args args = FUSE_ARGS_INIT(2, argv); 560 | 561 | fuse_opt_parse(&args, nullptr, nullptr, nullptr); 562 | 563 | // Properly unmount even on crash 564 | fuse_opt_add_arg(&args, "-oauto_unmount"); 565 | 566 | // Let FUSE and the kernel deal with permissions handling 567 | fuse_opt_add_arg(&args, "-odefault_permissions"); 568 | 569 | // OpenCL driver acts funky if program doesn't keep running in foreground 570 | fuse_opt_add_arg(&args, "-f"); 571 | 572 | return fuse_main(args.argc, args.argv, &operations, nullptr); 573 | } 574 | -------------------------------------------------------------------------------- /umount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | fusermount -u /tmp/vram && echo "unmounted VRAM file system" 3 | --------------------------------------------------------------------------------