CS 232 Project 4: Kernel Programming


This 3-person group project is to learn how system calls are implemented by implementing a few yourself. To do this, you will have to modify Linux kernel source files. The Linux kernel is written in C, not C++, and so you must do this project in C.


We have seen that most Unix-variant operating systems are layered systems, with user-level programs at the top layer, and the OS kernel at the bottom layer, just above the host computer's actual hardware.

Our previous assigments have involved writing code that runs at the user's level -- in user mode. In this assignment, we will for the first time write code that runs at the kernel level -- in supervisor mode. As such, the code you write will have full control over and access to the kernel, its data structures, and the machine's hardware, including priveleged instructions.

Because the kernel is the foundation of the operating system, an error in the kernel's code may render the host computer unuseable. Since

it would be potentially catastrophic for you to modify the actual kernel on one of our lab machines. (Also, we don't want to give out the root password for Linux on the lab machines.)

Instead, we will use the virtual machine approach we saw earlier in the course. More precisely, you will run your own personal copy of Linux on top of a virtual machine called VMWare, which is running on top of the copy of Linux that is running directly on your machine's hardware:


In this "top" copy of Linux, you will have the root password, and thus can be the system administrator, without "endangering" anyone or anything else in the lab.

To work on this assignment, you must thus run VMWare, and then boot your own copy of Linux to run on top of VMWare. To store your own copy of Linux, we have provided you with what VMWare calls a virtual disk image -- a special file that VMWare treats like a disk volume. Your virtual disk image has been preloaded with a copy of Linux. By specifying this disk as a boot device for VMWare, you can boot your own copy of Linux, on top of VMWare, and login as its system administrator. You may then modify the kernel of this "top" copy of Linux at will, without harming the kernel of "bottom" copy on which VMWare is running (and which is used by the students in other courses).

By the time you read this, your VMWare virtual disk should be in your home directory. Since your home directory is available on each machine in the Systems Lab, your virtual disk will be accessible from any machine in the lab that Chris has set up to mount the home directories. You can thus work on this assignment from any of those machines. You should read these directions, taking special note of how long kernel recompilations may take. This document also describes your login information.

Your VMWare virtual disk should contain a full installation of Linux, plus the C source code for the Linux kernel, in the /usr/src directory. By modifying this kernel source code and then recompiling your kernel, you can change its behavior. Besides altering the existing kernel source code, you may also access or alter existing kernel data structures, create new data structures, write new kernel functions, and so on.

Creating a system call for Ubuntu involves three steps:

  1. Define the system call by adding it to an existing file containing other system calls (e.g., /usr/src/linux/kernel/sys.c). Examining the other system calls in this file may prove informative as to how you should define yours. Be sure to define it using the SYSCALL_DEFINE0 (or SYSCALL_DEFINE1, ...) directive.
  2. Give your system call a number, which will be its index in the kernel's table of system calls. This number should be added at the end of the list in /usr/src/linux/arch/x86/include/asm/unistd_32.h, and you should follow the naming convention indicated in that list. When you do so, you will also be declaring an identifier (e.g., __NR_printMyName) that can be used for that number (i.e., a constant).
  3. Create the table entry for your system call. This is done by adding an entry at the appropriate spot in the system call table whose entries have the form:
          .long SYMBOL_NAME(sys_*)
    in /usr/src/linux/arch/x86/kernel/syscall_table_32.S. Again, be sure to follow the naming conventions of the other entries in this table, and use a descriptive name.
  4. Create a "wrapper function" for the system call that cleans up its interface, and store this wrapper in a publicly accessible directory. (While it is not generally a good idea to do so, for this assignment feel free to "cheat" by storing the definition of your wrapper function in a .h file in your /usr/include directory.) Your wrapper function should use the syscall() system call (see its description in section 2 of the online manual) to invoke your system call via the identifier you gave its number in #2 above, and pass it any parameters it requires.
  5. Recompile your kernel and then reboot it so that your modifications take effect.
  6. Create a program that tests your system call by #include-ing the file containing your wrapper function and then invoking the wrapper. Run this program to test your system call.
Your assignment is to use this procedure (at least) three times to create three new system calls and "wrapper" functions:

Plan of Action.

In the steps below, all paths assume a starting point of /usr/src/linux/.

  1. Read over these directions, especially the steps for recompiling the Linux kernel; then work through all of the steps in those directions.
  2. Start VMWare again. Make some trivial change to the kernel whose effect you will notice (e.g., add a printk() statement to the do_fork() function in /usr/src/linux/kernel/fork.c, so that a message is displayed each time the function gets called). Recompile your modified Linux kernel source code; then reboot your modified kernel to verify that your modified kernel works as expected. Output from printk() goes to /var/log/kern.log
  3. Using the steps given above, create your first (simple) system call and wrapper that uses the kernel's logging function printk() to print your name to the screen, plus a program to test it. Recompile your kernel, reboot VMWare, and run your program to test it. Do not continue to the next steps until you have this working correctly.
  4. Using the steps given above, create a system call and wrapper that receives a character array buf and its size n from the caller, and then copies up to n letters of your name into buf. Write a simple test program that declares a character array and an integer variable containing its size, passes these values to the system call, and then displays the contents of the array to verify correctness. Recompile your kernel; then reboot VMWare and run your program to verify that your system call works as expected. As before, do not continue to the next step until this is working correctly.
  5. Using the steps given above, create a system call and wrapper that receives a character array buf and its size n from the caller, and then copies into buf up to n letters of the name of the owner of the currently executing process. Write a simple test program that declares a character array and an integer variable containing its size, passes these values to the wrapper, and then displays the contents of the array to verify correctness.
You may use the same program to test all three of your system calls.

Note that kernel recompilations are relatively time-consuming, as are reboots, so you want to minimize your recompilations.

One way to do this is to invest time in a quality design so that you can focus on coding an already correct algorithm. If you try to use the compiler to debug a poorly designed algorithm, you are unlikely to complete this assignment in the allotted time.

Another way is to implement absolutely as much as you can in a standalone, user-level (i.e., regular) C program before you try it in the kernel. Find all the bugs in your code before you insert it into the kernel, if possible.

Another way is to design and code in pairs. When it comes to avoiding and/or finding errors (logic or syntax), two heads may be much better than one. Normal debuggers run in user-mode; so they are essentially useless when it comes to debugging at the kernel level. Debugging a logic error thus consists of inserting output statements (printk()) into your source, recompiling, and tracing the output. Given the time required to recompile, it is essential that you minimize recompilation; so you should make every effort to ensure that your logic is correct ahead of time.

Another way to save (a little) time is to write a shell script to perform the copy operation used to install your new kernel following each compilation.

If you get stuck, come and see me for some suggestions.

As in any project, you are to use descriptive identifiers, and in-line comments that explain any 'tricky' parts. You should document the changes you've made at the beginning of any file you modify, including the author, date, and purpose of the modification.

Turn in: Each team should submit to one team member's directory in /home/cs/232/current/<yourid>/proj4/ the following:

To save disk space, please delete parts of large source files, leaving the context surrounding your modifications. When you do this, please INSERT TEXT INTO YOUR SCRIPT FILE TO highlight your modifications (e.g., where extraneous stuff was cut out), so that the grader can find the good stuff easily. E.g., you might put this into the script file:


If there is controversy about how much an individual on the team contributed to the project, each individual may submit their own grading.txt file into /home/cs/232/current/<yourid>/proj4/ .

Due date: Thursday, Apr. 30, at 11:59 pm.

Warning: This is the most time-consuming project of the semester. You will likely need the entire time to get it working correctly. Begin work on it immediately!

Calvin > CS > 232 > Projects > 4

This page maintained by Victor Norman.