Lab 3: System Call Me Maybe
Our previous labs dealt with writing code for user space, which really isn’t much different than any other programs you’ve written. Write a program, compile it, run it. Watch it segfault.
However, kernel space is where privileged operations run. If you had to guess, did our previous labs do anything you’d consider privileged? Probably not, but in reality even the simplest “hello world” program interacts with the kernel; getting text to display on the screen requires a display driver or serial interface that user space programs do not have direct control over. Let’s take a look at the journey of a single printf
call in xv6:
User Space │ Kernel Space
---------- │ ------------
│
┌────────┐ ┌────────┐ ┌────────┐ │ ┌───────────────┐ ┌───────────────┐
│printf()│─▶│ putc() │─▶│write() │──┼───▶│ sys_write() │─▶│ filewrite() │──┐
└────────┘ └────────┘ └────────┘ │ └───────────────┘ └───────────────┘ │
│ ┌────────────────────────────────────────┘
│ │ ┌───────────────┐ ┌───────────────┐
│ └─▶│consolewrite() │─▶│ uartputc() │
│ └───────────────┘ └───────────────┘
│
│
│
The act of displaying a single character sends us through several layers of abstraction, ultimately making the hardware do something.
In this lab, we’re going to add our own system call and companion user space utility that will use it.
Adding a System Call
Here’s an overview of the process for adding a new system call. You will probably do this a few times over the course of the semester. If you want to simply walk through the process, pick an existing system call (such as uptime()
) and note all the locations it appears in the files below:
In the Kernel (/kernel)
- In
syscall.h
, give our system call a number by adding an entry for it. Use the next available number. - In
syscall.c
:- Add a function prototype for the system call. These are prefixed with
sys_
. - Add a mapping for it to the
syscalls
array. This allows us to look up a system call function by its number.- This might look odd, but it’s just setting up an array of function pointers.
- Add a function prototype for the system call. These are prefixed with
- Provide an implementation of your system call, prefixed with
sys_
as we entered above. The implementations are split acrosssysfile.c
andsysproc.c
, with the former containing anything file system related and the latter containing anything process related.
In User Space (/user)
- In
usys.pl
, add an entry for the name of the user-facing version of the system call, e.g., if you added sys_uptime() then theusys.pl
version should beuptime
. - Finally, in
user.h
, add a function prototype for the user-facing system call function.
Afterward, compile and run your OS to make sure you didn’t miss any steps.
Shutting Down
All great operating systems have a way to shut down. Except ours. Try running exit
from the shell, and you’ll see that init
simply runs a new shell instance. There’s no halt
or shutdown
command to be found. No reboot
. No poweroff
. We need to fix this so that later when you’re really frustrated with your OS you can just shut it off until it starts functioning correctly. (I’ve had limited success with this approach).
Since shutting down involves the hardware, it’s a privileged operation. On Linux and most other UNIX derivatives, it’s even restricted to the root user; try to run halt
, reboot
, etc. on gojira as a regular user.
The QEMU RISC-V virtual machine we’re using in class has a test interface at memory location 0x100000
that allows us to shut it down. Run your OS in QEMU and press CTRL+A
, then C
to get the QEMU console. Enter info mtree
at the (qemu)
prompt to see the memory tree:
memory-region: system
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000001000-000000000000ffff (prio 0, rom): riscv_virt_board.mrom
0000000000100000-0000000000100fff (prio 0, i/o): riscv.sifive.test
0000000000101000-0000000000101023 (prio 0, i/o): goldfish_rtc
0000000002000000-0000000002003fff (prio 0, i/o): riscv.aclint.swi
0000000002004000-000000000200bfff (prio 0, i/o): riscv.aclint.mtimer
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport_window
0000000003000000-000000000300ffff (prio 0, i/o): gpex_ioport
0000000004000000-0000000005ffffff (prio 0, i/o): platform bus
If we write 0x5555
to that memory location, the VM will shut down. If we write 0x7777
, it will reboot. However, there is one problem here: by default, our kernel can’t access the physical address 0x100000
. If you try it, your OS will crash.
We will fully explore virtual memory later in the semester. For now, you just need to edit kernel/memlayout.h
and kernel/vm.c
:
In memlayout.h
, you will see a description of the physical memory layout of our machine. Add a new entry for the test interface:
#define VIRT_TEST 0x100000
Then, in vm.c
, you need to map this into the kernel’s virtual memory. Inside kvmmake()
, you’ll find the mappings. Add one:
kvmmap(kpgtbl, VIRT_TEST, VIRT_TEST, PGSIZE, PTE_R | PTE_W);
Both of these additions need to be commented to explain what you are doing, just like the existing code is!
Again, you’re not expected to fully understand virtual memory for now. We’ll pretend it’s magic and come back to it.
Finally, to make the system shut down:
volatile uint32 *test_dev = (uint32 *) VIRT_TEST;
*test_dev = 0x7777;
If you don’t already know what volatile
does, you should try to figure it out.
Lab Instructions
There’s a lot of information to absorb in this lab, but not a lot of code to write.
- Create a system call (or system calls) for reboot and shut down.
- Be sure to choose a name that makes sense.
- See the steps above, and make sure you follow all of them.
- Create a user space program (or programs) that uses the system call(s) to reboot or shutdown.
- Test the programs(s) and make sure they behave correctly.
If you get stuck, don’t be afraid to ask for help. After you’re finished, take a break. You deserve it.
In the Part II of the lab, you’re going to do it all over again, except this time all on your own. If you look back at the info mtree
output, you’ll notice an interesting device: goldfish_rtc
. This is the real time clock for our virtual hardware; it reports the current time as a 64-bit UNIX timestamp. Your objective for Part II is to create a system call and user space program that will report the current UNIX timestamp, as provided by the goldfish_rtc
device.
To do this:
- Add your system call(s) as usual, making sure to name them something intuitive.
- Adjust memory mappings and the model of the machine’s memory layout.
- Add a user space utility that calls the system call and prints the current UNIX timestamp.
- Note: pay attention to the data type you use here. You may also want to convert the raw timestamp from nanoseconds to seconds so that
printf
can handle it as a regular integer.
- Note: pay attention to the data type you use here. You may also want to convert the raw timestamp from nanoseconds to seconds so that
Grading and Submission
To receive 75% credit:
- Create system call(s) to handle shut down and reboot
To receive 85% credit:
- Complete all previous requirements
- Implement the user utility (or utilities) to shut down and reboot
To receive full credit for this lab:
- Complete all previous requirements
- Implement a system call and user space program to retrieve the current UNIX timestamp as reported by the hardware (Part II).
Once you are finished, check your changes into your OS repo. Then have a member of the course staff take a look at your lab to check it.