This project consists of two tasks that you will work on XV6. Task A aims to help you understand how to increase parallelism on a multicore architecture. Task B aims to help you understand the index structure and the support of large files on XV6.
Re-designing memory allocator code in XV6 to increase parallelism.
A common symptom of poor parallelism on multi-core machines is high lock contention. Improving parallelism often involves changing both data structures and locking strategies in order to reduce contention. You'll do this for the xv6 memory allocator.
Before writing code, you should make sure you have read "Chapter 4: Locking" from the xv6 book and studied the corresponding code.
The program user/kalloctest stresses xv6's memory allocator: three processes grow and shrink their address spaces, resulting in many calls to kalloc and kfree. kalloc and kfree obtain kmem.lock. kalloctest prints the number of test-and-sets that did not succeed in acquiring the kmem lock (and some other locks), which is a rough measure of contention:
$ kalloctest
start test0
test0 results:
=== lock kmem/bcache stats
lock: kmem: #test-and-set 161724 #acquire() 433008
lock: bcache: #test-and-set 0 #acquire() 812
=== top 5 contended locks:
lock: kmem: #test-and-set 161724 #acquire() 433008
lock: proc: #test-and-set 290 #acquire() 961
lock: proc: #test-and-set 240 #acquire() 962
lock: proc: #test-and-set 72 #acquire() 907
lock: proc: #test-and-set 68 #acquire() 907
test0 FAIL
start test1
total allocated number of pages: 200000 (out of 32768)
test1 OK
acquire maintains, for each lock, the count of calls to acquire for that lock,
and the count of test-and-sets that didn't manage to acquire the lock.
kalloctest calls a system call that causes the kernel to print those counts
for the kmem and bcache locks and for the 5 most contended locks.
If there is lock contention the number of test-and-sets will be high because it takes many loop iterations before acquire obtains the lock. The system call returns the sum of the #test-and-sets for the kmem and bcache locks.
For this task, you must use a dedicated unloaded machine with multiple cores. All the SoC lab machines will meet this requirement.
The root cause of lock contention in kalloctest is that kalloc() has a single free list, protected by a single lock. To remove lock contention, you will have to redesign the memory allocator to avoid a single lock and list. The basic idea is to maintain a free list per CPU, each list with its own lock. Allocations and frees on different CPUs can run in parallel, b ecause each CPU will operate on a different list. The main challenge will be to deal with the case in which one CPU's free list is empty, but another CPU's list has free memory; in that case, the one CPU must "steal" part of the other CPU's free list. Stealing may introduce lock contention, but that will hopefully be infrequent.
- Your job is to implement per-CPU freelists and stealing when one CPU's list is empty.
- Run kalloctest to see if your implementation has reduced lock contention, and to check that it can still allocate all of memory.
- Your output will look similar to that shown below, although the specific numbers will differ.
- Make sure usertests still passes.
$ kalloctest
start test0
test0 results:
=== lock kmem/bcache stats
lock: kmem: #test-and-set 0 #acquire() 33167
lock: kmem: #test-and-set 0 #acquire() 200114
lock: kmem: #test-and-set 0 #acquire() 199752
lock: bcache: #test-and-set 0 #acquire() 28
=== top 5 contended locks:
lock: proc: #test-and-set 22303 #acquire() 180082
lock: proc: #test-and-set 4162 #acquire() 180130
lock: proc: #test-and-set 4115 #acquire() 180129
lock: proc: #test-and-set 342 #acquire() 180070
lock: proc: #test-and-set 39 #acquire() 180070
test0 OK
start test1
total allocated number of pages: 200000 (out of 32768)
test1 OK
$
$ usertests
...
ALL TESTS PASSED
$
- Please give all of your locks
initlocknames that start with "kmem". - You can use the constant
NCPUfrom kernel/param.h - Let
freerangegive all free memory to the CPU runningfreerange. - The function
cpuidreturns the current core number, but it's only safe to call it and use its result when interrupts are turned off. - You should use push_off() and pop_off() to turn interrupts off and on.
You can run ./grade-kalloc to see your potential grade for this task.
Implement Large Files Support using doubly-indirect blocks on XV6
In this task, you'll increase the maximum size of an xv6 file. Currently xv6 files are limited to 268 blocks, or 268*BSIZE bytes (BSIZE is 1024 in xv6). This limit comes from the fact that an xv6 inode contains 12 "direct" block numbers and one "singly-indirect" block number, which refers to a block that holds up to 256 more block numbers, for a total of 12+256=268 blocks.
Before writing code, you should read "Chapter 7: File system" from the xv6 book and study the corresponding code.
The bigfile user program creates the longest file it can, and reports that size:
$ bigfile
..
wrote 268 blocks
bigfile: file is too small
$
The test fails because the longest file is only 268 blocks.
You'll change the xv6 file system code to support a "doubly-indirect" block in each inode, containing 256 addresses of singly-indirect blocks, each of which can contain up to 256 addresses of data blocks. The result will be that a file will be able to consist of up to 256*256+256+11 blocks (11 instead of 12, because we will sacrifice one of the direct block numbers for the double-indirect block).
mkfs initializes the file system to have fewer than 2000 free data blocks,
too few to show off the changes you'll make. Modify kernel/param.h to change FSSIZE from 2000 to 200,000:
#define FSSIZE 200000 // size of file system in blocks
Rebuild mkfs so that is produces a bigger disk: $ rm mkfs/mkfs fs.img; make mkfs/mkfs
-
The format of an on-disk inode is defined by struct dinode in fs.h. You're particularly interested in NDIRECT, NINDIRECT, MAXFILE, and the addrs[] element of struct dinode. Look at Figure 7.3 in the xv6 book for a diagram of the standard xv6 inode.
-
The code that finds a file's data on disk is in
bmap()in fs.c. Have a look at it and make sure you understand what it's doing.bmap()is called both when reading and writing a file. When writing,bmap()allocates new blocks as needed to hold file content, as well as allocating an indirect block if needed to hold block addresses.
bmap() deals with two kinds of block numbers. The bn argument is a "logical block number" -- a block number
within the file, relative to the start of the file. The block numbers in ip->addrs[], and the argument to bread(),
are disk block numbers. You can view bmap() as mapping a file's logical block numbers into disk block numbers.
- Your job is to modify bmap() so that it implements a doubly-indirect block, in addition to direct blocks and a singly-indirect block.
- You'll have to have only 11 direct blocks, rather than 12, to make room for your new doubly-indirect block.
- You're not allowed to change the size of an on-disk inode.
- The first 11 elements of ip->addrs[] should be direct blocks;
- The 12th should be a singly-indirect block (just like the current one);
- The 13th should be your new doubly-indirect block.
- You are done with this exercise when bigfile writes 65803 blocks and usertests runs successfully:
$ bigfile
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
wrote 65803 blocks
done; ok
$ usertests
...
ALL TESTS PASSED
$
bigfile will take at least a minute and a half to run.
- Make sure you understand
bmap(). Draw a diagram of the relationships betweenip->addrs[], the indirect block, the doubly-indirect block and the singly-indirect blocks it points to, and data blocks. - Make sure you understand why adding a doubly-indirect block increases the maximum file size by 256*256 blocks (really -1, since you have to decrease the number of direct blocks by one).
- Think about how you'll index the doubly-indirect block, and the indirect blocks it points to, with the logical block number.
- If you change the definition of
NDIRECT, you'll probably have to change the declaration ofaddrs[]in struct inode in file.h. - Make sure that struct inode and struct dinode have the same number of elements in their addrs[] arrays.
- If you change the definition of
NDIRECT, make sure to create a newfs.img, since mkfs usesNDIRECTto build the file system. - If your file system gets into a bad state, perhaps by crashing, delete
fs.img(do this from Unix, not xv6).makewill build a new clean file system image for you. - Don't forget to
brelse()each block that youbread(). - You should allocate indirect blocks and doubly-indirect blocks only as needed, like the original
bmap(). - Make sure
itruncfrees all blocks of a file, including double-indirect blocks.
You can run ./grade-bigfile to see your potential grade for this task.
Please following the procedure below in your submission.
- Switch to your project folder:
cd ~/path/to/your-projects-folder - Clean up the folders
cd TaskA make clean cd ../TaskB make clean - List files that you have added or modified:
git status - Stage the new and modified files in your local repo:
git add files-you-created-or-modified - Commit the changes to your local repo:
git commit -m "Finished Project #2" - Push the changes to the remote repo (i.e. github):
git pushOrgit push origin master. - Verify all changes have been push into the repo on github.
- You can run "git status", "git log", and "git show" to the see the changes and commits you have made
- You can also log into github to see if your changes have been actually pushed into your project repo on github.
- (Optional but recommended) For a further validation, you can check out the repo in a separate folder on your computer and then verify that it has all the files for the programs to work correctly.