CS 1550 – Project 2: Syscalls

Due: Friday, June 29, 2007 at 11:59 p.m.

Project Description

 

We know from our description of the kernel that it provides fundamental operations that help us to interact with resources. These system calls include things like read, write, open, close, and fork. With access to the source code for the Linux kernel, we have the ability to extend the kernel’s standard functionality with our own.

In this assignment we will be creating a simple interface for passing small text messages from one user to another by way of the kernel.

How it Will Work

 

There will be a userspace application called osmsg that allows a user to send and receive short text messages. To send a message the user will issue a command like:

osmsg -s jrmst106 “Hello world”

which will queue up a message to be sent to the user jrmst106 when they get their messages with the following command:

osmsg -r

This would output something like the following:

abc123 said: “Hello world”

Notice that this is more like email than instant messages, due to the fact that the recipient needs to explicitly request their messages. If there is more than one message waiting to be received, they will all be displayed at once. A message is discarded from the system after it has been read.

Syscall Interface

 

The osmsg application will do the sending and receiving of messages via two new system calls, which you will implement. The syscalls should look like the following:

asmlinkage int sys_cs1550_send_msg(const char __user *to, const char __user *msg, const char __user *from)

This function sends a single message given the three string parameters. The return value should be 0 for success, and a negative number on error.

asmlinkage int sys_cs1550_get_msg(const char __user *to,  char __user *msg,  char __user *from)

This function takes the recipient’s username as an input, and then returns via the two other parameters the message and sender. Notice that this only returns one message, but there may be many messages to deliver. The return value should be used to indicate if there are more messages to receive: 1 means to call the function again, 0 means that there are no more to deliver, and a negative number indicates an error. The msg and from pointers need to be pointing to space allocated in the user program’s address space.

Implementation

 

There are two halves of implementation, the syscalls themselves, and the osmsg program.

One obvious need we have is a place to queue the sent messages until the recipient requests them. For this, let us use a dedicated file /etc/.osmsgqueue

This file begins with a dot, so it is hidden from normal ls requests. Do ls -a  to see such “hidden” files.

Adding a New Syscall

 

To add a new syscall to the Linux kernel, there are three main files that need to be modified:

  1. linux-2.6.21/kernel/sys.c

This file contains the actual implementation of the system calls.

  1. linux-2.6.21/arch/i386/kernel/syscall_table.S

This file declares the number that corresponds to the syscalls

  1. linux-2.6.21/include/asm/unistd.h

This file exposes the syscall number to C programs which wish to use it


Reading and Writing Files in Kernel Space

 

/******************************************************************************

 * Wrapper function to read from a file in kernel space.

 *

 * Arguments:

 *    filename - a string allocated in kernel space

 *    data - a pointer to the data allocated in kernel space to write

 *    length - how many bytes of data to write

 *

 * Return value:

 *    returns the number of bytes actually read or a negative number on error

 *

 * Additional comments:

 *    Generally, reading and writing files from the kernel is frowned upon.

 * This is due to the fact if said files are configuration files, it makes it

 * a nightmare to deal with where different Linux distros put the file. For

 * us, we're just learning how to use the syscall interface, and seeing that

 * things are generally done differently inside the kernel.

 *****************************************************************************/

int cs1550_file_read(const char *filename, void *data, int length)

{

       struct file *fin;

       mm_segment_t save_fs;

       int actual; //how many bytes did we actually save

 

       //Open the file for read only access

       fin = filp_open(filename, O_RDONLY, 0);

 

       //Something went amiss... hope we don't ever see this

       if (!fin || IS_ERR(fin)) {

              printk("cs1550_file_read err %d\n", (int) PTR_ERR(fin));

              return -1;

       }

 

       //reset the file pointer to the start of the file

       //You may want to change this (parameterize?) or put read in a loop

       fin->f_pos = 0;

   

       //We need to tell the filesystem the buffer is in kernel, not user, space

       save_fs = get_fs();

       set_fs(KERNEL_DS);

   

       actual = fin->f_op->read(fin, data, length, &fin->f_pos);

   

       //Restore it to whatever it was before and close

       set_fs(save_fs);

       filp_close(fin, NULL);

 

       //return how many bytes we actually read

       return actual;

}


The code above illustrates how to read from a file in kernel space. You will probably want to do something resembling this to place messages in the queue file using the corresponding write() function. These functions can go in the kernel/sys.c file, but should not be exported as syscalls.

Setting up the Kernel Source (To do in recitation)

 

  1. Copy the linux-2.6.21.tar.bz2 file to your local space under /u/OSLab/username
    cp /u/OSLab/original/linux-2.6.21.tar.bz .
  2. Extract
    tar xfj linux-2.6.21.tar.bz2 .
  3. Change into linux-2.6.21/ directory
    cd linux-2.6.21
  4. Copy the .config file
    cp /u/OSLab/original/.config .
  5. Build
    make bzImage

You should only need to do this once, however redoing step 2 will undo any changes you've made and give you a fresh copy of the kernel should things go horribly awry.

Rebuilding the Kernel

 

To build any changes you made, from the linux-2.6.21/ directory, simply:

make bzImage

Copying the Files to QEMU

 

From QEMU, you will need to download two files from the new kernel that you just built. The kernel itself is a file named bzImage that lives in the directory linux-2.6.21/arch/i386/boot/  There is also a supporting file called System.map in the linux-2.6.21/ directory that tells the system how to find the system calls. 


Use scp to download the kernel to a home directory (/root/ if root):

scp USERNAME@os.cs.pitt.edu:/u/OSLab/USERNAME/linux-2.6.21/arch/i386/boot/bzImage .

scp USERNAME@os.cs.pitt.edu:/u/OSLab/USERNAME/linux-2.6.21/System.map .

Installing the Rebuilt Kernel

 

As root (either by logging in or via su):

cp bzImage /boot/bzImage-devel

cp System.map /boot/System.map-devel

and respond ‘y’ to the prompts to overwrite. Please note that we are replacing the -devel files, the others are the original unmodified kernel so that if your kernel fails to boot for some reason, you will always have a clean version to boot QEMU.

Under some circumstances you may need to update the bootloader when the kernel changes. To do this (and you can do it every time you install a new kernel if you like) as root type:

lilo

lilo stands for LInux Loader, and is responsible for the menu that allows you to choose which version of the kernel to boot into.

Booting into the Modified Kernel

 

As root, you simply can use the reboot command to cause the system to restart. When LILO starts (the red menu) make sure to use the arrow keys to select the linux(devel) option and hit enter.

Implementing and Building the osmsg Program

 

As you implement your syscalls, you are also going to want to test them via your co-developed osmsg program. The first thing we need is a way to use our new syscalls. We do this by using the syscall() function. The syscall function takes as its first parameter the number that represents which system call we would like to make. The remainder of the parameters are passed as the parameters to our syscall function. We have the syscall numbers exported as #defines of the form __NR_syscall via our unistd.h file that we modified when we added our syscalls.

However if we try to build our code using gcc, the <linux/unistd.h> file that will be preprocessed in will be the one of the kernel version that os.cs.pitt.edu is running and we will get an undefined symbol error. This is because the default unistd.h is not the one that we changed. What instead needs to be done is that we need to tell gcc to look for the new include files with the -I option:

gcc –o osmsg –I /u/OSLab/USERNAME/linux-2.6.21/include/ osmsg.c

Running osmsg

 

We cannot run our osmsg program on os.cs.pitt.edu because its kernel does not have the new syscalls in it. However, we can test the program under QEMU once we have installed the modified kernel. We first need to download osmsg using scp as we did for the kernel. However, we can just run it from our home directory without any installation necessary.

File Backups

 

One of the major contributions the university provides for the AFS filesystem is nightly backups. However, the /u/OSLab/ partition is not part of AFS space. Thus, any files you modify under your personal directory in /u/OSLab/ are not backed up. If there is a catastrophic disk failure, all of your work will be irrecoverably lost. As such, it is my recommendation that you:

Backup all the files you change under /u/OSLab to your ~/private/ directory frequently!

Loss of work not backed up is not grounds for an extension. You have been warned.

Hints and Notes

 

Requirements and Submission

 

You need to submit:

 

Make a tar.gz file as in the first assignment, named USERNAME-project2.tar.gz

Copy it to ~jrmst106/submit/ by the deadline for credit.