user/stressfs.c
About this file
stressfs is a small file-system stress test. It starts five processes that run at
the same time, each creating its own file in the root directory (stressfs0 to
stressfs4), writing 20 blocks of 512 bytes to it, and reading them back.
The files are separate, so the processes never share a file’s data. They do share
everything underneath: the root directory that all five add an entry to, the
free bitmap and inode blocks, the buffer cache, the
write-ahead log that groups their writes into transactions, and
the disk driver’s queue. Running them concurrently on three CPUs checks the locking in
all of those layers. The program checks nothing itself: it ignores the return values of
open, write and read and never compares the data it reads. A failure shows up only
as a kernel panic, a hang, or wrong files afterwards; ls should show five
files of 10240 bytes each.
The comment at the top is from the x86 version of xv6 and is out of date (see the note on lines 1–8).
Read before: kernel/sysfile.c (sys_open) and kernel/file.c
(filewrite). Read next: user/logstress.c, a heavier test of the log.
An outdated comment from x86 xv6
This comment describes how stressfs was used in the x86 version of xv6, whose disk
driver had a function iderw that appended requests to a queue called idequeue.
Neither exists in this RISC-V version: the disk driver is
kernel/virtio_disk.c, and the corresponding function is
virtio_disk_rw. The idea still applies: a test with many concurrent disk
requests is how you would demonstrate a race condition in a driver’s queue,
for example by deliberately taking a lock too late and adding a delay loop to widen
the window, as the comment suggests.
Headers
kernel/fcntl.h supplies O_CREATE, O_RDWR and O_RDONLY. kernel/fs.h is
not used.
main(): a file name and a block of data
path is a writable array holding "stressfs0"; line 32 changes its last character
to give each process its own name. data is 512 bytes of the letter a. 512 is half
of xv6’s BSIZE (1024), so two writes fill each disk block and every second
write starts in the middle of a block.
512 bytes: half a disk block.
Fill the buffer with the letter a.
Make a chain of five processes
The loop creates the processes as a chain, not as five children of one parent. At
each step the parent (fork returned a positive PID (process ID)) leaves the loop, keeping
the current i, and the child goes on to the next iteration and forks again:
- the original process stops with
i= 0, - its child with
i= 1, that child’s child withi= 2, and so on, - the last child runs the loop to the end and has
i= 4.
So there are five processes, numbered 0 to 4, each the parent of the next. A failed
fork (-1) is treated like being the child, so the program carries on with fewer
processes.
All five print write N at about the same time. Because the user printf
sends each character with its own write system call, their output comes out
interleaved, something like wrwriwtireit et0e. That is expected, not a bug.
The parent leaves the loop; the child continues and forks the next process.
Create a private file and write 10 KiB to it
path[8] is the 0 in stressfs0; adding i turns it into 0…4. open with
O_CREATE goes through sys_open → create, which allocates a
new inode (ialloc) and adds a name to the root directory
(dirlink). Five processes do that in the same directory at once. This
tests that the directory’s inode lock (held by create) keeps two of them from
taking the same free directory slot, and that ialloc, which claims a free inode
while it holds that inode block’s buffer from bread, never hands the same
inode to two processes.
Each write goes sys_write → filewrite, which wraps the work
in one file-system operation (begin_op … end_op); operations
that overlap are committed together as one transaction. It calls
writei. That allocates data blocks with balloc (all five
processes compete for the same bitmap block) and records the changed blocks in the
log. Twenty writes of 512 bytes give a 10240-byte file of 10 blocks.
Line 35 is a leftover from an older version whose printf took a file descriptor
first.
Turn stressfs0 into stressfs0…stressfs4 (index 8 is the digit).
Create the file (or open it if it is left over from an earlier run) for reading and writing.
Append 512 bytes. One file-system operation per call, since 512 bytes is far below
the per-operation limit in filewrite; overlapping operations from the
five processes commit together as one transaction.
Read the file back
Reopening the file by name and reading it 512 bytes at a time exercises
sys_read → fileread → readi and the
buffer cache, while the other processes may still be writing their own files.
The data is read into data and then ignored: nothing checks that it is still all
a. Each of the five processes prints read.
Reopen read-only, so reading starts at offset 0.
Read the next 512 bytes; the result is not checked.
Wait for the next process in the chain
Each process waits for its one child, so process 0, which the shell is waiting for,
finishes only after the whole chain has finished, and the prompt does not come back
in the middle of the output. Process 4 has no children; its wait returns -1 at
once.
Wait for this process’s child (the next one in the chain), if any.