// Memory-backed block device. // // Serves an entire SCHFS image out of RAM, so the system can boot and run a // shell with the physical disk untouched. That is what makes destructive work // on the real disk survivable: reformatting, running mkfs, or bringing up a new // on-disk format cannot take away the root you are running from. It is also the // first piece of the installer -- an install environment is exactly "a root in // RAM plus a disk you are allowed to destroy". // // The image is linked into the kernel as a binary blob (see the ramdisk_obj // rule in the Makefile) and is served in place: the blob lives in writable // kernel memory, so writes to the ramdisk simply modify it. They are lost on // reboot, which is the expected behaviour for this kind of root. // // Enabled by building with RAMROOT=1. Without it this file still compiles, the // device reports zero size, and the kernel boots from the IDE disk as before. // // The device is larger than the image. The image is only what the root holds, // with a little slack; past its end the device goes on to half of the memory // free at mount time, and those blocks are pages taken from kalloc the first // time each is written -- a block nobody has written reads as zeros and costs // nothing. SchFS grows the volume to the device's size when it mounts (see // grow_to in kernel/fs/schfs/vol.c), so `apk add` on a live system has as // much room as the machine has memory to give, rather than whatever empty // space was built into the ISO. The ISO used to carry 60 MB of zeros for it. // // Pages are not given back when SchFS frees a block: there is no discard. // A freed block is reused by the next write, so the cost is the high-water // mark, not a leak. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/fs.h" #include "fs/buf.h" #ifdef RAMDISK_ROOT // Supplied by `ld -r -b binary` over build/fs-ram.img, renamed by objcopy. extern uchar _binary_ramdisk_start[]; extern uchar _binary_ramdisk_size[]; #endif static uchar *rd_base; static uint64 rd_size; // bytes static uint rd_blocks; // rd_size / BSIZE: the image, served in place // Past the image: rd_dir[i] is a page of pointers, entry j the page holding // block rd_blocks + i*RD_PTRS + j, or 0 for one never written. 32768 pages of // 512 pointers reach 64 GB, which is as far as half of any machine this runs // on is likely to go, and costs 256 KB of pointers here. #define RD_PTRS (PGSIZE / sizeof(uchar *)) #define RD_DIRS 32768 static uchar **rd_dir[RD_DIRS]; static uint64 rd_total; // blocks, image and the rest; 0 until sized static uint64 rd_pages; // pages taken for blocks past the image static struct spinlock rd_lock; void ramdiskinit(void) { initlock(&rd_lock, "ramdisk"); #ifdef RAMDISK_ROOT rd_base = _binary_ramdisk_start; rd_size = (uint64)_binary_ramdisk_size; rd_blocks = (uint)(rd_size / BSIZE); if(rd_blocks == 0) panic("ramdiskinit: embedded image is empty"); // A truncated image is worth catching here rather than as a confusing // "invalid file system" panic later: the superblock lives in block 1. if(rd_blocks < 2) panic("ramdiskinit: embedded image smaller than a superblock"); cprintf("ramdisk: %u blocks (%u KB) at %p\n", rd_blocks, (uint)(rd_size / 1024), rd_base); #else rd_base = 0; rd_size = 0; rd_blocks = 0; #endif } int ramdisk_present(void) { return rd_blocks != 0; } // The device's size in blocks, fixed the first time anyone asks -- which is // SchFS mounting the root, after every page has reached the allocator. Asked // earlier, during ramdiskinit, the free count would be the first few // megabytes kinit1 handed over and nothing more. uint64 ramdisk_blocks(void) { if (rd_blocks == 0) return 0; acquire(&rd_lock); if (rd_total == 0) { uint64 extra = kalloc_free_bytes() / 2 / BSIZE; if (extra > (uint64)RD_DIRS * RD_PTRS) extra = (uint64)RD_DIRS * RD_PTRS; rd_total = rd_blocks + extra; cprintf("ramdisk: %u MB image, %u MB of memory to grow into\n", (uint)(rd_size >> 20), (uint)(extra * BSIZE >> 20)); } release(&rd_lock); return rd_total; } // The page behind a block past the image, made when `make` says so. static uchar * rd_page(uint64 blk, int make) { uint64 i = (blk - rd_blocks) / RD_PTRS, j = (blk - rd_blocks) % RD_PTRS; uchar *pg = 0; acquire(&rd_lock); if (rd_dir[i] == 0 && make) { if ((rd_dir[i] = (uchar **)kalloc()) != 0) memset(rd_dir[i], 0, PGSIZE); } if (rd_dir[i]) { pg = rd_dir[i][j]; if (pg == 0 && make && (pg = (uchar *)kalloc()) != 0) { rd_dir[i][j] = pg; rd_pages++; } } release(&rd_lock); if (make && pg == 0) panic("ramdisk: out of memory for the live root"); return pg; } // Sync buf with the in-memory image. Mirrors iderw()'s contract: on a read, // fill b->data and set B_VALID; on a write, copy out and clear B_DIRTY. void ramdiskrw(struct buf *b) { if(!holdingsleep(&b->lock)) panic("ramdiskrw: buf not locked"); if((b->flags & (B_VALID|B_DIRTY)) == B_VALID) panic("ramdiskrw: nothing to do"); if(rd_base == 0) panic("ramdiskrw: no ramdisk"); if(b->blockno >= rd_blocks){ if(b->blockno >= ramdisk_blocks()) panic("ramdiskrw: block out of range"); uchar *pg = rd_page(b->blockno, (b->flags & B_DIRTY) != 0); if(b->flags & B_DIRTY){ memmove(pg, b->data, BSIZE); b->flags &= ~B_DIRTY; } else if(pg){ memmove(b->data, pg, BSIZE); } else { memset(b->data, 0, BSIZE); // never written } b->flags |= B_VALID; return; } uchar *p = rd_base + (uint64)b->blockno * BSIZE; if(b->flags & B_DIRTY){ memmove(p, b->data, BSIZE); b->flags &= ~B_DIRTY; } else { memmove(b->data, p, BSIZE); } b->flags |= B_VALID; }