| 1 | /* |
| 2 | * This file is part of the KDE project. |
| 3 | * |
| 4 | * SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org> |
| 5 | * SPDX-License-Identifier: LGPL-2.0-only |
| 6 | */ |
| 7 | |
| 8 | #ifndef KSDCMEMORY_P_H |
| 9 | #define KSDCMEMORY_P_H |
| 10 | |
| 11 | #include "kcoreaddons_debug.h" |
| 12 | #include "ksdclock_p.h" |
| 13 | #include "kshareddatacache.h" |
| 14 | |
| 15 | /* |
| 16 | * A very simple class whose only purpose is to be thrown as an exception from |
| 17 | * underlying code to indicate that the shared cache is apparently corrupt. |
| 18 | * This must be caught by top-level library code and used to unlink the cache |
| 19 | * in this circumstance. |
| 20 | * |
| 21 | * @internal |
| 22 | */ |
| 23 | class KSDCCorrupted |
| 24 | { |
| 25 | public: |
| 26 | KSDCCorrupted() |
| 27 | { |
| 28 | qCWarning(KCOREADDONS_DEBUG) << "Error detected in cache!" ; |
| 29 | } |
| 30 | |
| 31 | KSDCCorrupted(const QString message) |
| 32 | { |
| 33 | qCWarning(KCOREADDONS_DEBUG).noquote() << message; |
| 34 | } |
| 35 | |
| 36 | KSDCCorrupted(const char *message) |
| 37 | { |
| 38 | KSDCCorrupted(QLatin1String(message)); |
| 39 | } |
| 40 | }; |
| 41 | |
| 42 | typedef qint32 pageID; |
| 43 | |
| 44 | // ========================================================================= |
| 45 | // Description of the cache: |
| 46 | // |
| 47 | // The shared memory cache is designed to be handled as two separate objects, |
| 48 | // all contained in the same global memory segment. First off, there is the |
| 49 | // basic header data, consisting of the global header followed by the |
| 50 | // accounting data necessary to hold items (described in more detail |
| 51 | // momentarily). Following the accounting data is the start of the "page table" |
| 52 | // (essentially just as you'd see it in an Operating Systems text). |
| 53 | // |
| 54 | // The page table contains shared memory split into fixed-size pages, with a |
| 55 | // configurable page size. In the event that the data is too large to fit into |
| 56 | // a single logical page, it will need to occupy consecutive pages of memory. |
| 57 | // |
| 58 | // The accounting data that was referenced earlier is split into two: |
| 59 | // |
| 60 | // 1. index table, containing a fixed-size list of possible cache entries. |
| 61 | // Each index entry is of type IndexTableEntry (below), and holds the various |
| 62 | // accounting data and a pointer to the first page. |
| 63 | // |
| 64 | // 2. page table, which is used to speed up the process of searching for |
| 65 | // free pages of memory. There is one entry for every page in the page table, |
| 66 | // and it contains the index of the one entry in the index table actually |
| 67 | // holding the page (or <0 if the page is free). |
| 68 | // |
| 69 | // The entire segment looks like so: |
| 70 | // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══? |
| 71 | // ? Header │ Index Table │ Page Table ? Pages │ │ │ │...? |
| 72 | // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══? |
| 73 | // ========================================================================= |
| 74 | |
| 75 | // All elements of this struct must be "plain old data" (POD) types since it |
| 76 | // will be in shared memory. In addition, no pointers! To point to something |
| 77 | // you must use relative offsets since the pointer start addresses will be |
| 78 | // different in each process. |
| 79 | struct IndexTableEntry { |
| 80 | uint fileNameHash; |
| 81 | uint totalItemSize; // in bytes |
| 82 | mutable uint useCount; |
| 83 | time_t addTime; |
| 84 | mutable time_t lastUsedTime; |
| 85 | pageID firstPage; |
| 86 | }; |
| 87 | |
| 88 | // Page table entry |
| 89 | struct PageTableEntry { |
| 90 | // int so we can use values <0 for unassigned pages. |
| 91 | qint32 index; |
| 92 | }; |
| 93 | |
| 94 | // Each individual page contains the cached data. The first page starts off with |
| 95 | // the utf8-encoded key, a null '\0', and then the data follows immediately |
| 96 | // from the next byte, possibly crossing consecutive page boundaries to hold |
| 97 | // all of the data. |
| 98 | // There is, however, no specific struct for a page, it is simply a location in |
| 99 | // memory. |
| 100 | |
| 101 | // This is effectively the layout of the shared memory segment. The variables |
| 102 | // contained within form the header, data contained afterwards is pointed to |
| 103 | // by using special accessor functions. |
| 104 | struct SharedMemory { |
| 105 | /* |
| 106 | * Note to downstream packagers: This version flag is intended to be |
| 107 | * machine-specific. The KDE-provided source code will not set the lower |
| 108 | * two bits to allow for distribution-specific needs, with the exception |
| 109 | * of version 1 which was already defined in KDE Platform 4.5. |
| 110 | * e.g. the next version bump will be from 4 to 8, then 12, etc. |
| 111 | */ |
| 112 | enum { |
| 113 | PIXMAP_CACHE_VERSION = 12, |
| 114 | MINIMUM_CACHE_SIZE = 4096, |
| 115 | }; |
| 116 | |
| 117 | /// The maximum number of probes to make while searching for a bucket in |
| 118 | /// the presence of collisions in the cache index table. |
| 119 | static const uint MAX_PROBE_COUNT = 6; |
| 120 | |
| 121 | // Note to those who follow me. You should not, under any circumstances, ever |
| 122 | // re-arrange the following two fields, even if you change the version number |
| 123 | // for later revisions of this code. |
| 124 | QAtomicInt ready; ///< DO NOT INITIALIZE |
| 125 | quint8 version; |
| 126 | |
| 127 | // See kshareddatacache_p.h |
| 128 | SharedLock shmLock; |
| 129 | |
| 130 | uint cacheSize; |
| 131 | uint cacheAvail; |
| 132 | QAtomicInt evictionPolicy; |
| 133 | |
| 134 | // pageSize and cacheSize determine the number of pages. The number of |
| 135 | // pages determine the page table size and (indirectly) the index table |
| 136 | // size. |
| 137 | QAtomicInt pageSize; |
| 138 | |
| 139 | // This variable is added to reserve space for later cache timestamping |
| 140 | // support. The idea is this variable will be updated when the cache is |
| 141 | // written to, to allow clients to detect a changed cache quickly. |
| 142 | QAtomicInt cacheTimestamp; |
| 143 | |
| 144 | /* |
| 145 | * Converts the given average item size into an appropriate page size. |
| 146 | */ |
| 147 | static unsigned equivalentPageSize(unsigned itemSize); |
| 148 | |
| 149 | // Returns pageSize in unsigned format. |
| 150 | unsigned cachePageSize() const; |
| 151 | |
| 152 | /* |
| 153 | * This is effectively the class ctor. But since we're in shared memory, |
| 154 | * there's a few rules: |
| 155 | * |
| 156 | * 1. To allow for some form of locking in the initial-setup case, we |
| 157 | * use an atomic int, which will be initialized to 0 by mmap(). Then to |
| 158 | * take the lock we atomically increment the 0 to 1. If we end up calling |
| 159 | * the QAtomicInt constructor we can mess that up, so we can't use a |
| 160 | * constructor for this class either. |
| 161 | * 2. Any member variable you add takes up space in shared memory as well, |
| 162 | * so make sure you need it. |
| 163 | */ |
| 164 | bool performInitialSetup(uint _cacheSize, uint _pageSize); |
| 165 | |
| 166 | void clearInternalTables(); |
| 167 | const IndexTableEntry *indexTable() const; |
| 168 | const PageTableEntry *pageTable() const; |
| 169 | const void *cachePages() const; |
| 170 | const void *page(pageID at) const; |
| 171 | |
| 172 | // The following are non-const versions of some of the methods defined |
| 173 | // above. They use const_cast<> because I feel that is better than |
| 174 | // duplicating the code. I suppose template member functions (?) |
| 175 | // may work, may investigate later. |
| 176 | IndexTableEntry *indexTable(); |
| 177 | PageTableEntry *pageTable(); |
| 178 | void *cachePages(); |
| 179 | void *page(pageID at); |
| 180 | uint pageTableSize() const; |
| 181 | uint indexTableSize() const; |
| 182 | |
| 183 | /* |
| 184 | * @return the index of the first page, for the set of contiguous |
| 185 | * pages that can hold @p pagesNeeded PAGES. |
| 186 | */ |
| 187 | pageID findEmptyPages(uint pagesNeeded) const; |
| 188 | |
| 189 | // left < right? |
| 190 | static bool lruCompare(const IndexTableEntry &l, const IndexTableEntry &r); |
| 191 | |
| 192 | // left < right? |
| 193 | static bool seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r); |
| 194 | |
| 195 | // left < right? |
| 196 | static bool ageCompare(const IndexTableEntry &l, const IndexTableEntry &r); |
| 197 | |
| 198 | void defragment(); |
| 199 | |
| 200 | /* |
| 201 | * Finds the index entry for a given key. |
| 202 | * @param key UTF-8 encoded key to search for. |
| 203 | * @return The index of the entry in the cache named by @p key. Returns |
| 204 | * <0 if no such entry is present. |
| 205 | */ |
| 206 | qint32 findNamedEntry(const QByteArray &key) const; |
| 207 | |
| 208 | // Function to use with std::unique_ptr in removeUsedPages below... |
| 209 | static void deleteTable(IndexTableEntry *table); |
| 210 | |
| 211 | /* |
| 212 | * Removes the requested number of pages. |
| 213 | * |
| 214 | * @param numberNeeded the number of pages required to fulfill a current request. |
| 215 | * This number should be <0 and <= the number of pages in the cache. |
| 216 | * @return The identifier of the beginning of a consecutive block of pages able |
| 217 | * to fill the request. Returns a value >= pageTableSize() if no such |
| 218 | * request can be filled. |
| 219 | * @internal |
| 220 | */ |
| 221 | uint removeUsedPages(uint numberNeeded); |
| 222 | |
| 223 | // Returns the total size required for a given cache size. |
| 224 | static uint totalSize(uint cacheSize, uint effectivePageSize); |
| 225 | |
| 226 | uint fileNameHash(const QByteArray &utf8FileName) const; |
| 227 | void clear(); |
| 228 | void removeEntry(uint index); |
| 229 | |
| 230 | static quint32 generateHash(const QByteArray &buffer); |
| 231 | |
| 232 | /* |
| 233 | * @return the smallest integer greater than or equal to (@p a / @p b). |
| 234 | * @param a Numerator, should be ≥ 0. |
| 235 | * @param b Denominator, should be > 0. |
| 236 | */ |
| 237 | static unsigned intCeil(unsigned a, unsigned b); |
| 238 | }; |
| 239 | |
| 240 | #endif /* KSDCMEMORY_P_H */ |
| 241 | |