ChibiOS/RT 7.0.6
chobjcaches.c
Go to the documentation of this file.
1/*
2 ChibiOS - Copyright (C) 2006-2026 Giovanni Di Sirio.
3
4 This file is part of ChibiOS.
5
6 ChibiOS is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation version 3 of the License.
9
10 ChibiOS is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/**
20 * @file oslib/src/chobjcaches.c
21 * @brief Objects Caches code.
22 * @details Objects caches.
23 * <h2>Operation mode</h2>
24 * An object cache allows to retrieve and release objects from a
25 * slow media, for example a disk or flash.<br>
26 * The most recently used objects are kept in a series of RAM
27 * buffers making access faster. Objects are identified by a
28 * pair <group, key> which could be mapped, for example, to a
29 * disk drive identifier and sector identifier.<br>
30 * Read and write operations are performed using externally-supplied
31 * functions, the cache is device-agnostic.<br>
32 * The cache uses internally an hash table, the size of the table
33 * should be dimensioned to minimize the risk of hash collisions,
34 * a factor of two is usually acceptable, it depends on the specific
35 * application requirements.<br>
36 * Operations defined for caches:
37 * - <b>Get Object</b>: Retrieves an object from cache, if not
38 * present then an empty buffer is returned.
39 * - <b>Read Object</b>: Retrieves an object from cache, if not
40 * present a buffer is allocated and the object is read from the
41 * media.
42 * - <b>Release Object</b>: Releases an object to the cache handling
43 * the media update, if required.
44 * .
45 * @pre In order to use the objects caches APIs the @p CH_CFG_USE_OBJ_CACHES
46 * option must be enabled in @p chconf.h.
47 * @note Compatible with RT and NIL.
48 *
49 * @addtogroup oslib_objcaches
50 * @{
51 */
52
53#include "ch.h"
54
55#if (CH_CFG_USE_OBJ_CACHES == TRUE) || defined(__DOXYGEN__)
56
57/*===========================================================================*/
58/* Module local definitions. */
59/*===========================================================================*/
60
61/* Default hash function.*/
62#if !defined(OC_HASH_FUNCTION) || defined(__DOXYGEN__)
63#define OC_HASH_FUNCTION(ocp, group, key) \
64 (((unsigned)(group) + (unsigned)(key)) & ((unsigned)(ocp)->hashn - 1U))
65#endif
66
67/* Insertion into an hash slot list.*/
68#define HASH_INSERT(ocp, objp, group, key) { \
69 oc_hash_header_t *hhp; \
70 (hhp) = &(ocp)->hashp[OC_HASH_FUNCTION(ocp, group, key)]; \
71 (objp)->hash_next = (hhp)->hash_next; \
72 (objp)->hash_prev = (oc_object_t *)(hhp); \
73 (hhp)->hash_next->hash_prev = (objp); \
74 (hhp)->hash_next = (objp); \
75}
76
77/* Removal of an object from the hash.*/
78#define HASH_REMOVE(objp) { \
79 (objp)->hash_prev->hash_next = (objp)->hash_next; \
80 (objp)->hash_next->hash_prev = (objp)->hash_prev; \
81}
82
83/* Insertion on LRU list head (newer objects).*/
84#define LRU_INSERT_HEAD(ocp, objp) { \
85 (objp)->lru_next = (ocp)->lru.lru_next; \
86 (objp)->lru_prev = (oc_object_t *)&(ocp)->lru; \
87 (ocp)->lru.lru_next->lru_prev = (objp); \
88 (ocp)->lru.lru_next = (objp); \
89}
90
91/* Insertion on LRU list head (newer objects).*/
92#define LRU_INSERT_TAIL(ocp, objp) { \
93 (objp)->lru_prev = (ocp)->lru.lru_prev; \
94 (objp)->lru_next = (oc_object_t *)&(ocp)->lru; \
95 (ocp)->lru.lru_prev->lru_next = (objp); \
96 (ocp)->lru.lru_prev = (objp); \
97}
98
99/* Removal of an object from the LRU list.*/
100#define LRU_REMOVE(objp) { \
101 (objp)->lru_prev->lru_next = (objp)->lru_next; \
102 (objp)->lru_next->lru_prev = (objp)->lru_prev; \
103}
104
105/*===========================================================================*/
106/* Module exported variables. */
107/*===========================================================================*/
108
109/*===========================================================================*/
110/* Module local types. */
111/*===========================================================================*/
112
113/*===========================================================================*/
114/* Module local variables. */
115/*===========================================================================*/
116
117/*===========================================================================*/
118/* Module local functions. */
119/*===========================================================================*/
120
121/**
122 * @brief Returns an object pointer from the cache, if present.
123 *
124 * @param[out] ocp pointer to the @p objects_cache_t structure to be
125 * @param[in] group object group identifier
126 * @param[in] key object identifier within the group
127 * initialized
128 * @return The pointer to the retrieved object.
129 * @retval NULL if the object is not in cache.
130 *
131 * @notapi
132 */
134 uint32_t group,
135 uint32_t key) {
136 oc_hash_header_t *hhp;
137 oc_object_t *objp;
138
139 /* Hash slot where to search for an hit.*/
140 hhp = &ocp->hashp[OC_HASH_FUNCTION(ocp, group, key)];
141 objp = hhp->hash_next;
142
143 /* Scanning the siblings collision list.*/
144 while (objp != (oc_object_t *)hhp) {
145 if ((objp->obj_key == key) && (objp->obj_group == group)) {
146
147 /* Cache hit.*/
148 return objp;
149 }
150 objp = objp->hash_next;
151 }
152
153 return NULL;
154}
155
156/**
157 * @brief Gets the least recently used object buffer from the LRU list.
158 *
159 * @param[out] ocp pointer to the @p objects_cache_t structure to be
160 * @return The pointer to the retrieved object.
161 *
162 * @notapi
163 */
165 oc_object_t *objp;
166
167 while (true) {
168 /* Waiting for an object buffer to become available in the LRU.*/
169 (void) chSemWaitS(&ocp->lru_sem);
170
171 /* Now an object buffer is in the LRU for sure, taking it from the
172 LRU tail.*/
173 objp = ocp->lru.lru_prev;
174
176 "not in LRU");
178 "semaphore counter not 1");
179
180 LRU_REMOVE(objp);
181 objp->obj_flags &= ~OC_FLAG_INLRU;
182
183 /* Getting the object semaphore, we know there is no wait so
184 using the "fast" variant.*/
185 chSemFastWaitI(&objp->obj_sem);
186
187 /* If it is a buffer not needing (lazy) write then it can be used
188 right away.*/
189 if ((objp->obj_flags & OC_FLAG_LAZYWRITE) == 0U) {
190
191 /* Removing from hash table if required.*/
192 if ((objp->obj_flags & OC_FLAG_INHASH) != 0U) {
193 HASH_REMOVE(objp);
194 }
195
196 /* Removing all flags, it is "new" now.*/
197 objp->obj_flags = 0U;
198
199 return objp;
200 }
201
202 /* Out of critical section.*/
203 chSysUnlock();
204
205 /* Invoking the writer asynchronously, it will release the buffer once it
206 is written. It is responsibility of the write function to release
207 the buffer.*/
209 (void) ocp->writef(ocp, objp, true);
210
211 /* Critical section enter again.*/
212 chSysLock();
213 }
214}
215
216/*===========================================================================*/
217/* Module exported functions. */
218/*===========================================================================*/
219
220/**
221 * @brief Initializes a @p objects_cache_t object.
222 *
223 * @param[out] ocp pointer to the @p objects_cache_t structure to be
224 * initialized
225 * @param[in] hashn number of elements in the hash table array, must be
226 * a power of two and not lower than @p objn
227 * @param[in] hashp pointer to the hash table as an array of
228 * @p oc_hash_header_t
229 * @param[in] objn number of elements in the objects table array
230 * @param[in] objsz size of elements in the objects table array, the
231 * minimum value is <tt>sizeof (oc_object_t)</tt>.
232 * @param[in] objvp pointer to the hash objects as an array of structures
233 * starting with an @p oc_object_t
234 * @param[in] readf pointer to an object reader function
235 * @param[in] writef pointer to an object writer function
236 *
237 * @init
238 */
240 ucnt_t hashn,
241 oc_hash_header_t *hashp,
242 ucnt_t objn,
243 size_t objsz,
244 void *objvp,
245 oc_readf_t readf,
246 oc_writef_t writef) {
247
248 chDbgCheck((ocp != NULL) && (hashp != NULL) && (objvp != NULL) &&
249 ((hashn & (hashn - (ucnt_t)1)) == (ucnt_t)0) &&
250 (objn > (ucnt_t)0) && (hashn >= objn) &&
251 (objsz >= sizeof (oc_object_t)) &&
252 ((objsz & (PORT_NATURAL_ALIGN - 1U)) == 0U));
253
254 chSemObjectInit(&ocp->lru_sem, (cnt_t)objn);
255 ocp->hashn = hashn;
256 ocp->hashp = hashp;
257 ocp->objn = objn;
258 ocp->objsz = objsz;
259 ocp->objvp = objvp;
260 ocp->readf = readf;
261 ocp->writef = writef;
262 ocp->lru.hash_next = NULL;
263 ocp->lru.hash_prev = NULL;
264 ocp->lru.lru_next = (oc_object_t *)&ocp->lru;
265 ocp->lru.lru_prev = (oc_object_t *)&ocp->lru;
266
267 /* Hash headers initialization.*/
268 do {
269 hashp->hash_next = (oc_object_t *)hashp;
270 hashp->hash_prev = (oc_object_t *)hashp;
271 hashp++;
272 } while (hashp < &ocp->hashp[ocp->hashn]);
273
274 /* Object headers initialization.*/
275 do {
276 oc_object_t *objp = (oc_object_t *)objvp;
277
278 chSemObjectInit(&objp->obj_sem, (cnt_t)1);
279 LRU_INSERT_HEAD(ocp, objp);
280 objp->obj_group = 0U;
281 objp->obj_key = 0U;
282 objp->obj_flags = OC_FLAG_INLRU;
283 objp->dptr = NULL;
284 objvp = (void *)((uint8_t *)objvp + objsz);
285 objn--;
286 } while (objn > (ucnt_t)0);
287}
288
289/**
290 * @brief Retrieves an object from the cache.
291 * @note If the object is not in cache then the returned object is marked
292 * as @p OC_FLAG_NOTSYNC meaning that its data contains garbage and
293 * must be initialized.
294 *
295 * @param[in] ocp pointer to the @p objects_cache_t structure
296 * @param[in] group object group identifier
297 * @param[in] key object identifier within the group
298 * @return The pointer to the retrieved object.
299 *
300 * @api
301 */
303 uint32_t group,
304 uint32_t key) {
305 oc_object_t *objp;
306
307 /* Critical section enter, the hash check operation is fast.*/
308 chSysLock();
309
310 /* Checking the cache for a hit.*/
311 objp = hash_get_s(ocp, group, key);
312 if (objp != NULL) {
313
315 "not in hash");
316
317 /* Cache hit, checking if the buffer is owned by some
318 other thread.*/
319 if (chSemGetCounterI(&objp->obj_sem) > (cnt_t)0) {
320 /* Not owned case, it is in the LRU list.*/
321
323 "not in LRU");
325 "LRU semaphore out of sync");
326
327 /* Removing the object from LRU, now it is "owned".*/
328 LRU_REMOVE(objp);
329 objp->obj_flags &= ~OC_FLAG_INLRU;
330
331 /* Consuming the matching LRU token.*/
332 chSemFastWaitI(&ocp->lru_sem);
333
334 /* Getting the object semaphore, we know there is no wait so
335 using the "fast" variant.*/
336 chSemFastWaitI(&objp->obj_sem);
337 }
338 else {
339 /* Owned case, some other thread is playing with this object, we
340 need to wait.*/
341
342 chDbgAssert((objp->obj_flags & OC_FLAG_INLRU) == 0U, "in LRU");
343
344 /* Waiting on the buffer semaphore.*/
345 (void) chSemWaitS(&objp->obj_sem);
346 }
347 }
348 else {
349 /* Cache miss, getting an object buffer from the LRU list.*/
350 objp = lru_get_last_s(ocp);
351
352 /* Naming this object and publishing it in the hash table.*/
353 objp->obj_group = group;
354 objp->obj_key = key;
356 HASH_INSERT(ocp, objp, group, key);
357 }
358
359 /* Out of critical section and returning the object.*/
360 chSysUnlock();
361
362 return objp;
363}
364
365/**
366 * @brief Releases an object into the cache.
367 * @note This function gives a meaning to the following flags:
368 * - @p OC_FLAG_INLRU must be cleared.
369 * - @p OC_FLAG_INHASH must be set.
370 * - @p OC_FLAG_SHARED must be cleared.
371 * - @p OC_FLAG_NOTSYNC invalidates the object and queues it on
372 * the LRU tail.
373 * - @p OC_FLAG_LAZYWRITE is ignored and kept, a write will occur
374 * when the object is removed from the LRU list (lazy write).
375 * .
376 *
377 * @param[in] ocp pointer to the @p objects_cache_t structure
378 * @param[in] objp pointer to the @p oc_object_t structure
379 *
380 * @iclass
381 */
383 oc_object_t *objp) {
384
385 /* Checking initial conditions of the object to be released.*/
389 "invalid object state");
391 "semaphore counter greater than 0");
392
393 /* If some thread is waiting for this specific buffer then it is
394 handed directly without going through the LRU.*/
395 if (chSemGetCounterI(&objp->obj_sem) < (cnt_t)0) {
396 /* Clearing all flags except those that are still meaningful, note,
397 OC_FLAG_NOTSYNC and OC_FLAG_LAZYWRITE are passed, the other thread
398 will handle them.*/
400 chSemSignalI(&objp->obj_sem);
401 return;
402 }
403
404 /* If the object specifies OC_FLAG_NOTSYNC then it must be invalidated
405 and removed from the hash table.*/
406 if ((objp->obj_flags & OC_FLAG_NOTSYNC) != 0U) {
407 HASH_REMOVE(objp);
408 LRU_INSERT_TAIL(ocp, objp);
409 objp->obj_group = 0U;
410 objp->obj_key = 0U;
411 objp->obj_flags = OC_FLAG_INLRU;
412 }
413 else {
414 /* LRU insertion point depends on the OC_FLAG_FORGET flag.*/
415 if ((objp->obj_flags & OC_FLAG_FORGET) == 0U) {
416 /* Placing it on head.*/
417 LRU_INSERT_HEAD(ocp, objp);
418 }
419 else {
420 /* Low priority data, placing it on tail.*/
421 LRU_INSERT_TAIL(ocp, objp);
422 }
424 objp->obj_flags |= OC_FLAG_INLRU;
425 }
426
427 /* Increasing the LRU counter semaphore.*/
428 chSemSignalI(&ocp->lru_sem);
429
430 /* Releasing the object, we know there are no threads waiting so
431 using the "fast" signal variant.*/
433}
434
435/**
436 * @brief Reads object data from the storage.
437 * @note In case of asynchronous operation an error condition is not
438 * reported by this function.
439 *
440 * @param[in] ocp pointer to the @p objects_cache_t structure
441 * @param[in] objp pointer to the @p oc_object_t structure
442 * @param[in] async requests an asynchronous operation if supported, the
443 * function is then responsible for releasing the
444 * object
445 * @return The operation status. In case of asynchronous
446 * operation @p false is always returned.
447 * @retval false if the operation succeeded.
448 * @retval true if the synchronous read operation failed.
449 *
450 * @api
451 */
453 oc_object_t *objp,
454 bool async) {
455
456 /* Marking it as OC_FLAG_NOTSYNC because the read operation is going
457 to corrupt it in case of failure. It is responsibility of the read
458 implementation to clear it if the operation succeeds.*/
459 objp->obj_flags |= OC_FLAG_NOTSYNC;
460
461 return ocp->readf(ocp, objp, async);
462}
463
464/**
465 * @brief Writes the object data back to storage.
466 * @note In case of asynchronous operation an error condition is not
467 * reported by this function.
468 *
469 * @param[in] ocp pointer to the @p objects_cache_t structure
470 * @param[in] objp pointer to the @p oc_object_t structure
471 * @param[in] async requests an asynchronous operation if supported, the
472 * function is then responsible for releasing the
473 * object
474 * @return The operation status. In case of asynchronous
475 * operation @p false is always returned.
476 * @retval false if the operation succeeded.
477 * @retval true if the synchronous write operation failed.
478 *
479 * @api
480 */
482 oc_object_t *objp,
483 bool async) {
484
485 /* Resetting the OC_FLAG_LAZYWRITE flag in order to prevent multiple
486 writes.*/
488
489 return ocp->writef(ocp, objp, async);
490}
491
492#endif /* CH_CFG_USE_OBJ_CACHES == TRUE */
493
494/** @} */
ChibiOS/RT main include file.
#define chDbgAssert(c, r)
Condition assertion.
Definition chdebug.h:143
#define chDbgCheck(c)
Function parameters check.
Definition chdebug.h:117
int32_t cnt_t
Definition chearly.h:91
uint32_t ucnt_t
Definition chearly.h:92
bool chCacheReadObject(objects_cache_t *ocp, oc_object_t *objp, bool async)
Reads object data from the storage.
#define OC_FLAG_LAZYWRITE
Definition chobjcaches.h:44
void chCacheObjectInit(objects_cache_t *ocp, ucnt_t hashn, oc_hash_header_t *hashp, ucnt_t objn, size_t objsz, void *objvp, oc_readf_t readf, oc_writef_t writef)
Initializes a objects_cache_t object.
#define OC_FLAG_INLRU
Definition chobjcaches.h:40
struct ch_oc_object oc_object_t
Type of a cached object.
Definition chobjcaches.h:82
bool chCacheWriteObject(objects_cache_t *ocp, oc_object_t *objp, bool async)
Writes the object data back to storage.
struct ch_oc_hash_header oc_hash_header_t
Type of an hash element header.
Definition chobjcaches.h:72
bool(* oc_readf_t)(objects_cache_t *ocp, oc_object_t *objp, bool async)
Object read function.
Definition chobjcaches.h:97
#define OC_FLAG_FORGET
Definition chobjcaches.h:45
oc_object_t * chCacheGetObject(objects_cache_t *ocp, uint32_t group, uint32_t key)
Retrieves an object from the cache.
#define LRU_INSERT_HEAD(ocp, objp)
Definition chobjcaches.c:84
static oc_object_t * lru_get_last_s(objects_cache_t *ocp)
Gets the least recently used object buffer from the LRU list.
struct ch_objects_cache objects_cache_t
Type of a cache object.
Definition chobjcaches.h:87
void chCacheReleaseObjectI(objects_cache_t *ocp, oc_object_t *objp)
Releases an object into the cache.
#define OC_FLAG_SHARED
Definition chobjcaches.h:42
bool(* oc_writef_t)(objects_cache_t *ocp, oc_object_t *objp, bool async)
Object write function.
#define HASH_REMOVE(objp)
Definition chobjcaches.c:78
#define LRU_INSERT_TAIL(ocp, objp)
Definition chobjcaches.c:92
#define OC_FLAG_NOTSYNC
Definition chobjcaches.h:43
#define OC_HASH_FUNCTION(ocp, group, key)
Definition chobjcaches.c:63
#define OC_FLAG_INHASH
Definition chobjcaches.h:41
#define HASH_INSERT(ocp, objp, group, key)
Definition chobjcaches.c:68
#define LRU_REMOVE(objp)
static oc_object_t * hash_get_s(objects_cache_t *ocp, uint32_t group, uint32_t key)
Returns an object pointer from the cache, if present.
static cnt_t chSemGetCounterI(const semaphore_t *sp)
Returns the semaphore counter current value.
Definition chsem.h:188
void chSemObjectInit(semaphore_t *sp, cnt_t n)
Initializes a semaphore with the specified counter value.
Definition chsem.c:96
msg_t chSemWaitS(semaphore_t *sp)
Performs a wait operation on a semaphore.
Definition chsem.c:190
static void chSemFastWaitI(semaphore_t *sp)
Decreases the semaphore counter.
Definition chsem.h:157
void chSemSignalI(semaphore_t *sp)
Performs a signal operation on a semaphore.
Definition chsem.c:314
static void chSemFastSignalI(semaphore_t *sp)
Increases the semaphore counter.
Definition chsem.h:173
static void chSysLock(void)
Enters the kernel lock state.
Definition chsys.h:406
static void chSysUnlock(void)
Leaves the kernel lock state.
Definition chsys.h:420
oc_writef_t writef
Writer functions for cached objects.
void * objvp
Pointer to the objects table.
ucnt_t objn
Number of elements in the objects table.
oc_hash_header_t * hashp
Pointer to the hash table.
ucnt_t hashn
Number of elements in the hash table.
semaphore_t lru_sem
Semaphore for LRU access.
size_t objsz
Size of elements in the objects table.
oc_readf_t readf
Reader functions for cached objects.
oc_lru_header_t lru
LRU list header.
oc_object_t * hash_prev
Previous in the collisions list.
oc_object_t * hash_next
Next in the collisions list.
oc_object_t * lru_next
Next in the LRU list.
oc_object_t * hash_next
Next in the collisions list.
oc_object_t * lru_prev
Previous in the LRU list.
oc_object_t * hash_prev
Previous in the collisions list.
uint32_t obj_key
Object key.
oc_flags_t obj_flags
Object flags.
oc_object_t * hash_next
Next in the collisions list.
void * dptr
User pointer.
semaphore_t obj_sem
Semaphore for object access.
uint32_t obj_group
Object group.