blob: 0679aeddc6b553cd857a198aeecd7b0fd53565e5 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2007 Travis Geiselbrecht
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23#include <list.h>
24#include <stdlib.h>
25#include <assert.h>
26#include <string.h>
27#include <sys/types.h>
28#include <debug.h>
29#include <trace.h>
30#include <lib/bcache.h>
31#include <lib/bio.h>
32
33#define LOCAL_TRACE 0
34
35struct bcache_block {
36 struct list_node node;
37 bnum_t blocknum;
38 int ref_count;
39 bool is_dirty;
40 void *ptr;
41};
42
43struct bcache_stats {
44 uint32_t hits;
45 uint32_t depth;
46 uint32_t misses;
47 uint32_t reads;
48 uint32_t writes;
49};
50
51struct bcache {
52 bdev_t *dev;
53 size_t block_size;
54 int count;
55 struct bcache_stats stats;
56
57 struct list_node free_list;
58 struct list_node lru_list;
59
60 struct bcache_block *blocks;
61};
62
63bcache_t bcache_create(bdev_t *dev, size_t block_size, int block_count)
64{
65 struct bcache *cache;
66
67 cache = malloc(sizeof(struct bcache));
68
69 cache->dev = dev;
70 cache->block_size = block_size;
71 cache->count = block_count;
72 memset(&cache->stats, 0, sizeof(cache->stats));
73
74 list_initialize(&cache->free_list);
75 list_initialize(&cache->lru_list);
76
77 cache->blocks = malloc(sizeof(struct bcache_block) * block_count);
78 int i;
79 for (i=0; i < block_count; i++) {
80 cache->blocks[i].ref_count = 0;
81 cache->blocks[i].is_dirty = false;
82 cache->blocks[i].ptr = malloc(block_size);
83 // add to the free list
84 list_add_head(&cache->free_list, &cache->blocks[i].node);
85 }
86
87 return (bcache_t)cache;
88}
89
90static int flush_block(struct bcache *cache, struct bcache_block *block)
91{
92 int rc;
93
94 rc = bio_write(cache->dev, block->ptr,
95 (off_t)block->blocknum * cache->block_size,
96 cache->block_size);
97 if (rc < 0)
98 goto exit;
99
100 block->is_dirty = false;
101 cache->stats.writes++;
102 rc = 0;
103exit:
104 return (rc);
105}
106
107void bcache_destroy(bcache_t _cache)
108{
109 struct bcache *cache = _cache;
110 int i;
111
112 for (i=0; i < cache->count; i++) {
113 DEBUG_ASSERT(cache->blocks[i].ref_count == 0);
114
115 if (cache->blocks[i].is_dirty)
116 printf("warning: freeing dirty block %u\n",
117 cache->blocks[i].blocknum);
118
119 free(cache->blocks[i].ptr);
120 }
121
122 free(cache);
123}
124
125/* find a block if it's already present */
126static struct bcache_block *find_block(struct bcache *cache, uint blocknum)
127{
128 uint32_t depth = 0;
129 struct bcache_block *block;
130
131 LTRACEF("num %u\n", blocknum);
132
133 block = NULL;
134 list_for_every_entry(&cache->lru_list, block, struct bcache_block, node) {
135 LTRACEF("looking at entry %p, num %u\n", block, block->blocknum);
136 depth++;
137
138 if (block->blocknum == blocknum) {
139 list_delete(&block->node);
140 list_add_tail(&cache->lru_list, &block->node);
141 cache->stats.hits++;
142 cache->stats.depth += depth;
143 return block;
144 }
145 }
146
147 cache->stats.misses++;
148 return NULL;
149}
150
151/* allocate a new block */
152static struct bcache_block *alloc_block(struct bcache *cache)
153{
154 int err;
155 struct bcache_block *block;
156
157 /* pop one off the free list if it's present */
158 block = list_remove_head_type(&cache->free_list, struct bcache_block, node);
159 if (block) {
160 block->ref_count = 0;
161 list_add_tail(&cache->lru_list, &block->node);
162 LTRACEF("found block %p on free list\n", block);
163 return block;
164 }
165
166 /* walk the lru, looking for a free block */
167 list_for_every_entry(&cache->lru_list, block, struct bcache_block, node) {
168 LTRACEF("looking at %p, num %u\n", block, block->blocknum);
169 if (block->ref_count == 0) {
170 if (block->is_dirty) {
171 err = flush_block(cache, block);
172 if (err)
173 return NULL;
174 }
175
176 // add it to the tail of the lru
177 list_delete(&block->node);
178 list_add_tail(&cache->lru_list, &block->node);
179 return block;
180 }
181 }
182
183 return NULL;
184}
185
186static struct bcache_block *find_or_fill_block(struct bcache *cache, uint blocknum)
187{
188 int err;
189
190 LTRACEF("block %u\n", blocknum);
191
192 /* see if it's already in the cache */
193 struct bcache_block *block = find_block(cache, blocknum);
194 if (block == NULL) {
195 LTRACEF("wasn't allocated\n");
196
197 /* allocate a new block and fill it */
198 block = alloc_block(cache);
199 DEBUG_ASSERT(block);
200
201 LTRACEF("wasn't allocated, new block %p\n", block);
202
203 block->blocknum = blocknum;
204 err = bio_read(cache->dev, block->ptr, (off_t)blocknum * cache->block_size, cache->block_size);
205 if (err < 0) {
206 /* free the block, return an error */
207 list_add_tail(&cache->free_list, &block->node);
208 return NULL;
209 }
210
211 cache->stats.reads++;
212 }
213
214 DEBUG_ASSERT(block->blocknum == blocknum);
215
216 return block;
217}
218
219int bcache_read_block(bcache_t _cache, void *buf, uint blocknum)
220{
221 struct bcache *cache = _cache;
222
223 LTRACEF("buf %p, blocknum %u\n", buf, blocknum);
224
225 struct bcache_block *block = find_or_fill_block(cache, blocknum);
226 if (block == NULL) {
227 /* error */
228 return -1;
229 }
230
231 memcpy(buf, block->ptr, cache->block_size);
232 return 0;
233}
234
235int bcache_get_block(bcache_t _cache, void **ptr, uint blocknum)
236{
237 struct bcache *cache = _cache;
238
239 LTRACEF("ptr %p, blocknum %u\n", ptr, blocknum);
240
241 DEBUG_ASSERT(ptr);
242
243 struct bcache_block *block = find_or_fill_block(cache, blocknum);
244 if (block == NULL) {
245 /* error */
246 return -1;
247 }
248
249 /* increment the ref count to keep it from being freed */
250 block->ref_count++;
251 *ptr = block->ptr;
252
253 return 0;
254}
255
256int bcache_put_block(bcache_t _cache, uint blocknum)
257{
258 struct bcache *cache = _cache;
259
260 LTRACEF("blocknum %u\n", blocknum);
261
262 struct bcache_block *block = find_block(cache, blocknum);
263
264 /* be pretty hard on the caller for now */
265 DEBUG_ASSERT(block);
266 DEBUG_ASSERT(block->ref_count > 0);
267
268 block->ref_count--;
269
270 return 0;
271}
272
273int bcache_mark_block_dirty(bcache_t priv, uint blocknum)
274{
275 int err;
276 struct bcache *cache = priv;
277 struct bcache_block *block;
278
279 block = find_block(cache, blocknum);
280 if (!block) {
281 err = -1;
282 goto exit;
283 }
284
285 block->is_dirty = true;
286 err = 0;
287exit:
288 return (err);
289}
290
291int bcache_zero_block(bcache_t priv, uint blocknum)
292{
293 int err;
294 struct bcache *cache = priv;
295 struct bcache_block *block;
296
297 block = find_block(cache, blocknum);
298 if (!block) {
299 block = alloc_block(cache);
300 if (!block) {
301 err = -1;
302 goto exit;
303 }
304
305 block->blocknum = blocknum;
306 }
307
308 memset(block->ptr, 0, cache->block_size);
309 block->is_dirty = true;
310 err = 0;
311exit:
312 return (err);
313}
314
315int bcache_flush(bcache_t priv)
316{
317 int err;
318 struct bcache *cache = priv;
319 struct bcache_block *block;
320
321 list_for_every_entry(&cache->lru_list, block, struct bcache_block, node) {
322 if (block->is_dirty) {
323 err = flush_block(cache, block);
324 if (err)
325 goto exit;
326 }
327 }
328
329 err = 0;
330exit:
331 return (err);
332}
333
334void bcache_dump(bcache_t priv, const char *name)
335{
336 uint32_t finds;
337 struct bcache *cache = priv;
338
339 finds = cache->stats.hits + cache->stats.misses;
340
341 printf("%s: hits=%u(%u%%) depth=%u misses=%u(%u%%) reads=%u writes=%u\n",
342 name,
343 cache->stats.hits,
344 finds ? (cache->stats.hits * 100) / finds : 0,
345 cache->stats.hits ? cache->stats.depth / cache->stats.hits : 0,
346 cache->stats.misses,
347 finds ? (cache->stats.misses * 100) / finds : 0,
348 cache->stats.reads,
349 cache->stats.writes);
350}