Assignment 3: Adding a Linux system call

This assignment is to be completed as a group.

Building a Kernel

For this and each of the following programming homework assignments, you will build a new Linux kernel. The image of the kernel you build for this assignment should be vmlinuz.hmwk1. Grading for this assignment will be done based on vmlinuz.hmwk1. Note that the version of Linux running as the Host OS on the CLIC machines of the Guest OS that you will modify running in VMware.

  1. Compile and install your own Linux kernel on your VM. This can be a daunting task at first, and if you aren't careful, you can run into a lot of problems whose causes are not easily traced. There are many web-based resources with instructions on compiling your own kernel. A good place to start is the Kernel Newbies FAQ. The Linux Kernel HOWTO also describes how to do this in some detail, but is a bit dated.
  2. Download the latest kernel source (2.6.17-13) from kernel.org; we recommend that you build your kernel within vmware.
  3. First, get the vmware IP address from within the VM by typing ifconfig in the shell. It should look like 172.16.85.xxx.
  4. scp the kernel source into your VM from the host OS:
      scp linux-2.6.17.tar.bzip2 root@172.16.85.xxx: 
    
    Don't forget the colon at the end!
  5. Extract the linux source from within the VM:
      tar xjf linux-2.6.17-13
    
  6. The source will extract into a directory called linux-2.6.17-13 (for a version 2.6.17-13 kernel). Don't touch this directory! As we'll see, it's useful to have a copy of the pristine sources lying around. Instead, make a copy with the command cp -al linux-2.6.17-13 linux-2.6.17-pudding, replacing pudding with your UNI or some other string that will uniquely identify your kernel. The -al flags to cp make the copied tree use hard links instead of full copies of files.

    A little care is needed to make sure the linked files are copied when written. If you use emacs to edit, you should have no problem: when you first make a change to a file in emacs, it moves the original file link to a backup (the ~ file), and makes a copy to work on. Likewise, the patch program moves the original link out of the way and makes a copy before patching a file. However, if you use vi, you will have to run mv file file~; cp file~ file before you start editing. Yes, this may seem like a pain in the neck right now, but you'll be glad for the time saved, based on an earlier kernel measurement:

      $ time cp -a linux-2.6.17-13 linux-2.6.17-realcopy 
      real 0m41.476s user 0m0.140s sys 0m3.340s 
      $ time cp -al linux-2.6.17-13 linux-2.6.17-linkcopy 
      real 0m0.428s user 0m0.030s sys 0m0.400s 
    

    Also, comparing files with the original sources is much quicker:

      $ time diff -Naur linux-2.6.17-13 linux-2.6.17-realcopy > /tmp/patch1 
      real 0m2.615s user 0m1.250s sys 0m1.360s 
      $ time diff -Naur linux-2.6.17-13 linux-2.6.17-linkcopy > /tmp/patch2 
      real 0m0.278s user 0m0.160s sys 0m0.120s 
    
  7. To compile the kernel so that it recognizes your hardware, you need to set up a configuration file. We have generated a sample configuration file that you may used within the vmware environment. This config file is confirmed to work with your recently downloaded kernel version, as well as with the patched one you will be building for part 2 of this assignment.
  8. Get the config file from the w4118 account:
       cp ~w4118/vmware/config-2.6.17-13 . 
    
  9. In order to use this config file, simply copy it as .config in the toplevel of your source tree within the VM and type make oldconfig. The kernel should configure itself without any input necessary on your part.
  10. Now simply type make to build the kernel. Once the kernel build process is complete, the kernel image will be stored as a file called bzImage in /arch/i386/boot.
  11. Copy this file to your /boot directory in the vm and rename it vmlinuz.hmwk1.
  12. Now you need to build the dependencies and set up the ramdisk initialization files. In the toplevel kernel directory type:
      make modules 
      make modules_install 
    
  13. Next, create a dependency file:
      depmod -ae 2.6.17-13
    

    This will set up the modules.dep file in /lib/modules/2.6.17.13/ check to make sure that it's there.

  14. Now make the initrd file:
       mkinitrd /boot/initrd-2.6.17.img initrd-2.6.17
    

    This will create this file: /boot/initrd-2.6.17.img. Verify that it has been created.

  15. Now you're ready to modify the grub loader to boot your kernel. To let the bootloader, grub, know where to find your kernel, edit the file /boot/grub/grub.conf by adding the following lines:
      title HW1 Kernel 
      root (hd0,0) 
      kernel /vmlinuz.hmwk1 ro root=/dev/VolGroup00/LogVol00 
      initrd /initrd-2.6.17.img 
    

    Whatever you put on the line next to title will be displayed as the name of your kernel image the next time you boot.

  16. Now you're all done. Reboot the machine and you should see your new kernel in the grub loader menu.

Notes

Do not remove the default kernel. You need a fail safe way to boot your VM in case your own kernel doesn't work.

Writing a New System Call

Write a new system call in Linux. The system call you write should take one argument and return the process state information for that process. Note that you will be adding this system call to the kernel based on the 2.6.17-13 kernel you previously built with kernel debugging installed. The prototype for your system call will be:

 int pinfo(struct pinfo *info); 
pid_t is defined in /usr/include/sys/types.h.

You should define struct pinfo as

struct pinfo { 
 int pid;                   /* process id */ 
 long state;                /* current state of process */ 
 long nice;                 /* process nice value */ 
 int parent_pid;            /* process id of parent */ 
 int children;              /* total number of children */
 int youngest_child_pid;    /* pid of youngest child */ 
 int younger_sibling_pid;   /* pid of younger sibling */ 
 int older_sibling_pid;     /* pid of older sibling */ 
 unsigned long start_time;  /* process start time */ 
 long user_time;            /* CPU time spent in user mode */ 
 long sys_time;             /* CPU time spent in system mode */ 
 long uid;                  /* user id of process owner */ 
 char comm[16];             /* name of program executed */ 
}; 
in /usr/src/linux/include/linux/pinfo.h as part of your solution.

Your system call should return 0 unless an error occurs. Your code should handle errors that can occur but not handle any errors that cannot occur. At a minimum, your system call should detect the following conditions and respond as described:

If the address for the pinfo structure is null, return -22.

If a value to be set in pinfo is accessible through a pointer which is null, set the value in pinfo to -1. For example, the youngest_child_pid should be set to -1 if the process does not have a child. The referenced error code is defined as EINVAL in /usr/src/linux/asm/errno.h.

You should start by reviewing the "How System Calls Work in Linux/i86" section of the Linux Kernel Hacker's (LKH) Guide. Note that because the Linux kernel changed over time, the procedure is no longer exactly correct. However, there are messages appended at the end of the section that will help significantly. Two more recent articles that discuss how to add system calls can be found ?here? and ?here?.

Each system call must be assigned a number. Your system call should be assigned number 222.

You should create a patch to store changes you've made to the kernel and to submit your changes for this problem. If you've changed 50 lines of code, a patch will be about 50 lines long. This is much easier to store, email, or move around than the full 283,000-line source tree. To create a patch, first back up your .config file, then do a make distclean in your changed source tree (you don't want object files, config files, etc. in your patch). Then, from the directory above your source tree (e.g. /vmware1/src), run

  diff -ruN linux-2.6.17-13 linux-2.6.17-changed > changed.patch 

Notice that the original source tree is first, and the changed second. There is a particular "algebra" to patches. You may find it helpful to think of diff as subtracting two source trees. The result of this subtraction is a patch. To apply a patch, then, is to add this difference back. If you supply the -R option to the patch program, you subtract the difference, instead of adding. If you thus "subtract the difference" from your changed tree, you end up with the original tree. So, to contain all the information of n kernel trees, all you need is the n - 1 patches between them, and only one full kernel tree (and it can be any one of the n).

Linux maintains a list of all processes in a doubly linked list. Each entry in this list is a task_struct structure, which is defined in /usr/src/linux/include/linux/sched.h. In /usr/src/linux/include/asm/current.h, current is defined to inline a function which returns the address of the task_struct of the currently running process. All of the information to be returned in the pinfo structure can be determined by starting with current.

In order to learn about system calls, you may also find it helpful to search the Linux kernel for other system calls and see how they are defined. The file kernel/sched.c might give some useful examples of this. The getpid() and getuid system calls might be useful starting points. The system call sys_getpid defined in /usr/src/linux/kernel/sched.c uses current and provides a good reference point for defining your system call.

Your kernel has partially reserved a slot in the syscall table for ni_syscall (when you see it, you will know what we are talking about). Since ni_syscall means "not implemented", do not be afraid to commandeer that slot.

To test your system call, write a simple program that calls pinfo. Your program should print all the process state information for the calling process. Run the program several times. Which fields in the pinfo structure change? Which ones do not? For each field in the pinfo structure, discuss how frequently it changes and why as part of your writeup.

Although system calls are generally accessed through a library (libc), your test program should access your system call directly. This is accomplished by utilizing the appropriate _syscall macro in /usr/include/asm/unistd.h. As explained in LKH, which _syscall macro you use is determined by the number of arguments passed to your system call.

The output of the program should be easy to read, and the program should check for errors such as invalid input or too many or too few arguments. The ps command will provide valuable help in verifying the accuracy of information printed by your program. You can access detailed information on the ps command by entering man ps.

One of cool features of Linux is the ease with which you can implement kernel modules. Kernel modules are pieces of code that can be added to a running kernel; they are often used for the implementation of device-drivers. Write a loadable kernel module that creates the same system call as question 3 of this assignment, this time called pinfo2. This system call should occupy index number 223 of the syscall table. Remember that you should be compiling your module on the host machine, but you will need to specify the correct include path for the version of the kernel that you are using in your Makefile. In order to learn about kernel module programming, you should check out The Kernel Module Programmer's Guide as well as Loadable Kernel Modules. Additional Information:

Remember, as a safety measure, you are strongly encouraged to copy the source files you plan to modify to your home directory on the host system.

printk() statements in system calls will print their output to the console whenever they are invoked. To request printk() messages be sent to a log file, insert the following line into the /etc/syslog.conf file:

  kern.*  /var/kern.log 

This will cause printk() messages to be written to /var/kern.log. You can send a signal to request the syslog daemon re-read the updated /etc/syslog.conf file, with the command kill -1 pid where pid is the process ID of the syslogd process.

A lot of your problems will come from system administration issues. If nobody in your group in familiar with unix, you might want to pick up a book on system administration or consult some of the guides at The Linux Documentation Project.

Some people are confused by the virtual network connection between the host and guest VMs. Usually for all ftping, sshing and telneting, you must specifically identify the target machine's IP address. Usually, the host sees the VM as 172.16.156.128 and the VM sees the host (cityname.clic.cs.columbia.edu) as 172.16.156.1. If these numbers do not work, type /sbin/ifconfig to see what numbers have been assigned (they should be pretty close to these). In cases where more than one VM is running simultaneously, different network numbers might be established.

To add to the previous point, your group should not, under any circumstances, be running two or more instances of your VM simultaneously. This often happens through the use of VNC. View the VM itself as a critical section and use some method (e.g., emailing your fellow group members) to enforce mutual exclusion.

What to Submit

You must submit a single file named your UNI.tar.gz or your UNI.tgz. Any other filename and we will dock points! That tar file should contain one subdirectory named p1 which should contain the kernel patch, the readme file, sample output, and testing programs. Your readme file should describe what changes you made to the kernel, how to run your test programs and what the expected output should be. For each programming part, if your program(s) do not work, you must submit file named nonworking.txt. Inside this file, you need to describe what problems you ran into, where your program fails and what you think are the reasons.

Do not remove old kernels, keep them around because we will need to login to your VM and test them. So your boot loader should have:

in addition to the default kernel.
Last updated by Henning Schulzrinne