The last three phases of the filesystem, which were written together and
touch the same files throughout -- kmount.c, objw.c and vfs.c are each
rewritten by all three -- so they land as one commit rather than three
that could not be built separately.
Durability. A transaction that reaches the platter out of order is worse
than one that never reached it: the uberblock can name a tree whose
blocks are still in the drive's write cache. bdirty/bflush give the
buffer layer an explicit barrier, ideflush issues ATA FLUSH CACHE and
usb_storage_flush the SCSI SYNCHRONIZE CACHE that means the same thing,
and a commit now waits for the data before it writes the root that
points at it. The uberblock groups are read newest-first with fallback,
so a volume whose newest roots are torn mounts at an older one and says
which generations were lost.
Reflink. Extents carry a refcount, so two objects can name the same
blocks and neither frees them while the other holds them. Exposed as
FICLONE -- Linux's ioctl value, so `cp --reflink` works unmodified --
and as cmd/reflink for the case where there is no cp to hand.
Attributes, and the one genuinely new idea here. attr.c stores named
values on an object, reachable through setxattr/getxattr/removexattr at
Linux's syscall numbers. On top of that, schfs_index declares an index
over an attribute name and schfs_query answers "which objects carry this
value" from the B-tree rather than by walking. A query against an
unindexed attribute is refused with an error, never silently answered by
a full scan: a query that quietly becomes a scan is worse than one that
admits it cannot help. cmd/schattr drives both.
Three bugs fixed on the way, each found by the numbers being wrong
rather than by inspection:
- statfs's f_fsid was declared int64[2]. Linux's fsid_t is two *ints*,
so every field after it landed one slot late: userland read f_frsize
out of f_namelen, got 255, and df reported a 32 MB root filesystem
as 1992 KB. It hid for as long as it did because the fields used to
be invented constants, so wrong numbers looked like the usual ones.
- struct inode's nlink was a short against SchFS's uint32, so a file
past 32767 names came back negative -- and iput frees an object
whose count reaches zero. It is an int now, signed on purpose, so an
underflow lands somewhere a check can see rather than wrapping.
- filewrite split every write to fit v1's log. That limit belongs to
the journal, not to writing, and SchFS makes one transaction of
whatever it is handed: a 1 MB write was becoming 170 commits and
170 flushes for nothing. fs_ops carries max_write now, and SchFS
sets no limit.
Also here: a real mount table, so `mount -t schfs /dev/hdb /mnt` shows
up in /proc/mounts -- df looks a path up there before it calls statfs at
all, so a mount missing from that list was invisible to it -- several
volumes mounted at once, snapshots mounted by name, and /proc/fsstat
with an rdtsc-based counter, which is the only instrument fine enough to
see inside a single syscall.
Verified: `make schfs-check` passes in full, including the sweep that
crashes the volume at each of 119 points mid-transaction and requires
every one to mount with exactly its committed data, and the case where
both newest uberblocks are rubbish and the volume still comes back. The
headless boot check passes with 27 [ok] and no failures, on a SchFS root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
231 lines
5.1 KiB
C
231 lines
5.1 KiB
C
#pragma once
|
|
|
|
struct dtr {
|
|
ushort limit;
|
|
uint64 base;
|
|
} __attribute__((packed));
|
|
|
|
static inline uchar
|
|
inb(ushort port)
|
|
{
|
|
uchar data;
|
|
asm volatile("in %1,%0" : "=a" (data) : "d" (port));
|
|
return data;
|
|
}
|
|
|
|
// Cycle counter. Not a clock -- it does not convert to time without knowing the
|
|
// frequency, and it is not synchronised across cores -- but for "which of these
|
|
// two paths costs more" it is exactly the right instrument, and the only one
|
|
// with fine enough resolution to see inside a single syscall.
|
|
static inline uint64
|
|
rdtsc(void)
|
|
{
|
|
uint lo, hi;
|
|
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
|
|
return ((uint64)hi << 32) | lo;
|
|
}
|
|
|
|
static inline void
|
|
insl(int port, void *addr, int cnt)
|
|
{
|
|
asm volatile("cld; rep insl" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"d" (port), "0" (addr), "1" (cnt) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
static inline void
|
|
outb(ushort port, uchar data)
|
|
{
|
|
asm volatile("out %0,%1" : : "a" (data), "d" (port));
|
|
}
|
|
|
|
static inline uint
|
|
inl(ushort port)
|
|
{
|
|
uint data;
|
|
asm volatile("inl %1,%0" : "=a" (data) : "d" (port));
|
|
return data;
|
|
}
|
|
|
|
static inline void
|
|
outw(ushort port, ushort data)
|
|
{
|
|
asm volatile("out %0,%1" : : "a" (data), "d" (port));
|
|
}
|
|
|
|
// PCI configuration space is reached through a pair of 32-bit ports, so this
|
|
// is not optional decoration -- see kernel/dev/pci.c.
|
|
static inline void
|
|
outl(ushort port, uint data)
|
|
{
|
|
asm volatile("outl %0,%1" : : "a" (data), "d" (port));
|
|
}
|
|
|
|
static inline void
|
|
outsl(int port, const void *addr, int cnt)
|
|
{
|
|
asm volatile("cld; rep outsl" :
|
|
"=S" (addr), "=c" (cnt) :
|
|
"d" (port), "0" (addr), "1" (cnt) :
|
|
"cc");
|
|
}
|
|
|
|
static inline void
|
|
stosb(void *addr, int data, int cnt)
|
|
{
|
|
asm volatile("cld; rep stosb" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"0" (addr), "1" (cnt), "a" (data) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
// 64-bit-only helpers. `rep stosq` and `xchgq` do not exist in 32-bit mode, so
|
|
// these must not even be parsed by the -m32 bootloader build -- they compiled
|
|
// there before only because uint64 was accidentally 32 bits wide, which is the
|
|
// bug include/types.h now asserts against.
|
|
#ifdef __x86_64__
|
|
static inline void
|
|
stosq(void *addr, uint64 data, int cnt)
|
|
{
|
|
asm volatile("cld; rep stosq" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"0" (addr), "1" (cnt), "a" (data) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
struct segdesc;
|
|
|
|
static inline void
|
|
lgdt(struct segdesc *p, int size)
|
|
{
|
|
struct dtr d;
|
|
d.limit = size - 1;
|
|
d.base = (uint64)p;
|
|
asm volatile("lgdt (%0)" : : "r" (&d));
|
|
}
|
|
|
|
struct gatedesc;
|
|
|
|
static inline void
|
|
lidt(struct gatedesc *p, int size)
|
|
{
|
|
struct dtr d;
|
|
d.limit = size - 1;
|
|
d.base = (uint64)p;
|
|
asm volatile("lidt (%0)" : : "r" (&d));
|
|
}
|
|
|
|
static inline void
|
|
ltr(ushort sel)
|
|
{
|
|
asm volatile("ltr %0" : : "r" (sel));
|
|
}
|
|
|
|
static inline uint64
|
|
readeflags(void)
|
|
{
|
|
uint64 flags;
|
|
asm volatile("pushfq; pop %0" : "=r" (flags));
|
|
return flags;
|
|
}
|
|
|
|
static inline void
|
|
cli(void)
|
|
{
|
|
asm volatile("cli");
|
|
}
|
|
|
|
static inline void
|
|
sti(void)
|
|
{
|
|
asm volatile("sti");
|
|
}
|
|
|
|
static inline void
|
|
hlt(void)
|
|
{
|
|
asm volatile("hlt");
|
|
}
|
|
|
|
static inline uint64
|
|
xchg(volatile uint64 *addr, uint64 newval)
|
|
{
|
|
uint64 result;
|
|
asm volatile("lock; xchgq %0, %1" :
|
|
"+m" (*addr), "=a" (result) :
|
|
"1" (newval) :
|
|
"cc");
|
|
return result;
|
|
}
|
|
#endif /* __x86_64__ */
|
|
|
|
static inline uint64
|
|
rcr2(void)
|
|
{
|
|
uint64 val;
|
|
asm volatile("mov %%cr2,%0" : "=r" (val));
|
|
return val;
|
|
}
|
|
|
|
static inline void
|
|
lcr3(uint64 val)
|
|
{
|
|
asm volatile("mov %0,%%cr3" : : "r" (val));
|
|
}
|
|
|
|
// Invalidate the TLB entry for one page. Needed any time a PTE is changed
|
|
// or cleared for an address space that may still be the active CR3 —
|
|
// otherwise the CPU can keep translating through the stale entry until the
|
|
// next full CR3 reload (context switch), silently reading/writing the
|
|
// wrong physical page.
|
|
static inline void
|
|
invlpg(void *va)
|
|
{
|
|
asm volatile("invlpg (%0)" : : "r" (va) : "memory");
|
|
}
|
|
|
|
static inline void
|
|
wrmsr(uint32 msr, uint64 val)
|
|
{
|
|
uint32 low = (uint32)val;
|
|
uint32 high = (uint32)(val >> 32);
|
|
asm volatile("wrmsr" : : "c" (msr), "a" (low), "d" (high));
|
|
}
|
|
|
|
static inline uint64
|
|
rdmsr(uint32 msr)
|
|
{
|
|
uint32 low, high;
|
|
asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
|
|
return ((uint64)high << 32) | low;
|
|
}
|
|
|
|
static inline uint64
|
|
readcs(void)
|
|
{
|
|
uint64 val;
|
|
asm volatile("mov %%cs, %0" : "=r" (val));
|
|
return val;
|
|
}
|
|
|
|
// CPUID. `leaf` in EAX, results in EAX/EBX/ECX/EDX.
|
|
//
|
|
// %rbx is callee-saved *and* the PIC base register on some builds, so it is
|
|
// exchanged out rather than named as an operand -- naming it directly is the
|
|
// classic way to get "inline asm clobbers reserved register" or, worse, silent
|
|
// corruption in the caller.
|
|
static inline void
|
|
cpuid_leaf(uint32 leaf, uint32 *a, uint32 *b, uint32 *c, uint32 *d)
|
|
{
|
|
uint32 ra, rb, rc, rd;
|
|
asm volatile("xchgq %%rbx, %q1\n\t"
|
|
"cpuid\n\t"
|
|
"xchgq %%rbx, %q1"
|
|
: "=a"(ra), "=&r"(rb), "=c"(rc), "=d"(rd)
|
|
: "0"(leaf), "2"(0));
|
|
if (a) *a = ra;
|
|
if (b) *b = rb;
|
|
if (c) *c = rc;
|
|
if (d) *d = rd;
|
|
}
|