AquilaOS documentation
How it works, and its code
Loading...
Searching...
No Matches
blkcache.c
Go to the documentation of this file.
1
25
26#include <core/system.h>
27#include <core/string.h>
28#include <core/arch.h>
29#include <mm/mm.h>
30#include <dev/blkcache.h>
31#include <aquila/errno.h>
32
33MALLOC_DEFINE(M_BLKCACHE, "blkcache", "block cache");
34
35#define SECTOR_SIZE 512
36#define BLK_SECTORS 8
37#define BLK_SIZE (BLK_SECTORS * SECTOR_SIZE)
38#define BLK_ALL 0xFF /* all the sectors of a block, as a mask */
39
40/* 8 MiB, in one allocation */
41#define CACHE_BLOCKS 2048
42#define CACHE_BUCKETS 1024
43
44/* the most blocks one command reads or writes */
45#define RUN_MAX 16
46
47/* the most dirty blocks before they are written, and how long a block may
48 * stay dirty */
49#define DIRTY_MAX (CACHE_BLOCKS / 2)
50#define DIRTY_AGE 5000000000ULL /* ns */
51
53struct blk {
54 void *owner; /* the device it is of; NULL while the slot is free */
55 uint64_t nr; /* the block's number: its first sector / BLK_SECTORS */
56 uint8_t valid, dirty; /* which of its sectors are valid, and dirty */
57 blkcache_io write; /* how its dirty sectors get to the disk */
58
59 struct blk *hnext; /* the next block in its hash chain */
60 struct blk *newer, *older; /* the list from most to least recently used */
61};
62
63static struct blk blks[CACHE_BLOCKS];
64static char *arena;
65
66static struct blk *buckets[CACHE_BUCKETS];
67static struct blk *newest, *oldest;
68static size_t used, dirty;
69
70/* when the oldest dirty block of the cache became dirty */
72
73/* how many writes there were: a read during which a write came doesn't
74 * cache what it read, since that may be older than what was written */
76
78static inline char *blk_data(struct blk *b)
79{
80 return arena + (b - blks) * BLK_SIZE;
81}
82
84static inline uint8_t sector_mask(size_t first, size_t n)
85{
86 return ((1U << n) - 1) << first;
87}
88
90static inline struct blk **bucket(void *owner, uint64_t nr)
91{
92 return &buckets[(nr ^ ((uintptr_t) owner >> 4)) % CACHE_BUCKETS];
93}
94
96static struct blk *lookup(void *owner, uint64_t nr)
97{
98 for (struct blk *b = *bucket(owner, nr); b; b = b->hnext)
99 if (b->owner == owner && b->nr == nr)
100 return b;
101
102 return NULL;
103}
104
106static void lru_unlink(struct blk *b)
107{
108 if (b->newer) b->newer->older = b->older; else newest = b->older;
109 if (b->older) b->older->newer = b->newer; else oldest = b->newer;
110 b->newer = b->older = NULL;
111}
112
114static void lru_push(struct blk *b)
115{
116 b->older = newest;
117 b->newer = NULL;
118
119 if (newest) newest->newer = b; else oldest = b;
120 newest = b;
121}
122
124static void touch(struct blk *b)
125{
126 if (b != newest) {
127 lru_unlink(b);
128 lru_push(b);
129 }
130}
131
133static void evict(struct blk *b)
134{
135 struct blk **p = bucket(b->owner, b->nr);
136
137 while (*p != b)
138 p = &(*p)->hnext;
139
140 *p = b->hnext;
141 lru_unlink(b);
142 b->owner = NULL;
143 --used;
144}
145
147static void make_dirty(struct blk *b, uint8_t mask, blkcache_io write)
148{
149 if (!b->dirty && !dirty++)
151
152 b->dirty |= mask;
153 b->write = write;
154}
155
157static void make_clean(struct blk *b, uint8_t mask)
158{
159 if (b->dirty && !(b->dirty &= ~mask))
160 --dirty;
161}
162
166static struct blk *block_new(void *owner, uint64_t nr)
167{
168 if (!arena && !(arena = kmalloc(CACHE_BLOCKS * BLK_SIZE, &M_BLKCACHE, 0)))
169 return NULL;
170
171 struct blk *b = NULL;
172
173 if (used < CACHE_BLOCKS) {
174 for (size_t i = 0; i < CACHE_BLOCKS && !b; ++i)
175 if (!blks[i].owner)
176 b = &blks[i];
177 } else {
178 /* the clean block that was used least recently */
179 for (struct blk *old = oldest; old && !b; old = old->newer)
180 if (!old->dirty)
181 b = old;
182
183 if (!b)
184 return NULL;
185
186 evict(b);
187 }
188
189 b->owner = owner;
190 b->nr = nr;
191 b->valid = b->dirty = 0;
192 b->hnext = *bucket(owner, nr);
193 *bucket(owner, nr) = b;
194 lru_push(b);
195 ++used;
196
197 return b;
198}
199
203static void prefault(void *buf, size_t size, int write)
204{
205 volatile char *p = buf, *end = (char *) buf + size;
206
207 for (; p < end; p = (volatile char *) PAGE_ALIGN((uintptr_t) p + PAGE_SIZE)) {
208 char c = *p;
209 if (write) *p = c;
210 }
211
212 if (size) {
213 char c = end[-1];
214 if (write) end[-1] = c;
215 }
216}
217
221static void overlap(uint64_t nr, uint64_t sector, size_t count,
222 size_t *first, size_t *n, size_t *off)
223{
224 uint64_t start = MAX(nr * BLK_SECTORS, sector);
225 uint64_t end = MIN((nr + 1) * BLK_SECTORS, sector + count);
226
227 *first = start - nr * BLK_SECTORS;
228 *n = end - start;
229 *off = start - sector;
230}
231
233static inline int full_dirty(struct blk *b)
234{
235 return b && b->dirty == BLK_ALL;
236}
237
241static int flush_block(struct blk *b)
242{
243 void *owner = b->owner;
245 uint64_t nr = b->nr;
246 int err = 0;
247
248 char *tmp = kmalloc(RUN_MAX * BLK_SIZE, &M_BLKCACHE, 0);
249 if (!tmp) return -ENOMEM;
250
251 if (full_dirty(b)) {
252 /* a run of wholly dirty blocks, written with one command */
253 size_t run = 0;
254 struct blk *r;
255
256 while (run < RUN_MAX && full_dirty(r = lookup(owner, nr + run)) && r->write == write) {
257 memcpy(tmp + run * BLK_SIZE, blk_data(r), BLK_SIZE);
259 ++run;
260 }
261
262 if ((err = write(owner, nr * BLK_SECTORS, run * BLK_SECTORS, tmp)) < 0) {
263 /* they aren't on the disk: dirty again, if still cached */
264 for (size_t i = 0; i < run; ++i)
265 if ((r = lookup(owner, nr + i)))
267 }
268 } else {
269 /* write each range of dirty sectors */
270 uint8_t mask = b->dirty;
271
272 for (size_t s = 0; s < BLK_SECTORS && !err; ) {
273 if (!(mask & (1 << s))) {
274 ++s;
275 continue;
276 }
277
278 size_t n = 0;
279 while (s + n < BLK_SECTORS && (mask & (1 << (s + n))))
280 ++n;
281
282 uint8_t m = sector_mask(s, n);
283 memcpy(tmp, blk_data(b) + s * SECTOR_SIZE, n * SECTOR_SIZE);
284 make_clean(b, m);
285
286 if ((err = write(owner, nr * BLK_SECTORS + s, n, tmp)) < 0) {
287 struct blk *r = lookup(owner, nr);
288 if (r)
289 make_dirty(r, m, write);
290 }
291
292 /* b may have left the cache while the write slept */
293 if (!(b = lookup(owner, nr)))
294 break;
295
296 s += n;
297 }
298 }
299
300 kfree(tmp);
301 return err < 0 ? err : 0;
302}
303
310{
311 int err = 0;
312
313 /* go in slot order; writing sleeps, so the cache may change meanwhile,
314 * and each block is looked at again */
315 for (size_t i = 0; i < CACHE_BLOCKS && dirty; ++i) {
316 struct blk *b = &blks[i];
317
318 if (!b->owner || !b->dirty)
319 continue;
320
321 /* start from the first block of its run of wholly dirty blocks */
322 if (full_dirty(b)) {
323 struct blk *prev;
324
325 for (size_t k = 0; k < RUN_MAX && b->nr && full_dirty(prev = lookup(b->owner, b->nr - 1)) &&
326 prev->write == b->write; ++k)
327 b = prev;
328 }
329
330 int e = flush_block(b);
331 if (e)
332 err = e;
333
334 /* the slot may hold more dirty sectors now: look at it again */
335 if (blks[i].owner && blks[i].dirty && !e)
336 --i;
337 }
338
339 return err;
340}
341
344static void flush_old(void)
345{
346 static int flushing;
347
348 if (dirty && !flushing && arch_rtime_ns() - dirty_since > DIRTY_AGE) {
349 flushing = 1;
351 flushing = 0;
352 }
353}
354
367ssize_t blkcache_read(void *owner, uint64_t sector, size_t count, void *buf,
368 uint64_t nsectors, blkcache_io read)
369{
370 char *out = buf;
371 uint64_t last = (sector + count - 1) / BLK_SECTORS;
372 size_t first, n, off;
373
374 if (!count)
375 return 0;
376
377 flush_old();
378 prefault(buf, count * SECTOR_SIZE, 1);
379
380 for (uint64_t nr = sector / BLK_SECTORS; nr <= last; ) {
381 struct blk *b = lookup(owner, nr);
382
383 overlap(nr, sector, count, &first, &n, &off);
384 uint8_t need = sector_mask(first, n);
385
386 if (b && (b->valid & need) == need) {
387 memcpy(out + off * SECTOR_SIZE, blk_data(b) + first * SECTOR_SIZE, n * SECTOR_SIZE);
388 touch(b);
389 ++nr;
390 continue;
391 }
392
393 /* read the blocks from here on that miss sectors, at once */
394 size_t run = 1;
395 while (nr + run <= last && run < RUN_MAX) {
396 struct blk *r = lookup(owner, nr + run);
397 size_t f, k, o;
398
399 overlap(nr + run, sector, count, &f, &k, &o);
400
401 if (r && (r->valid & sector_mask(f, k)) == sector_mask(f, k))
402 break;
403
404 ++run;
405 }
406
407 uint64_t start = nr * BLK_SECTORS;
408 size_t sectors = run * BLK_SECTORS;
409
410 /* not past the end of the disk, if it is known */
411 if (nsectors && start + sectors > nsectors)
412 sectors = start < nsectors ? nsectors - start : 0;
413
414 if (!sectors)
415 return -EIO;
416
417 char *tmp = kmalloc(sectors * SECTOR_SIZE, &M_BLKCACHE, 0);
418
419 if (!tmp)
420 return -ENOMEM;
421
422 uint64_t w = writes;
423 ssize_t err = read(owner, start, sectors, tmp);
424
425 if (err < 0) {
426 kfree(tmp);
427 return err;
428 }
429
430 for (size_t i = 0; i < run; ++i) {
431 size_t have = sectors > i * BLK_SECTORS ? MIN(BLK_SECTORS, sectors - i * BLK_SECTORS) : 0;
432 char *data = tmp + i * BLK_SIZE;
433
434 overlap(nr + i, sector, count, &first, &n, &off);
435
436 if (first + n > have) {
437 kfree(tmp);
438 return -EIO;
439 }
440
441 /* if a block was written meanwhile, the cache has newer
442 * sectors than the disk had, and only the others are filled
443 * in. A block that wasn't cached is only cached if nothing
444 * was written meanwhile. */
445 struct blk *r = lookup(owner, nr + i);
446
447 if (!r && w == writes && (r = block_new(owner, nr + i)))
448 r->valid = 0;
449
450 if (r) {
451 for (size_t s = 0; s < have; ++s) {
452 if (!(r->valid & (1 << s))) {
453 memcpy(blk_data(r) + s * SECTOR_SIZE, data + s * SECTOR_SIZE, SECTOR_SIZE);
454 r->valid |= 1 << s;
455 }
456 }
457
458 memcpy(out + off * SECTOR_SIZE, blk_data(r) + first * SECTOR_SIZE, n * SECTOR_SIZE);
459 touch(r);
460 } else {
461 memcpy(out + off * SECTOR_SIZE, data + first * SECTOR_SIZE, n * SECTOR_SIZE);
462 }
463 }
464
465 kfree(tmp);
466 nr += run;
467 }
468
469 return 0;
470}
471
484ssize_t blkcache_write(void *owner, uint64_t sector, size_t count, void *buf,
486{
487 char *in = buf;
488 size_t first, n, off;
489 ssize_t err = 0;
490
491 if (!count)
492 return 0;
493
494 flush_old();
495 prefault(buf, count * SECTOR_SIZE, 0);
496
497 uint64_t last = (sector + count - 1) / BLK_SECTORS;
498
499 for (uint64_t nr = sector / BLK_SECTORS; nr <= last; ++nr) {
500 overlap(nr, sector, count, &first, &n, &off);
501
502 struct blk *b = lookup(owner, nr);
503
504 if (!b && !(b = block_new(owner, nr))) {
505 /* every block is dirty: write them, and then there is room */
507 b = lookup(owner, nr);
508
509 if (!b && !(b = block_new(owner, nr))) {
510 /* no memory for the cache: write straight to the disk */
511 char *tmp = kmalloc(n * SECTOR_SIZE, &M_BLKCACHE, 0);
512
513 if (!tmp)
514 return -ENOMEM;
515
516 memcpy(tmp, in + off * SECTOR_SIZE, n * SECTOR_SIZE);
517 err = write(owner, nr * BLK_SECTORS + first, n, tmp);
518 kfree(tmp);
519
520 if (err < 0)
521 return err;
522
523 continue;
524 }
525 }
526
527 uint8_t m = sector_mask(first, n);
528
529 memcpy(blk_data(b) + first * SECTOR_SIZE, in + off * SECTOR_SIZE, n * SECTOR_SIZE);
530 b->valid |= m;
531 make_dirty(b, m, write);
532 touch(b);
533 }
534
535 ++writes;
536
537 if (dirty >= DIRTY_MAX)
538 err = blkcache_flush();
539
540 return err < 0 ? err : 0;
541}
#define PAGE_SIZE
Definition system.h:7
static struct blk * buckets[CACHE_BUCKETS]
Definition blkcache.c:66
static struct blk * oldest
Definition blkcache.c:67
static int flush_block(struct blk *b)
Writes the dirty sectors of b to the disk (with the wholly dirty blocks right after it,...
Definition blkcache.c:241
static char * blk_data(struct blk *b)
Returns a block's data.
Definition blkcache.c:78
#define RUN_MAX
Definition blkcache.c:45
static void overlap(uint64_t nr, uint64_t sector, size_t count, size_t *first, size_t *n, size_t *off)
Finds the sectors of block nr that are in the request [sector, sector + count): n of them,...
Definition blkcache.c:221
static void flush_old(void)
Writes the dirty blocks to the disk if the oldest one is old enough (DIRTY_AGE).
Definition blkcache.c:344
static uint8_t sector_mask(size_t first, size_t n)
Returns the mask of n sectors from sector first of a block.
Definition blkcache.c:84
static uint64_t writes
Definition blkcache.c:75
ssize_t blkcache_read(void *owner, uint64_t sector, size_t count, void *buf, uint64_t nsectors, blkcache_io read)
Reads sectors of a disk through the cache.
Definition blkcache.c:367
static void touch(struct blk *b)
Marks a block as just used.
Definition blkcache.c:124
ssize_t blkcache_write(void *owner, uint64_t sector, size_t count, void *buf, blkcache_io write)
Writes sectors of a disk through the cache: they stay dirty in it until it is flushed.
Definition blkcache.c:484
static struct blk ** bucket(void *owner, uint64_t nr)
Returns the hash chain that block nr of owner is on.
Definition blkcache.c:90
#define BLK_SIZE
Definition blkcache.c:37
#define CACHE_BUCKETS
Definition blkcache.c:42
static void make_dirty(struct blk *b, uint8_t mask, blkcache_io write)
Marks sectors of a block dirty, to be written with write.
Definition blkcache.c:147
static int full_dirty(struct blk *b)
Returns whether a block exists and all its sectors are dirty.
Definition blkcache.c:233
static void make_clean(struct blk *b, uint8_t mask)
Marks sectors of a block clean.
Definition blkcache.c:157
static struct blk blks[CACHE_BLOCKS]
Definition blkcache.c:63
static struct blk * newest
Definition blkcache.c:67
#define CACHE_BLOCKS
Definition blkcache.c:41
#define DIRTY_AGE
Definition blkcache.c:50
#define BLK_ALL
Definition blkcache.c:38
#define SECTOR_SIZE
Definition blkcache.c:35
static size_t dirty
Definition blkcache.c:68
static void prefault(void *buf, size_t size, int write)
Maps every page of a buffer, by touching them: a process's buffer isn't mapped until it is used.
Definition blkcache.c:203
int blkcache_flush(void)
Writes every dirty block of the cache to its disk.
Definition blkcache.c:309
#define BLK_SECTORS
Definition blkcache.c:36
static struct blk * block_new(void *owner, uint64_t nr)
Takes a block for block nr of owner, with nothing valid in it: a free slot, or the clean block used l...
Definition blkcache.c:166
static void lru_unlink(struct blk *b)
Takes a block off the list of blocks by use.
Definition blkcache.c:106
static char * arena
Definition blkcache.c:64
static uint64_t dirty_since
Definition blkcache.c:71
static struct blk * lookup(void *owner, uint64_t nr)
Returns block nr of owner, or NULL if it isn't cached.
Definition blkcache.c:96
static size_t used
Definition blkcache.c:68
static void evict(struct blk *b)
Takes a clean block out of the cache.
Definition blkcache.c:133
#define DIRTY_MAX
Definition blkcache.c:49
static void lru_push(struct blk *b)
Puts a block at the head of the list: the most recently used.
Definition blkcache.c:114
ssize_t(*) blkcache_io(void *owner, uint64_t sector, size_t count, void *buf)
Definition blkcache.h:10
#define MIN(a, b)
Definition system.h:11
#define MAX(a, b)
Definition system.h:10
#define PAGE_ALIGN(ptr)
Definition mm.h:9
#define MALLOC_DEFINE(type, _name, _desc)
Definition kvmem.h:30
void * kmalloc(size_t size, struct malloc_type *type, int flags)
Allocates memory for one of the kernel's structures.
Definition kvmem.c:123
void kfree(void *)
Frees an object that kmalloc() gave.
Definition kvmem.c:146
void * memcpy(void *dst, const void *src, size_t n)
Copies n bytes; dst and src must not overlap.
Definition libc.c:11
#define NULL
Definition stddef.h:27
__SIZE_TYPE__ uintptr_t
Definition stddef.h:12
unsigned char uint8_t
Definition stddef.h:20
unsigned long long int uint64_t
Definition stddef.h:23
A block of the cache.
Definition blkcache.c:53
struct blk * hnext
Definition blkcache.c:59
struct blk * newer
Definition blkcache.c:60
uint64_t nr
Definition blkcache.c:55
uint8_t valid
Definition blkcache.c:56
struct blk * older
Definition blkcache.c:60
void * owner
Definition blkcache.c:54
blkcache_io write
Definition blkcache.c:57
uint8_t dirty
Definition blkcache.c:56
uint64_t arch_rtime_ns(void)
Reads the monotonic clock, which starts at boot.
Definition timer.c:56
long int ssize_t
Definition types.h:12