The Copy-on-Write (COW) feature implementation in ZuluSCSI firmware, allows read-only disk images to appear writable to the host system while preserving the original image file integrity. This is useful for settings where a machine is used for demonstration purposes.
Copy-on-Write is a storage optimization technique where:
- The original disk image remains untouched and read-only
- All write operations are redirected to a separate "dirty" file
- Reads seamlessly combine data from both the original and dirty files
- At each cold boot, the disk image resets to its original state
To enable COW mode, simply rename your disk image file to have a .cow extension:
mydisk.img→mydisk.cow- ZuluSCSI automatically creates a companion
mydisk.tmpfile for writes - The host system sees a fully writable disk
- The original data in
mydisk.cowis never modified
COW settings are integrated into the existing configuration system:
System Settings (zuluscsi.ini):
[SCSI]
CowBufferSize=4096 # Buffer size for copy operationsTo perform I/O operations while the main SCSI buffer is used, COW allocated a separate I/O buffer shared by all instances. Its default size is 4096 and is allocated only if COW is used and is never deallocated.
Device Settings (per-device sections):
[SCSI2]
CowBitmapSize=4096 # Bitmap size in bytes (affects granularity)The file is divided in groups of file-size/(CowBitmapSize*8) bytes. There is a bitmap for every COW device that maintains the clean/dirty size of every group. A larger bitmap size gives smaller groups, which enhance write performance If the device does not have enough free memory to allocate the CowBitMapSize, its size if halved until it fits.
The COW feature is included if ENABLE_COW is set to 1. It is set to one by default, apart from the ZuluSCSIv1_1_plus platform where ENABLE_COW is set to 0 due to lack of memory.
The COW implementation is built into the ImageBackingStore class with minimal impact on existing code:
Host SCSI Commands
↓
ImageBackingStore
↓
┌─────────────────────────┐
│ COW Mode Enabled? │
│ (filename ends .cow) │
└─────────────────────────┘
↓
┌─────────────────────────┐ ┌─────────────────────────┐
│ Read Operation │ │ Write Operation │
│ │ │ │
│ Check bitmap for each │ │ 1. Copy-on-write if │
│ group: │ │ group is clean │
│ • CLEAN → read original │ │ 2. Write to dirty file │
│ • DIRTY → read dirty │ │ 3. Mark group as dirty │
└─────────────────────────┘ └─────────────────────────┘
- Purpose: Track which disk regions have been modified
- Granularity: Configurable groups of sectors (default calculated from bitmap size)
- Storage: Bit array where 1 = dirty, 0 = clean
- Size: Configurable via
CowBitmapSizesetting (default 4096 bytes = 32768 groups)
- Group Size: Dynamically calculated:
total_sectors / (bitmap_size * 8) - Alignment: Operations must be sector-aligned
- Efficiency: Larger groups = less memory, more over-write; smaller groups = more memory, less over-write
- Original File: Opened read-only, never modified
- Dirty File: Created as an empty file only if doesn't already exists, same size as original
- Automatic Creation: Dirty file auto-created if missing or too small
In ImageBackingStore constructor, if filename ends with ".cow", the cow feature is activated.
All standard ImageBackingStore operations automatically route through COW when enabled:
read()→cow_read()write()→cow_write()seek()→ COW position trackingisWritable()→ Always true for COW mode
The original code relied on ImageBackingStore being trivialy copiable, which it isn't since it contains the bitmap pointer.
Placement New Pattern: To avoid re-setting every variable by hands, which is error prone, we use destructor + placement new:
void image_config_t::clear() {
this->~image_config_t(); // Proper cleanup
new (this) image_config_t(); // Fresh construction
}class ImageBackingStore {
// All copy/move operations completely disabled
ImageBackingStore(const ImageBackingStore&) = delete;
ImageBackingStore& operator=(const ImageBackingStore&) = delete;
ImageBackingStore(ImageBackingStore&&) = delete;
ImageBackingStore& operator=(ImageBackingStore&&) = delete;
};This prevents accidental expensive copies.
- Global
g_cow_buffershared across all COW instances - Allocated once, reused for all copy-on-write operations
- Configurable size via
CowBufferSizesetting
- Dirty file created only when COW mode is activated
- Uses existing
createImageFile()infrastructure - Sparse file allocation for efficiency
- At creation, the size of the various components are displayed in the log
- The copy-on-write overhead is tracked and displayed in the log every 1Mb written
| Setting | Default | Description |
|---|---|---|
CowBufferSize |
4096 | Buffer size for copy operations (bytes) |
| Setting | Default | Description |
|---|---|---|
CowBitmapSize |
4096 | Bitmap size in bytes (affects granularity) |