summaryrefslogtreecommitdiff
path: root/labs/fs1.html
blob: 45d3e0c2d8741373307b95c9265e38f07c0e0db5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<html>
<head>
<title>Lab: mount/umount</title>
<link rel="stylesheet" href="homework.css" type="text/css" />
</head>
<body>

<h1>Lab: mount/umount</h1>

<p>In this lab you will add support for mounting/unmounting of file
systems to xv6.  This lab will expose you to many parts of the xv6
file system, including pathname lookup, inodes, logging/recovery, disk
driver, concurrency, etc.

<p>Your job is modify xv6 so that your modified kernel passes the
  tests in mounttest. You will have to implement two system
  calls: <tt>mount(char *source, char *target)</tt>
  and <tt>umount(char *target)</tt>. Mount attaches the device
  referenced by <tt>source</tt> (e.g., <tt>/disk1</tt>) at the
  location specified by <tt>target</tt>.  For
  example, <tt>mount("/disk1", "/m")</tt> will attach <tt>disk1</tt>
  at the directory <tt>/m</tt>. After this mount call, users can use
  pathnames such as <tt>/m/README</tt> to read the
  file <tt>README</tt> stored in the root directory
  on <tt>disk1</tt>.  <tt>Umount</tt> removes the attachment.  For
  example, <tt>umount("/m")</tt> unmounts disk1 from <tt>/m</tt>.

<p>There are several major challenges in implementing the mount system
calls:

  <ul>
    
    <li>Adding the actual system calls so that user programs can call
      them.  This is similar to previous labs in which you added
      systems calls xv6.

    <li>Supporting several disks.  You will have generalize to
      virtio_disk.c to support at least two disks.

    <li>Logging file system modifications to the right disk.  xv6
      assumes there is only disk and file system calls typically start
      with <tt>begin_op</tt> and end with <tt>end_op</tt>, logging all
      modifications between these two calls to the log on the one
      disk.  With mount, modifications to the file system on the
      second disk must be logged to the second disk.

    <li>Modifying pathname lookup (<tt>namex</tt>) so that when a
      lookup cross a mount point, it continues at the root inode of
      the attached disk.

  </ul>

<p>The rest of this assignment provides some hints how you might go
about the above challenges.

<h2>Adding system calls</h2>

<p>Add the stubs for the two systems calls to xv6 so that you can
compile mounttest and add two empty functions for the two system calls
to sysfile.c. Run mounttest and it will fail on the first call
to <tt>mount</tt>.


<h2>Adding a second disk</h2>      

<p>To be able to mount another disk, you need to extend xv6 to support
at least two disks.  Modify virtio_disk.c to support an array of two
disks instead of a single disk.  The address of the second disk
is <tt>0x10002000</tt>; modify the macro <tt>R</tt> to take a disk
number (0, 1,..) and read/write to the memory address for that disk.

<p>All functions in <tt>virtio_disk.c</tt> need to take the disk
number as an argument to update the state of the disk that is
read/written to or to receive an interrupt from the disk.
Modify <tt>virtio_disk_init</tt> to take a disk number as an argument
and update is to that it initializes that disk.  Similar, go through
the other functions; make these changes should be most mechanical
(i.e., text substitutions).

<p>The second disk interrupts at IRQ 2; modify trap.c to receive that
interrupt and <tt>virtio_disk_intr</tt> with the number of the disk
that generated the interrupt.
     
<p>Modify the file Makefile to tell qemu to provide a second
disk. Define the variable <tt>QEMUEXTRA = -drive
file=fs1.img,if=none,format=raw,id=x1 -device
virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1</tt> and
add <tt>$(QEMUEXTRA)</tt> to the end of <tt>QEMUOPTS</tt>.

<p>Create a second disk image <tt>fs1.img</tt>.  Easiest thing to do
  is just copy the file <tt>fs.img</tt>.  You might want to add rules
  to the Makefile to make this image and remove it on <tt>make
  clean</tt>.

<p>Add to the user program init a call to create a device for the new
  disk. For example, add the line <tt>mknod("disk1", DISK, 1);</tt> to
  init.c. This will create an inode of type device in the root
  directory with major number <tt>DISK</tt> and minor number 1.

<p>The first argument of the <tt>mount</tt> system call ("disk1") will
  refer to the device you created using <tt>mknod</tt> above.  In your
  implementation of the mount system call,
  call <tt>virtio_disk_init</tt> with the minor number as the argument
  to initialize the second disk.  (We reserve minor number 0 for the
  first disk.)

<p>Boot xv6, run mounttest, and make sure <tt>virtio_disk_init</tt>
  gets called (e.g., add print statement).  You won't know if your
  changes are correct, but your code should compile and invoke the
  driver for the second disk.

<h2>Modify the logging system</h2>

<p>After calling <tt>virtio_disk_init</tt>, you need to also
  call <tt>loginit</tt> to initialize the logging system for the
  second disk (and restore the second disk if a power failure happened
  while modifying the second disk).  Generalize the logging system to
  support to two logs, one on disk 0 and one disk 1.  These changes
  are mostly mechanical (e.g., <tt>log.</tt> changes
  to <tt>log[n].</tt>), similar to generalizing the disk driver to
  support two disks.

<p>To make xv6 compile, you need to provide a disk number
  to <tt>begin_op</tt> and <tt>end_op</tt>.  It will be a challenge to
  figure out what the right value is; for now just specify the first
  disk (i.e., 0).  This isn't correct, since modifications to the
  second disk should be logged on the second disk, but we have no way
  yet to read/write the second disk.  Come back to this later when you
  have a better idea how things will fit together, but make sure that
  xv6 compiles and still runs.

<h2>Pathname lookup</h2>

<p>Modify <tt>namex</tt> to traverse mount points: when <tt>namex</tt>
  sees an inode to which a file system is attached, it should traverse
  to the root inode of that file system.  Hint: modify the in-memory
  inode in file.h to keep some additional state, and initialize that
  state in the mount system call.  Note that the inode already has a
  field for disk number (i.e., <tt>dev</tt>), which is initialized and
  passed to reads and writes to the driver.  <tt>dev</tt> corresponds
  to the minor number for disk devices.

<p>Your modified xv6 should be able to pass the first tests in
  mounttest (i.e., <tt>stat</tt>).  This is likely to be challenging,
  however, because now your kernel will be reading from the second
  disk for the first time, and you may run into many issues.

<p>Even though <tt>stat</tt> may return correctly, your code is likely
  to be incorrect, because in <tt>namex</tt>
  because <tt>iunlockput</tt> may modify the second disk (e.g., if
  another process removes the file or directory) and those
  modifications must be written to the second disk.  Your job is to
  fix the calls to <tt>begin_op</tt> and <tt>end_op</tt> to take the
  right device.  One challenge is that <tt>begin_op</tt> is called at
  the beginning of a system call but then you don't know the device
  that will be involved; you will have to postpone this call until you
  know which inode is involved (which tells you will which device is
  involved).  Another challenge is that you cannot postpone
  calling <tt>begin_op</tt> passed <tt>ilock</tt> because that
  violates lock ordering in xv6; you should not be
  calling <tt>begin_op</tt> while holding locks on inodes. (The log
  system allows a few systems calls to run; if a system call that
  holds an inode lock isn't admitted and one of the admitted system
  calls needs that inode to complete, then xv6 will deadlock.)

<p>Once you have implemented a plan for <tt>begin_op</tt>
  and <tt>end_op</tt>, see if your kernel can pass <tt>test0</tt>.  It
  is likely that you will have to modify your implementation of the
  mount system call to handle several corner cases.  See the tests
  in <tt>test0</tt>.

<p>Run usertests to see if you didn't break anything else.  Since you
  modified <tt>namex</tt> and <tt>begin/end_op</tt>, which are at the
  core of the xv6 file system, you might have introduced bugs, perhaps
  including deadlocks.  Deadlocks manifest themselves as no output
  being produced because all processes are sleeping (hit ctrl-p a few
  times).  Your kernel might also suffer kernel panics, because your
  changes violate invariants.  You may have to iterate a few times to
  get a good design and implementation.

<h2>umount</h2>

<p>Once your kernel passes usertests and test0 of mounttest, implement
  umount.  The main challenge is that umount of a file system should
  fail if the file system is still in use; that is, if there is an
  inode on the mounted device that has a <tt>ref > 0</tt>.
  Furthermore, this test and unmounting should be an atomic
  operation. (Hint: lock the inode cache.)  Make sure your kernel
  passes test1 of mounttest.

<p>Test2 of mounttest stresses <tt>namex</tt> more; if you have done
    everything right above, your kernel should pass it.  Test3 tests
    concurrent mount/unmounts with file creation.

<h2>crash safety</h2>

<p>One of the main goals of the file system is to provide crash
  safety: if there is a power failure during a file system operation,
  xv6 should recover correctly.  It is difficult to introduce power
  failure at the critical steps of logging; instead, we added a system
  call that causes a kernel panic after committing an operation but
  before installing the operation.  Test4 with crashtest tests if your
  xv6 recovers the mounted disk correctly.
   
       
</body>
</html>

<h2>Optional challenges</h2>

<p>Modify xv6 so that init mounts the first disk on the root inode.
  This will allow you to remove some code specific for the first disk
  from the kernel.

<p>Support mounts on top of mounts.