CS134, Spring 2002
Homework Assignment #3
Due 9 P.M. Sunday, May 5, 2002

Summary

This assignment is based on the "System Calls" project in Chapter 5 of the handout selected from Gary Nutt, Linux Kernel Projects.

You may do the work on your own Linux box or on Emulab. However, if you use your own box and it's not running the 2.4 kernel, you might find that the documentation here differs from how your kernel works. That's your problem, not mine.

Project Description

Nutt's suggested project (adding a get-time system call) is boring. You already did a get-time /proc entry in the previous assignment. Besides, there is already gettimeofday. Finally, returning the time is not particularly instructive.

Instead, you will add a new system call that does something (very) mildly useful. This system call will return you a bit mask telling which file descriptors are open. The call will accept two parameters: an in parameter giving the maximum number of file descriptors to scan, and an out parameter that is a pointer to an area that will receive the bit masks. If your system call is named getfds, the prototype would be:

    int getfds(int maxFds, unsigned long* result);
and you would use it something like this:
    unsigned long result[5];
    if (getfds(5 * 32, &result[0]) == -1) {
	/* handle error */
    }
    else {
	/* result has bit mask of open fds */
    }

Writing Your System Call

You can follow the general outline in the handout for creating your system call. The code for the call should go in kernel/sys.c. You will also need to modify arch/i386/kernel/entry.S to add your system call to the table. I inserted it just after the #endifs that follow sys_tux, which gives it the number 223 under our kernel configuration.

The system-call code in sys.c needs to remember that the second parameter is in user space, not kernel space. Thus, you can't just store into result[i]. Instead, you must use put_user:

    int retval;
    // ...
    retval = put_user(something_useful, &result[i]);
    if (retval != 0)
	return retval;		/* put_user failed */
Your function should check maxFds and return -EINVAL if it is negative. Otherwise, it should return 0 to indicate success. (Note: there are also functions such as copy_to_user and strncpy_from_user, but you won't need them. It is interesting to search sys.c for the string "user" to find all the different ways the kernel can communicate with user-mode processes.)

You can find out whether a particular file descriptor iis open by examining the table entry current->files->fd[i]. If the value is non-NULL, the file is open.

If you're not familiar with bit masks, you can set bit i in a 32-bit word with the following code:

    int val = 0;
    ...
    val |= 1 << i;
Note that you will have to write code that allows for the fact that a single word is only 32 bits, but i might be greater than 32. This restriction is the reason why result is an array.

The Test Program

The handout talks about modifying include/asm/unistd.h to add your system call number. That's sensible for "real" calls, but for this project it's just as reasonable to #define __NR_getfds as 223 directly in your test program. Similarly, stubs normally get put into the C library as separate files, but you might as well define your stub in the test program. Incidentally, the handout doesn't make it clear that the function name mentioned in _syscall2 (foo in the example) must match the __NR_xxx symbol. Call it getfds and all will be well.

Your test program should call the getfds function twice, each time asking for information about 64 fds. You should check for errors and print an appropriate message if the call fails. After each call, the results should be printed on one line in hex, like this:

    3fadcbe 7f
(or whatever -- the above values are definitely wrong). Between the two calls, the program should use open, dup, or dup2 to create 35 new file descriptors.

Building an Emulab Kernel

To build a kernel on Emulab, you must first have a private source tree that you can work in. Unfortunately, there isn't enough disk space for us all to have our own trees. Therefore, I have set up a system that allows you to symlink large parts of the kernel that don't need to be privatized. Run the command ~geoff/maketree to create your own source tree in ~/usr/src/linux. (Note that the script takes a while to run.) The tree will be constructed so that you can change the files kernel/sys.c and arch/i386/kernel/entry.S, which should be the only files you need to modify.

If you screw up and want to start over, run ~geoff/zaptree. Note that this program will destroy your modified source files, so back them up first.

Once you have made a source tree, you can build a kernel by creating an experiment, cd'ing to ~/usr/src/linux, and typing "make bzImage". Note that you can only do builds from one of the PCs. The first time you do this, it will take a while, so I suggest that you start a kernel make immediately after you create the tree. Once you've made a kernel, subsequent builds will be reasonably fast.

Running an Emulab Kernel

To run your kernel, you must install it in /boot. The usual procedure (which you won't have to use) is:

  1. Make a backup copy of the kernel (which is normally named something like /boot/vmlinuz or /boot/vmlinuz-2.4.2-2) under a backup name such as /boot/vmlinuz.old.
  2. Copy your kernel (which is in arch/i386/boot/bzImage into /boot under a name such as /boot/vmlinuz or /boot/vmlinuz.new.
  3. Edit the boot configuration file, /etc/lilo.conf, to tell it about the new kernel. (Typically, you only need to do this once. On my own machines, I have an entry for vmlinuz.new, which can be booted by hand under the name devel, and an entry for vmlinuz.old, which can be booted under the name backup. When I start a new project, I back up my running kernel to vmlinuz.old in case I screw up so badly that the kernel won't boot. When I am sure that my kernel change works, I rename vmlinuz.new to vmlinuz so that it becomes the default.)
  4. Run the command lilo to tell the boot mechanism about the new kernel. This step is critical; otherwise you'll boot the old kernel even though the new one is in /boot/vmlinuz.

Since the above procedure is complicated, and since Emulab makes our lives easy (we can seriously screw up a machine by accident and they can recover it automatically), we have a much simpler procedure:

  1. Compile your kernel.
  2. Run sudo ~geoff/newkernel to install your new kernel.
  3. Use sudo reboot to reboot the machine (this will take a while, but eventually you'll get a login prompt).

If you decide your kernel is completely messed up, you can go back to the original kernel with the following steps:

  1. Run sudo ~geoff/oldkernel to revert to the known-good kernel.
  2. Use sudo reboot to reboot the machine.

Both of the above procedures must be run from the console, which is reached with tip from users.emulab.net. If you try them from an ssh connection, your connection will be closed as part of the reboot process.

After the reboot, you can double-check that you're running your own kernel by typing cat /proc/version. The kernel version string will include the date and time when you built your kernel, as well as your own login name.

Black Screens

When you connect to your test computer with tip, you may find that your screen goes into "reverse video" where the foreground and background colors are exchanged. You can cure this problem by running ~geoff/unblack.

Submission

Please use cs134submit to submit the following files:

If you use Emulab, you will have to use scp to download your source files onto Turing before you can submit them.

Resources

Handouts

There were two class sessions related to this project. You should have gotten handouts there. However, to save myself from requests from people who should know better, here are links to the handouts:

Web Sites

  • There is lots of stuff at kernelnewbies.org. Especially, there's good stuff in the documents directory and the procfs Guide.