diff --git a/docs/locking.md b/docs/locking.md index ac4c648..1d236ef 100644 --- a/docs/locking.md +++ b/docs/locking.md @@ -61,10 +61,10 @@ Let us start with the FLUSH handler, and its helper functions: ```rust fn flush() { - RWSEM.down_write(); + down_write(RWSEM); // Populate the FLUSH state, while we are holding the locks err = prepare_cwbs(); - RWSEM.up_write(); + up_write(RWSEM); // After unlocking, WRITEs and DISCARDs are no longer blocked and // can go through, potentially re-dirtying the PosMap blocks @@ -74,13 +74,13 @@ fn flush() { // It is important to first wait for all CWB callbacks to finish. err = DEV.flush(); - RWSEM.down_write(); + down_write(RWSEM); // Some blocks might be marked clean, some might not; the overall FLUSH // operation is successful only if all CWBs were successful err = mark_blocks_clean(); clear(CWB_ERROR); clear(FLUSH_PENDING); - RWSEM.up_write(); + up_write(RWSEM); return err; } @@ -174,10 +174,12 @@ Here, we only describe the critical section, rather than the whole handler. ```rust fn write(lba) { - lsi = lba / 256; // 256 4-KiB blocks in a slice - block = lsi / 1024; // PosMap block this falls into + lsi = lba / 256; // There are 256 4-KiB blocks in a slice + block = lsi / 1024; // The PosMap block this falls into - RWSEM.down_write(); + // Take both locks + RWSEM.down_read(); + spin_lock(LOCK); psi = ENTRIES[LSI]; // If LSI is unmapped, sample a new one and insert in PosMap if (psi == 0xFFFFFFFF) { @@ -192,10 +194,55 @@ fn write(lba) { DIRTY[lsi] = true; SEQNUM[block]++; // Can wrap around } - RWSEM.up_write(); + spin_unlock(LOCK); + up_read(RWSEM); } ``` -We make sure not to increment the sequence number too many times (16384 times) while a FLUSH is executing; this way, the check `SNAP_SEQNUM[block] == SEQNUM[block]` is, as mentioned, sufficient for `mark_blocks_clean()` to conclude that the block was not re-dirtied. +We make sure not to increment the sequence number too many times (16384 times) while a FLUSH is executing; this way, the check `SNAP_SEQNUM[block] == SEQNUM[block]` is, as mentioned, sufficient for `mark_blocks_clean()` to conclude that the block was not re-dirtied. It is anyway overwhelmingly unlikely that the block gets re-dirtied 16384 times before its FLUSH can complete. + + +#### DISCARD + +The DISCARD's critical section is similar to the WRITE's, in that it dirties a position map block, and increments the sequence number. Just, it does not also act on the device, so it does not need to take the per-device `spinlock`. + +```rust +fn discard(lsi) { + block = lsi / 1024; + + RWSEM.down_read(); + spin_lock(LOCK); + psi = ENTRIES[LSI]; + // Unmap LSI + if (psi != 0xFFFFFFFF) { + // If there's a FLUSH executing, ensure we don't increment the block's + // sequence number too many times + if (FLUSH_PENDING[block] && (SNAP_SEQNUM[block] + 1 == SEQNUM[block])) + return -EAGAIN; // Just try again later, after the FLUSH finished + + ENTRIES[lsi] = 0xFFFFFFFF; + DIRTY[lsi] = true; + SEQNUM[block]++; // Can wrap around + } + spin_unlock(LOCK); + up_read(RWSEM); +} +``` + + +#### READ + +The READ handler only takes the `spinlock`, and not the `rwsem`. This allows for concurrency between the READ's and the FLUSH's critical sections: this is fine because READs only *read* the `entries` without writing anything, and because the FLUSH handler does not *write* to `entries`. + +```rust +fn read(lba) { + lsi = lba / 256; // There are 256 4-KiB blocks in a slice + block = lsi / 1024; // The PosMap block this falls into + + spin_lock(LOCK); + psi = ENTRIES[LSI]; + spin_unlock(LOCK); +} +```