SimpleOS

LXR

Navigation



Site hébergé par : enix

The LXR Cross Referencer for SOS

source navigation ]
diff markup ]
identifier search ]
general search ]
 
 
Article:1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 6.5 ] [ 7 ] [ 7.5 ] [ 8 ] [ 9 ] [ 9.5 ]

001 /* Copyright (C) 2005,2006      David Decotigny
002 
003    This program is free software; you can redistribute it and/or
004    modify it under the terms of the GNU General Public License
005    as published by the Free Software Foundation; either version 2
006    of the License, or (at your option) any later version.
007 
008    This program is distributed in the hope that it will be useful,
009    but WITHOUT ANY WARRANTY; without even the implied warranty of
010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
011    GNU General Public License for more details.
012 
013    You should have received a copy of the GNU General Public License
014    along with this program; if not, write to the Free Software
015    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
016    USA.
017 */
018 
019 #include <sos/ksynch.h>
020 #include <sos/hash.h>
021 #include <sos/kmem_slab.h>
022 #include <sos/kmalloc.h>
023 #include <sos/ksynch.h>
024 #include <sos/assert.h>
025 #include <sos/list.h>
026 
027 #include "blkcache.h"
028 
029 
030 /**
031  * @note In the implementation below, we choose to transfer the blocks
032  * from list to list /before/ doing the disk access (if needed). The
033  * aim is to have the entries in the right list before any other
034  * thread can lock them. Actually, these "other threads" can lock the
035  * entry only during the disk accesses (kernel is non
036  * preemptible). So, putting the entries in the correct list /before/
037  * doing the actual disk read/write access will lower the probability
038  * for the thread to learn that, at the end, the entry it was waiting
039  * for has been transferred on another list.
040  *
041  * @note What is extremely important however, is that the
042  * sos_hash_insert/remove operation be carried out BEFORE the
043  * bocck_read/write operations. If not, then it may be possible to
044  * insert an entry already inserted (which causes the system to halt),
045  * etc.
046  */
047 
048 
049 
050 struct sos_block_cache_entry
051 {
052   /** The key of the hash map */
053   sos_luoffset_t block_index;
054 
055   /** Kernel address where the data for the block are stored */
056   sos_vaddr_t block_contents;
057 
058   /** Is the block available, dirty or not ? */
059   enum { ENTRY_FREE, ENTRY_SYNC, ENTRY_DIRTY } state;
060   
061   /** Lock to protect the entry */
062   struct sos_kmutex lock;
063 
064   /** Linkage structure to keep the cache block in the hash map */
065   struct sos_hash_linkage hlink;
066 
067   /** Links to insert the bloc into the free/sync/dirty lists */
068   struct sos_block_cache_entry *prev, *next;
069 };
070 
071 
072 /** The entries of all the cache entries are stored in a single SLAB pool */
073 static struct sos_kslab_cache * cache_of_bkcache_entries;
074 
075 
076 struct sos_block_cache
077 {
078   /** SLAB pool for the block contents */
079   struct sos_kslab_cache         * slab_cache;
080 
081   /** Dictionary offset -> block cache entry */
082   struct sos_hash_table          * lookup_table;
083 
084   /** Hardware read/write operations */
085   struct sos_blockdev_operations * operations;
086   void                           * blkdev_instance_custom_data;
087 
088   /* Lists to look into in order to free a node. LRU is enforced */
089   struct sos_block_cache_entry * free_list;  /**< List of entries
090                                                   still available */
091   struct sos_block_cache_entry * sync_list;  /**< Blocks in sync with
092                                                   disk (LRU at end) */
093   struct sos_block_cache_entry * dirty_list; /**< Dirty blocks (LRU
094                                                   last) */
095 };
096 
097 
098 sos_ret_t sos_blkcache_subsystem_setup()
099 {
100   cache_of_bkcache_entries
101     = sos_kmem_cache_create("blkcache_entry",
102                             sizeof(struct sos_block_cache_entry),
103                             1, 0,
104                             SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO);
105   if (NULL == cache_of_bkcache_entries)
106     return -SOS_ENOMEM;
107   return SOS_OK;
108 }
109 
110 
111 struct sos_block_cache *
112 sos_blkcache_new_cache(void * blockdev_instance_custom_data,
113                        sos_size_t  block_size,
114                        sos_count_t cache_size_in_blocks,
115                        struct sos_blockdev_operations * blockdev_ops)
116 {
117   sos_count_t idx;
118   struct sos_block_cache * blkcache;
119 
120   SOS_ASSERT_FATAL(block_size > 0);
121   SOS_ASSERT_FATAL(cache_size_in_blocks > 0);
122 
123   blkcache
124     = (struct sos_block_cache*) sos_kmalloc(sizeof(struct sos_block_cache), 0);
125   if (NULL == blkcache)
126     return NULL;
127 
128   blkcache->blkdev_instance_custom_data = blockdev_instance_custom_data;
129   blkcache->operations                  = blockdev_ops;
130 
131   /* Allocate the hash table */
132   blkcache->lookup_table = sos_hash_create("blkcache",
133                                            struct sos_block_cache_entry,
134                                            sos_hash_ui64,
135                                            sos_hash_key_eq_ui64,
136                                            17, block_index, hlink);
137   if (NULL == blkcache->lookup_table)
138     {
139       sos_kfree((sos_vaddr_t)blkcache);
140       return NULL;
141     }
142 
143   /* Allocate the slab cache for the blocks */
144   blkcache->slab_cache = sos_kmem_cache_create("blkcache", block_size,
145                                                4, 0,
146                                                SOS_KSLAB_CREATE_MAP
147                                                | SOS_KSLAB_CREATE_ZERO);
148   if (NULL == blkcache->slab_cache)
149     {
150       sos_hash_dispose(blkcache->lookup_table);
151       sos_kfree((sos_vaddr_t)blkcache);
152       return NULL;
153     }
154 
155   /* Allocate the cache blocks in this slab cache, and mark them as
156      "free" */
157   for (idx = 0 ; idx < cache_size_in_blocks ; idx ++)
158     {
159       struct sos_block_cache_entry * bkcache_entry;
160       sos_vaddr_t bkcache_contents;
161 
162       bkcache_entry
163         = (struct sos_block_cache_entry*)sos_kmem_cache_alloc(cache_of_bkcache_entries,
164                                                       0);
165       if (NULL == bkcache_entry)
166         {
167           sos_blkcache_delete_cache(blkcache);
168           return NULL;
169         }
170 
171       bkcache_contents = sos_kmem_cache_alloc(blkcache->slab_cache, 0);
172       if (NULL == (void*)bkcache_contents)
173         {
174           sos_kmem_cache_free((sos_vaddr_t)bkcache_entry);
175           sos_blkcache_delete_cache(blkcache);
176           return NULL;
177         }
178 
179       /* Initialize the cache entry */
180       bkcache_entry->block_contents = bkcache_contents;
181       bkcache_entry->state = ENTRY_FREE;
182       SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_init(& bkcache_entry->lock,
183                                                  "bkcache_lock",
184                                                  SOS_KWQ_ORDER_FIFO));
185       
186       /* Now add it in the free list */
187       list_add_head(blkcache->free_list, bkcache_entry);
188     }
189 
190   return blkcache;
191 }
192 
193 
194 /** Helper function used to collapse the sync/free lists */
195 static sos_ret_t blkcache_collapse_list(struct sos_block_cache_entry ** l)
196 {
197   while (! list_is_empty(*l))
198     {
199       struct sos_block_cache_entry * entry = list_pop_head(*l);
200       if (NULL == entry)
201         break;
202 
203       /* The delete_cache function should be called only when the
204          block device is not currently in use */
205       SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_dispose(& entry->lock));
206 
207       if (NULL != (void*)entry->block_contents)
208         sos_kmem_cache_free(entry->block_contents);
209 
210       sos_kmem_cache_free((sos_vaddr_t)entry);
211     }
212 
213   return SOS_OK;
214 }
215 
216 
217 sos_ret_t
218 sos_blkcache_delete_cache(struct sos_block_cache * bc)
219 {
220   sos_blkcache_flush(bc); /* The dirty list is expected to be empty */
221 
222   sos_hash_dispose(bc->lookup_table);
223   blkcache_collapse_list(& bc->sync_list);
224   blkcache_collapse_list(& bc->free_list);
225 
226   sos_kfree((sos_vaddr_t)bc);
227 
228   return SOS_OK;
229 }
230 
231 
232 /** Helper function used to transfer a dirty block to the
233     sync_list. @note the entry is expected to be locked */
234 static sos_ret_t blkcache_flush_entry(struct sos_block_cache * bc,
235                                       struct sos_block_cache_entry * entry,
236                                       sos_bool_t mark_as_free)
237 {
238   sos_ret_t retval = SOS_OK;
239 
240   SOS_ASSERT_FATAL(TRUE == sos_kmutex_owned_by_me(& entry->lock));
241   SOS_ASSERT_FATAL(entry->state == ENTRY_DIRTY);
242 
243   /* We prepare to transfer it to the sync/free list */
244   if (mark_as_free)
245     {
246       list_delete(bc->dirty_list, entry);
247       entry->state = ENTRY_FREE;
248       sos_hash_remove(bc->lookup_table, entry);
249       list_add_head(bc->free_list, entry);
250     }
251   else
252     {
253       list_delete(bc->dirty_list, entry);
254       entry->state = ENTRY_SYNC;
255       list_add_head(bc->sync_list, entry);
256     }
257 
258   retval = bc->operations->write_block(bc->blkdev_instance_custom_data,
259                                        entry->block_contents,
260                                        entry->block_index);
261   if (SOS_OK != retval)
262     {
263       /*
264        * Putting the entry back to the dirty list may cause
265        * retrieve_block to loop infinitely (always trying to transfer
266        * this block to disk...) !... A solution would be to register
267        * this block in a dictionary of failing blocks and to force
268        * retrieve block to ignore requests on these blocks
269        */
270       SOS_FATAL_ERROR("Not implemented yet: we don't support failed disk access (see comments in source file)");
271     }
272 
273   return retval;
274 }
275 
276 
277 struct sos_block_cache_entry *
278 sos_blkcache_retrieve_block(struct sos_block_cache * bc,
279                             sos_luoffset_t block_index,
280                             sos_blkcache_access_type_t access_type,
281                             sos_vaddr_t */*out*/ block_contents)
282 {
283   struct sos_block_cache_entry * entry = NULL;
284   *block_contents = (sos_vaddr_t)NULL;
285 
286   /* Get and lock the entry */
287   while (TRUE)
288     {
289       entry = sos_hash_lookup(bc->lookup_table,
290                               & block_index);
291       if (NULL != entry)
292         {
293           /* The block was already in the cache */
294           
295           sos_kmutex_lock(& entry->lock, NULL);
296           
297           /* We have the mutex. However, this entry can now be
298              dedicated to another block: it was stolen by another
299              thread during the blocking time */
300           if ((ENTRY_FREE != entry->state)
301               && (entry->block_index == block_index))
302             {
303               /* Great ! This entry is still the one we expect ! */
304 
305               /* Write-only access: force it to be dirty NOW ! */
306               if ( (access_type == SOS_BLKCACHE_WRITE_ONLY)
307                    && (ENTRY_SYNC == entry->state) )
308                 {
309                   list_delete(bc->sync_list, entry);
310                   entry->state = ENTRY_DIRTY;
311                   list_add_head(bc->dirty_list, entry);
312                 }
313 
314               *block_contents = entry->block_contents;
315               return entry;
316             }
317           
318           /* Bad luck: someone stole the entry and used it in the
319              meantime */
320           sos_kmutex_unlock(& entry->lock);
321           /* Go back and look it up again */
322           continue;
323         }
324 
325 
326       /* Ok, the block is not yet in the cache: we need to allocate a new
327          one */
328 
329       /* The simplest: there is a free slot in the free list (take the
330          most recently freed) */
331       if (! list_is_empty(bc->free_list))
332         {
333           entry = list_get_head(bc->free_list);
334           sos_kmutex_lock(& entry->lock, NULL);
335 
336           /* By the time we return from the lock, the entry could have
337              be stolen */
338           if ((ENTRY_FREE != entry->state)
339               || (NULL != sos_hash_lookup(bc->lookup_table, & block_index)))
340             {
341               sos_kmutex_unlock(& entry->lock);
342               continue;
343             }
344 
345           /* Ok, we got a REALLY free entry ! */
346           break;
347         }
348 
349       /* A little more complicated: there is a non-dirty entry in
350          synch with disk, transfer it to the free list. Take the least
351          recently used */
352       if (! list_is_empty(bc->sync_list))
353         {
354           entry = list_get_tail(bc->sync_list);
355           sos_kmutex_lock(& entry->lock, NULL);
356 
357           /* During blocking time, the block is not in the sync list
358              anymore ! We'd better redo a complete iteration to choose
359              another block, just in case the block we are looking for
360              was transferred in cache by someone else */
361           if ((ENTRY_SYNC != entry->state)
362               || (NULL != sos_hash_lookup(bc->lookup_table, & block_index)))
363             {
364               sos_kmutex_unlock(& entry->lock);
365               continue;
366             }
367 
368           list_delete(bc->sync_list, entry);
369           sos_hash_remove(bc->lookup_table, entry);
370           entry->state = ENTRY_FREE;
371           list_add_head(bc->free_list, entry);
372 
373           /* Now we have a free entry for us ! */
374           break;
375         }
376       
377       /*
378        * Bad luck: we have to flush a dirty entry back to disk
379        */
380       SOS_ASSERT_FATAL(! list_is_empty(bc->dirty_list));
381       entry = list_get_tail(bc->dirty_list);
382 
383       sos_kmutex_lock(& entry->lock, NULL);
384 
385       /* During blocking time, the block was transferred to the sync
386          list ! We'd better redo a complete iteration to choose
387          another block, if we want to keep this recently-accessed
388          block inside the cache */
389       if ((ENTRY_DIRTY == entry->state)
390           && (NULL == sos_hash_lookup(bc->lookup_table, & block_index)))
391         {
392           /* entry still dirty: transfer it to the free list */
393 
394           if (SOS_OK == blkcache_flush_entry(bc, entry, TRUE))
395               /* We can use it now ! */
396               break;
397         }
398 
399       sos_kmutex_unlock(& entry->lock);
400 
401       /* We can now go for another iteration because we blocked: maybe
402          the block was retrieved into the cache by someone else */
403     }
404 
405   /* Here we have a locked block that needs to be initialised */
406 
407   /* Remove it from the free list */
408   list_delete(bc->free_list, entry);
409 
410   /* Prepare it to be inserted in the dictionnary */
411   entry->block_index = block_index;
412 
413   /* Announce that we are preparing to add the block into the cache */
414   if (SOS_OK != sos_hash_insert(bc->lookup_table, entry))
415     SOS_FATAL_ERROR("Unexpected hash collision");
416 
417 
418   /* No need to do anything for a write-only block, simply mark it
419      dirty */
420   if (access_type == SOS_BLKCACHE_WRITE_ONLY)
421     {
422       entry->state = ENTRY_DIRTY;
423       list_add_head(bc->dirty_list, entry);
424     }
425 
426   /* Otherwise retrieve the block from disk NOW ! */
427   else
428     {
429       entry->state = ENTRY_SYNC;
430       list_add_head(bc->sync_list, entry);
431 
432       if (SOS_OK
433           != bc->operations->read_block(bc->blkdev_instance_custom_data,
434                                         entry->block_contents,
435                                         entry->block_index))
436         {
437           /* In case of failure, remove it from hash and return NULL */
438           list_delete(bc->sync_list, entry);
439           sos_hash_remove(bc->lookup_table, entry);
440           entry->state = ENTRY_FREE;
441           list_add_head(bc->free_list, entry);
442           sos_kmutex_unlock(& entry->lock);
443           return NULL;
444         }
445     }
446 
447   *block_contents = entry->block_contents;
448   return entry;
449 }
450 
451 
452 sos_ret_t
453 sos_blkcache_release_block(struct sos_block_cache * bc,
454                            struct sos_block_cache_entry * entry,
455                            sos_bool_t is_dirty,
456                            sos_bool_t force_flush)
457 {
458   /* Enforce the least recently used policy */
459   if (ENTRY_SYNC == entry->state)
460     {
461       list_delete(bc->sync_list, entry);
462       /* Eventually transfer the block to the dirty list */
463       if (is_dirty)
464         entry->state = ENTRY_DIRTY;
465     }
466   else
467     list_delete(bc->dirty_list, entry);
468 
469   if (ENTRY_SYNC == entry->state)
470     list_add_head(bc->sync_list, entry);
471   else
472     list_add_head(bc->dirty_list, entry);
473 
474   if ( (ENTRY_DIRTY == entry->state) && force_flush)
475     blkcache_flush_entry(bc, entry, FALSE);
476 
477   sos_kmutex_unlock(& entry->lock);
478   return SOS_OK;
479 }
480 
481 
482 sos_ret_t
483 sos_blkcache_flush(struct sos_block_cache * bc)
484 {
485   struct sos_block_cache_entry * entry;
486   while (NULL != (entry = list_get_head(bc->dirty_list)) )
487     {
488       sos_ret_t retval = SOS_OK;
489 
490       sos_kmutex_lock(& entry->lock, NULL);
491       if (ENTRY_DIRTY == entry->state)
492         retval = blkcache_flush_entry(bc, entry, FALSE);
493       sos_kmutex_unlock(& entry->lock);
494 
495       if (SOS_OK != retval)
496         return retval;
497     }
498 
499   return SOS_OK;
500 }

source navigation ] diff markup ] identifier search ] general search ]