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 | |