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