This is dirty but pretty cool! If we have a pending, unmasked signal for
a process that's blocked inside the kernel, we set up alternate stacks
for that process and unblock it to execute the signal handler.
A slightly different return trampoline is used here: since we need to
get back into the kernel, a dedicated syscall is used (sys$sigreturn.)
This restores the TSS contents of the process to the state it was in
while we were originally blocking in the kernel.
NOTE: There's currently only one "kernel resume TSS" so signal nesting
definitely won't work.
The SpinLock was all backwards and didn't actually work. Fixing it exposed
how wrong most of the locking here is.
I need to come up with a better granularity here.
This is pretty inefficient for ext2fs. We walk the entire block group
containing the inode, searching through every directory for an entry
referencing this inode.
It might be a good idea to cache this information somehow. I'm not sure
how often we'll be searching for it.
Obviously there are multiple caching layers missing in the file system.
I also added a generator cache to FileHandle. This way, multiple
reads to a generated file (i.e in a synthfs) can transparently
handle multiple calls to read() without the contents changing
between calls.
The cache is discarded at EOF (or when the FileHandle is destroyed.)
I added a dead-simple malloc that only allows allocations < 4096 bytes.
It just forwards the request to mmap() every time.
I also added simplified versions of opendir() and readdir().
This way subclasses only have to implement readBlock() and writeBlock().
read() and write() require that the offset and length are both divisible
by the blockSize().
BlockDevice was the wrong name for this abstraction, since a block device
is a type of file in a unix system, and we should use that name for that
concept in the fs implementation.