Homework 3: A basic screen saver in supervisor mode

In this homework, we extend xv6 with a basic screen saver. A screen saver probably serves no purpose these post-CRT days, and is frankly quite dumb from an energy perspective, but hey, it makes for a decent homework. In this homework, you'll:

  • deal with interrupt handlers
  • create top/bottom half interrupt routines
  • read a file from a file-system, from inside the kernel
  • call real-mode BIOS functions from protected mode (using provided code!)
The end-goal of the homework is simple: after 10 seconds of no keyboard activity, the text screen should be replaced by a static image. Once keyboard activity is detected, the image disappears, and the original contents of the text screen returns.

As in the previous homework, this is a good time to try for progress in small, incremental steps, as follows:

  1. when 10 seconds have passed since start-up, cprintf "Starting screen saver" on the console.
  2. when 10 seconds have passed since last key press, cprintf "Starting screen saver" on the console.
  3. if "the screen saver is active" when a key is pressed, cprintf "Stopping screen saver" on the console.
  4. like (3), but without using cprintf in an interrupt context
  5. like (4), but switch screen between mode 13h and mode 3h instead of printing text
  6. also load the picture into the VGA screen buffer, using namei() and readi()
  7. also save and restore the text from the CGA screen buffer (see lines 7352-7383, which writes characters to the buffer).

Getting started

For starters, first git fetch, then either git merge origin/hw3 in or git checkout -b hw3 origin/hw3 to get the template.

This homework will require a few small modifications to xv6, but virtually all your code should go in new files called screen_saver.c and screen_saver.h. When you make changes elsewhere, try to keep it to just a function call. This is called a "hook" in kernel lingo.

Implementing a timeout, without a running thread, or an alarm function

You're in the kernel now, so most of the conveniences you're used to no longer exist. For one, you don't get to have a thread running, so things like sleep(10) are not only unavailable, but even if sleep(time_in_seconds) existed, you'd have no place to put the code.

Instead, you need to modify the interrupt handlers. All interrupts (such as timer and keyboard...) start at alltraps: in trapasm.S. Here's your chance to run a bit of code, somewhere in the interrupt handling code (but for the love of FSM, don't put any code in trapasm.S, stick to C!). If you get the timeout to work, reacting to a keypress should be a straightforward extension to the same idea.

Doing an interrupt's bidding, but not in interrupt context

Some things, such as switching to real mode and running BIOS functions, cannot be done in an interrupt context. Meaning: if you are currently handling an interrupt, and you try to do such non-allowed things, your machine will reboot, crash, or worse. Of course, simply calling a function from inside an interrupt handler doesn't get you outside the interrupt context - you're still handling the interrupt, until you call iret (in trapasm.S), at which point execution ceases.

Nope, we need to have a thread in normal supervisor mode do these sensitive jobs. Unfortunately, there are no background kernel threads in xv6 to help us out. However, we can rely on the init process always being around, and we happen to have a global pointer to it available: initproc. All init does is wait for child processes to die. Specifically, on line 7833 it keeps calling the userspace wrapper wait() (which calls sys_wait(), which calls the kernel function wait()).

This in turn calls sleep() on line 2439, which will halt execution until some other thread calls wakeup() with the same argument. If you add code just before this, it'll be run whenever a process has woken up, and is about to go back to sleep. Add a condition to check that proc==initproc, wakeup(initproc) from an interrupt context when you need something done, and you're good to go.

One wrinkle remains - the above will result in a "panic" in acquire, when you try to print something. If you trace it with gdb (the "bt" command shows a stack trace), you'll find that the complaint has to do with trying to lock ptable.lock a second time. An easy fix is to release ptable.lock before your code, and re-aquire ptable.lock after you're done.

The concept of having a thread doing some work "for" an interrupt handler is called a "bottom half" interrupt routine. The "top half" is the interrupt handler itself.

Switching to VGA mode and back

Once outside of interrupt context, include int32.h, and the following will switch to VGA mode.

  regs16_t regs = { .ax = 0x13 };
  bios_int(0x10, &regs);

Reading from a file on disk

If file was a contiguous set of blocks in a known location, you'd "simply" hand BIOS a nice DAP with the appropriate sector range and destination address, and you'd be all set. Sadly, the file resides in a file system at an unknown location, and may be spread all across the disk.

Fortunately, sys_exec() needs to do something similar to what we need to do: find the file on disk given a name, and load it into memory. So, it remains to look at the implementation of sys_exec() to see how it does it. So take a closer look, and just do the essential parts. Hint: namei(), readi(), begin_op(), end_op(), ilock() and iunlockput() would in all likelihood be part of a correct solution. Watch out: rev7 (the printed version) is different from rev8, the one in git. Please reference rev8 for this!

As in the previous homework, the picture is called "/cover.raw", but you're strongly encouraged to use something more imaginative.

Sepideh created a "default VGA palette" for Photoshop, which can be used to convert any picture to the correct 256 colors, which would be very handy in this making a better screen saving image. Ask her for it.

Saving / restoring the text screen

I'm not sure why, but whenever you switch to VGA mode and back, the text goes away. According to line 7352, the text buffer is at 0xb8000, which is way above 0xa0000 + 64kb, so it doesn't seem like it would have to be cleared. Still, looks like BIOS or the graphics card clears it for us in their infinite wisdom.

So, before switching to VGA mode (or perhaps before switching back to text mode?), make sure you save away the contents of the text screen, including cursor position. After switching back, copy the contents back in and restore the cursor. cgaputc() has examples of how to access the text console.

Topic revision: r5 - 2014-09-11 - 22:18:16 - Main.jakob
Copyright 2016 The Board of Trustees
of the University of Illinois.webmaster@cs.uic.edu
Helping Women Faculty Advance
Funded by NSF