Merge branch 'riscv-bcache' into riscv
This commit is contained in:
commit
62ece4b09e
36
kernel/bio.c
36
kernel/bio.c
|
@ -12,11 +12,7 @@
|
||||||
// * Do not use the buffer after calling brelse.
|
// * Do not use the buffer after calling brelse.
|
||||||
// * Only one process at a time can use a buffer,
|
// * Only one process at a time can use a buffer,
|
||||||
// so do not keep them longer than necessary.
|
// so do not keep them longer than necessary.
|
||||||
//
|
|
||||||
// The implementation uses two state flags internally:
|
|
||||||
// * B_VALID: the buffer data has been read from the disk.
|
|
||||||
// * B_DIRTY: the buffer data has been modified
|
|
||||||
// and needs to be written to disk.
|
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "param.h"
|
#include "param.h"
|
||||||
|
@ -76,13 +72,11 @@ bget(uint dev, uint blockno)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not cached; recycle an unused buffer.
|
// Not cached; recycle an unused buffer.
|
||||||
// Even if refcnt==0, B_DIRTY indicates a buffer is in use
|
|
||||||
// because log.c has modified it but not yet committed it.
|
|
||||||
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
|
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
|
||||||
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
|
if(b->refcnt == 0) {
|
||||||
b->dev = dev;
|
b->dev = dev;
|
||||||
b->blockno = blockno;
|
b->blockno = blockno;
|
||||||
b->flags = 0;
|
b->valid = 0;
|
||||||
b->refcnt = 1;
|
b->refcnt = 1;
|
||||||
release(&bcache.lock);
|
release(&bcache.lock);
|
||||||
acquiresleep(&b->lock);
|
acquiresleep(&b->lock);
|
||||||
|
@ -99,8 +93,9 @@ bread(uint dev, uint blockno)
|
||||||
struct buf *b;
|
struct buf *b;
|
||||||
|
|
||||||
b = bget(dev, blockno);
|
b = bget(dev, blockno);
|
||||||
if((b->flags & B_VALID) == 0) {
|
if(!b->valid) {
|
||||||
virtio_disk_rw(b);
|
virtio_disk_rw(b, 0);
|
||||||
|
b->valid = 1;
|
||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
@ -111,8 +106,7 @@ bwrite(struct buf *b)
|
||||||
{
|
{
|
||||||
if(!holdingsleep(&b->lock))
|
if(!holdingsleep(&b->lock))
|
||||||
panic("bwrite");
|
panic("bwrite");
|
||||||
b->flags |= B_DIRTY;
|
virtio_disk_rw(b, 1);
|
||||||
virtio_disk_rw(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release a locked buffer.
|
// Release a locked buffer.
|
||||||
|
@ -139,3 +133,19 @@ brelse(struct buf *b)
|
||||||
|
|
||||||
release(&bcache.lock);
|
release(&bcache.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bpin(struct buf *b) {
|
||||||
|
acquire(&bcache.lock);
|
||||||
|
b->refcnt++;
|
||||||
|
release(&bcache.lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bunpin(struct buf *b) {
|
||||||
|
acquire(&bcache.lock);
|
||||||
|
b->refcnt--;
|
||||||
|
release(&bcache.lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
struct buf {
|
struct buf {
|
||||||
int flags;
|
int valid; // has data been read from disk?
|
||||||
|
int disk; // does disk "own" buf?
|
||||||
uint dev;
|
uint dev;
|
||||||
uint blockno;
|
uint blockno;
|
||||||
struct sleeplock lock;
|
struct sleeplock lock;
|
||||||
|
@ -9,6 +10,4 @@ struct buf {
|
||||||
struct buf *qnext; // disk queue
|
struct buf *qnext; // disk queue
|
||||||
uchar data[BSIZE];
|
uchar data[BSIZE];
|
||||||
};
|
};
|
||||||
#define B_VALID 0x2 // buffer has been read from disk
|
|
||||||
#define B_DIRTY 0x4 // buffer needs to be written to disk
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ void binit(void);
|
||||||
struct buf* bread(uint, uint);
|
struct buf* bread(uint, uint);
|
||||||
void brelse(struct buf*);
|
void brelse(struct buf*);
|
||||||
void bwrite(struct buf*);
|
void bwrite(struct buf*);
|
||||||
|
void bpin(struct buf*);
|
||||||
|
void bunpin(struct buf*);
|
||||||
|
|
||||||
// console.c
|
// console.c
|
||||||
void consoleinit(void);
|
void consoleinit(void);
|
||||||
|
@ -177,7 +179,7 @@ void plic_complete(int);
|
||||||
|
|
||||||
// virtio_disk.c
|
// virtio_disk.c
|
||||||
void virtio_disk_init(void);
|
void virtio_disk_init(void);
|
||||||
void virtio_disk_rw(struct buf *);
|
void virtio_disk_rw(struct buf *, int);
|
||||||
void virtio_disk_intr();
|
void virtio_disk_intr();
|
||||||
|
|
||||||
// number of elements in fixed-size array
|
// number of elements in fixed-size array
|
||||||
|
|
|
@ -77,6 +77,7 @@ install_trans(void)
|
||||||
struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst
|
struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst
|
||||||
memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
|
memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
|
||||||
bwrite(dbuf); // write dst to disk
|
bwrite(dbuf); // write dst to disk
|
||||||
|
bunpin(dbuf);
|
||||||
brelse(lbuf);
|
brelse(lbuf);
|
||||||
brelse(dbuf);
|
brelse(dbuf);
|
||||||
}
|
}
|
||||||
|
@ -203,7 +204,7 @@ commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caller has modified b->data and is done with the buffer.
|
// Caller has modified b->data and is done with the buffer.
|
||||||
// Record the block number and pin in the cache with B_DIRTY.
|
// Record the block number and pin in the cache by increasing refcnt.
|
||||||
// commit()/write_log() will do the disk write.
|
// commit()/write_log() will do the disk write.
|
||||||
//
|
//
|
||||||
// log_write() replaces bwrite(); a typical use is:
|
// log_write() replaces bwrite(); a typical use is:
|
||||||
|
@ -227,9 +228,10 @@ log_write(struct buf *b)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
log.lh.block[i] = b->blockno;
|
log.lh.block[i] = b->blockno;
|
||||||
if (i == log.lh.n)
|
if (i == log.lh.n) { // Add new block to log?
|
||||||
|
bpin(b);
|
||||||
log.lh.n++;
|
log.lh.n++;
|
||||||
b->flags |= B_DIRTY; // prevent eviction
|
}
|
||||||
release(&log.lock);
|
release(&log.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ alloc3_desc(int *idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
virtio_disk_rw(struct buf *b)
|
virtio_disk_rw(struct buf *b, int write)
|
||||||
{
|
{
|
||||||
uint64 sector = b->blockno * (BSIZE / 512);
|
uint64 sector = b->blockno * (BSIZE / 512);
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ virtio_disk_rw(struct buf *b)
|
||||||
uint64 sector;
|
uint64 sector;
|
||||||
} buf0;
|
} buf0;
|
||||||
|
|
||||||
if(b->flags & B_DIRTY)
|
if(write)
|
||||||
buf0.type = VIRTIO_BLK_T_OUT; // write the disk
|
buf0.type = VIRTIO_BLK_T_OUT; // write the disk
|
||||||
else
|
else
|
||||||
buf0.type = VIRTIO_BLK_T_IN; // read the disk
|
buf0.type = VIRTIO_BLK_T_IN; // read the disk
|
||||||
|
@ -208,7 +208,7 @@ virtio_disk_rw(struct buf *b)
|
||||||
|
|
||||||
desc[idx[1]].addr = (uint64) b->data;
|
desc[idx[1]].addr = (uint64) b->data;
|
||||||
desc[idx[1]].len = BSIZE;
|
desc[idx[1]].len = BSIZE;
|
||||||
if(b->flags & B_DIRTY)
|
if(write)
|
||||||
desc[idx[1]].flags = 0; // device reads b->data
|
desc[idx[1]].flags = 0; // device reads b->data
|
||||||
else
|
else
|
||||||
desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
|
desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
|
||||||
|
@ -222,6 +222,7 @@ virtio_disk_rw(struct buf *b)
|
||||||
desc[idx[2]].next = 0;
|
desc[idx[2]].next = 0;
|
||||||
|
|
||||||
// record struct buf for virtio_disk_intr().
|
// record struct buf for virtio_disk_intr().
|
||||||
|
b->disk = 1;
|
||||||
info[idx[0]].b = b;
|
info[idx[0]].b = b;
|
||||||
|
|
||||||
// avail[0] is flags
|
// avail[0] is flags
|
||||||
|
@ -235,10 +236,13 @@ virtio_disk_rw(struct buf *b)
|
||||||
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
|
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
|
||||||
|
|
||||||
// Wait for virtio_disk_intr() to say request has finished.
|
// Wait for virtio_disk_intr() to say request has finished.
|
||||||
while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
|
while(b->disk == 1) {
|
||||||
sleep(b, &vdisk_lock);
|
sleep(b, &vdisk_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info[idx[0]].b = 0;
|
||||||
|
free_chain(idx[0]);
|
||||||
|
|
||||||
release(&vdisk_lock);
|
release(&vdisk_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,15 +256,10 @@ virtio_disk_intr()
|
||||||
|
|
||||||
if(info[id].status != 0)
|
if(info[id].status != 0)
|
||||||
panic("virtio_disk_intr status");
|
panic("virtio_disk_intr status");
|
||||||
|
|
||||||
info[id].b->flags |= B_VALID;
|
info[id].b->disk = 0; // disk is done with buf
|
||||||
info[id].b->flags &= ~B_DIRTY;
|
|
||||||
|
|
||||||
wakeup(info[id].b);
|
wakeup(info[id].b);
|
||||||
|
|
||||||
info[id].b = 0;
|
|
||||||
free_chain(id);
|
|
||||||
|
|
||||||
used_idx = (used_idx + 1) % NUM;
|
used_idx = (used_idx + 1) % NUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ workloads.
|
||||||
|
|
||||||
<p>Run usertests to see if you don't break anything.
|
<p>Run usertests to see if you don't break anything.
|
||||||
|
|
||||||
<h2>Lock-free bcache lookup</h2>
|
<h2>More scalabale bcache lookup</h2>
|
||||||
|
|
||||||
|
|
||||||
<p>Several processes reading different files repeatedly will
|
<p>Several processes reading different files repeatedly will
|
||||||
|
@ -98,7 +98,7 @@ workloads.
|
||||||
and run test0 from bcachetest and you will see "!"s.
|
and run test0 from bcachetest and you will see "!"s.
|
||||||
|
|
||||||
<p>Modify <tt>bget</tt> so that a lookup for a buffer that is in the
|
<p>Modify <tt>bget</tt> so that a lookup for a buffer that is in the
|
||||||
bcache doesn't need to acquire <tt>bcache.lock</tt>. This more
|
bcache doesn't need to acquire <tt>bcache.lock</tt>. This is more
|
||||||
tricky than the kalloc assignment, because bcache buffers are truly
|
tricky than the kalloc assignment, because bcache buffers are truly
|
||||||
shared among processes. You must maintain the invariant that a
|
shared among processes. You must maintain the invariant that a
|
||||||
buffer is only once in memory.
|
buffer is only once in memory.
|
||||||
|
@ -116,15 +116,33 @@ against, including:
|
||||||
|
|
||||||
<p>A challenge is testing whether you code is still correct. One way
|
<p>A challenge is testing whether you code is still correct. One way
|
||||||
to do is to artificially delay certain operations
|
to do is to artificially delay certain operations
|
||||||
using <tt>sleepticks</tt>.
|
using <tt>sleepticks</tt>. <tt>test1</tt> trashes the buffer cache
|
||||||
|
and exercises more code paths.
|
||||||
|
|
||||||
<p>Here are some hints:
|
<p>Here are some hints:
|
||||||
<ul>
|
<ul>
|
||||||
<li> Use an atomic increment instruction for incrementing and
|
<li>Read the description of buffer cache in the xv6 book (Section 7.2).
|
||||||
decrementing <tt>b->ref</tt> (see <tt>__sync_fetch_and_add() and
|
<li>Use a simple design: i.e., don't design a lock-free implementation.
|
||||||
related primitives</tt>)
|
<li>Use a simple hash table with locks per bucket.
|
||||||
<li>Don't walk the <tt>bcache.head</tt> list to find a buffer
|
<li>Searching in hash table for a buffer and allocating an entry
|
||||||
|
for that buffer when the buffer is not found must be atomic.
|
||||||
|
<li>It is fine to acquire <tt>bcache.lock</tt> in <tt>brelse</tt>
|
||||||
|
to update the LRU/MRU list.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p>Check that your implementation has less contention
|
||||||
|
on <tt>test0</tt>
|
||||||
|
|
||||||
|
<p>Make sure your implementation passes bcachetest and usertests.
|
||||||
|
|
||||||
|
<p>Optional:
|
||||||
|
<ul>
|
||||||
|
<li>make the buffer cache more scalable (e.g., avoid taking
|
||||||
|
out <tt>bcache.lock</tt> on <tt>brelse</tt>).
|
||||||
|
<li>make lookup lock-free (Hint: use gcc's <tt>__sync_*</tt>
|
||||||
|
functions.) How do you convince yourself that your implementation is correct?
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue