Lab 2: Implementing System Calls
Due: Friday Oct 7, before 1am (very late Thursday night)

Problem Introduction
Implementation Details
What to Hand in
Preparing Your Demo

Introduction

For this project you will add two new system call to the linux kernel and write user-level programs that test your system calls:
  1. The getcurrenttime system call returns the system time value.
  2. The getprocinfo returns information about a running process specified by an argument value.
The goals of this lab are:
  1. To better understand system calls by implementing them in linux and testing them at user-level.
  2. To learn how to read linux kernel code.
  3. To learn some system resources for testing and verifying the correctness of your system calls.
  4. To learn about Linux's PCB data structure (task_struct)
  5. To get some practice thinking about concurrency.
  6. To learn how to use QEMU, and how to compile and install modified linux image and header packages.
  7. To learn how to demonstrate the correctness of your solution.

QEMU

This is the first of several lab projects that involve modifying the Linux kernel. For these projects, you will use QEMU running on one of the CS Lab machines for your Linux kernel development.

You and your partner should run QEMU only on the machine to which you have been assigned, so that we can better distribute QEMU load across the machines. In addition, it is important for correct networking between the host machine and qemu, that you do not run qemu on the same physical machine as any other group. You can always ssh into your machine from any other machine in our labs; you don't need physical access to your machine. See the machine assignments below.

Start by logging into your machine, and follow the "QEMU Start-up" instructions from the QEMU Guide for CS45 to set up QEMU so that you and your partner can share the same virtual machine. Once you have set up your QEMU environment, try running QEMU and booting and shutdown Linux from inside it. Next, try building the kernel from source and installing and booting it.

Qemu Machine Assignments
orange : Steven H. and Kevin P. cucumber: Stromme and Nick F. clove : Catie and Niels
cream : Christina and Kevin Li fennel : Jonathan and Elliot dill : Nick R. and Greg T.
cheese : Peter and Greg R. parsley : Jacob and Luis vanilla : Allen and Sam W.
salt : Sam C. and Steve D. perilla : Katherine and Becca sumac : Ben and Phil

Implementation Details

For all labs in this course, I strongly suggest you and your partner work together on all (or almost all) parts. For this lab I suggest first implementing the getcurrenttime system call together and after it is debugged and working, then implement the getprocinfo system call together.

Starting Point Code:
You can copy over lab2 starting point code from my public/cs45 subdirectory:

$ cp /home/newhall/public/cs45/lab2/* .
This contains examples that show you what you need to #include in user-level test programs for your kernel changes. It also includes a makefile for building user-level test programs (you may need to change . If you add new header files, you may need to include them in your user-level test program.

System call 1: getcurrenttime

You will add your new system call in a separate file in the kernel. The getcurrenttime system call returns the value of the kernel xtime variable to the user-level program that calls it. It should take 2 argument values, the first is a flag that if non-zero, will print out the time value at kernel-level. The second, is a struct timespec that is passed by value to getcurrenttime. getcurrenttime fills in the fields of the pasted struct to "return" the xtime value to the caller.
/*
 *  flag: if non-zero print out the time at kernel level
 *  tval: passed by reference to system call, the current time
 *        will be "returned" through this parameter
 */
long getcurrenttime(int flag, struct timespec *tval);
NOTE: as you modify existing kernel modules, first make a copy of of the .c or .h or .s file as orig_filename.c, then modify filename.c. This way if you really break something you can easily go back to the original kernel source.

To get some ideas of how to implement the system call function, you may want to look at other example code in kernel/ that access xtime, and look at the linux/time.h header file for interface functions and type definitions.

Steps for implementing a system call:

There are three general steps to implementing a system call:

  1. Adding a new system call number to the kernel.
  2. Adding an entry to the kernel's system call table, associated with your system call number, that specifies which kernel function to invoke when there is a trap with your system call number.
  3. Adding code to the kernel that implements your system call functionality. This may include adding header files for new type definitions used by the interface to your system call. The implementation may require copying system call arguments from user to kernel space on entry, and then copying them back from kernel space to user space before returning from the system call.

Details: In this example, I'm adding a new system call named getsecretcode, that takes has one int parameter and one int passed by reference that the kernel fills with a secret code. It returns 0 on success and an error value otherwise.

  1. Adding a new system call number to the kernel
  2. Adding an entry to the system call table

    Changes for 64-bit x86 (this is our platform):

    • in linux-2.6.32.44/arch/x86/include/asm/unistd_64.h add a new entry:
        #define __NR_getsecretcode                             299
        __SYSCALL(__NR_getsecretcode, sys_getsecretcode)
      
      If you look in the file linux-2.6.32.44/arch/x86/kernel/syscall_64.c, you can see how the 64-bit system call table is generated. The change that you make in unistd_64.h will result in header files being added to include/asm-generic and in include/asm-x86/ with your new system call information in it.

    Changes for 32-bit x86. You DO NOT need to make these changes for building on our system; this is just to show you that this part of the kernel is architecture-specific, and to port your system call to other platforms you would need to modify all the architecture-specific files:

    • in arch/x86/kernel/syscall_table_32.S add a new entry:
          .long sys_rt_tgsigqueueinfo     /* 335 */
          .long sys_perf_event_open
          .long sys_getsecretcode
      
    • in linux-2.6.32.44/arch/x86/include/asm/unistd_32.h add:
          #define __NR_perf_event_open    337
          #define NR_syscalls 338
      
  3. Implement your system call: add source and header files, and add function prototypes for it

    1. at the bottom of linux-2.6.32.44/include/asm-generic/syscalls.h add a function prototype for your system call:
        #ifndef sys_getsecretcode
        asmlinkage long sys_getsecretcode(int flag1, int flag2);
        #endif
      
    2. if you have new type definitions that are needed at user-level, add a new header file in linux-2.6.32.44/include/linux/ (you don't need to do this for getcurrenttime):
      /* 
       * file: getsecretcode.h with really good comments
       */
      #ifndef __GET_SECRET_CODE_H__
      #define __GET_SECRET_CODE_H__
      // add any type definitions etc. that may be needed at user-level here
      // you can look at other .h files in here to see some examples
      
      // anything between "#ifdef __KERNEL__"  and "#endif"  is only visable at kernel-level
      #endif
      
    3. add a new file to the kernel that contains your system call code. You should add the file in linux-source-2.6.32.44/kernel/. Your system call function must have "asmlinkage" prepended to its header and a "sys_" prefix to its name, but you should use the SYSCALL_DEFINEX macros that will generate this for you. For example:
      /*
       * file: getsecretcode.c 
       */
      #include <linux/kernel.h>   // can only be included at kernel-level
      #include <asm/uaccess.h>    
      #include <linux/syscalls.h> 
      
      // you also may need to include other header files based on what 
      // types and functions you want to access (linux/time.h might be useful for getcurrenttime)
      
      /* getsecretcode system call: 
       *    an awesome comment 
       */	
      SYSCALL_DEFINE2(getsecretcode, int , pflag, int __user *, codeval) {
      
         /* to print a debug message use printk, which is similar 
          * to printf: the kernel's stdout goes to the console and
          * to the files: /var/log/[syslog/kern.log]
          */
         printk("Inside system call getsecretcode\n");
      
         /* copy argument values that are passed by reference from user 
          * space to to kernel space:
          *
          * (1) first call access_ok() to check if the space
          *     pointed to by thetime is valid
          * (2) then call copy_from_user() to copy to kernel space
          *
          * note: pflag's value is passed on the stack so it does not need to be 
          *       copied to kernel space...its value can be grabed off the stack
          */
      			
         /* If you access any kernel variables that could be
          * modified by interrupt handlers that interrupt our syscall,
          * or by other processes simulteneously running in kernel mode,
          * then you need to put some synchronization around their access.
          * For existing kernel objects, there should be a lock or semaphore 
          * you can use (in inlcude/linux there are interface files to 
          * spinlock, seqlock, and semaphore)  For new kernel state you add, you 
          * need to add spinlock or semaphores (this is likely not necessary 
          * for lab2 where you are accessing existing kernel state)
          */
      
         /* copy pass by reference "return" values to user-space
          *
          *  (1) first call access_ok() to check if the space
          *      pointed to by codeval is valid (if you have not already done so)
          *  (2) then call copy_to_user() to copy the value to user space
          *
          *  You CANNOT directly access the space pointed to by codeval.
          *  Instead you need to copy values to/from kernel space to the
          *  space pointed to by the pass-by-reference parameters.
          */
      
          /* a successful return from the system call */
          return 0;
      }
      
    4. Modify linux-source-2.6.32.44/kernel/Makefile to include your new file. Add a line like this:
      obj-y += getsecretcode.o   # add this right before the following line
      obj-y += groups.o 
      
      Then, re-build the linux kernel.

Testing your system call

Specific implementation hints:

System call 2: getprocinfo

After you have the getcurrenttime system call implemented and tested, next implement a system call named getprocinfo. It takes two arguments: a process identifier value; and a reference to a process information struct that you need to define in a new header file. getcurrenttime fills in the field values of the second argument with information about the specified process. If the pid argument is 0, then getprocinfo should "return" information about the calling process, otherwise it should "return" information about the process with a matching pid value. The system call returns 0 on success, and one of the following error values otherwise (feel free to add additional error return values): (ESRCH, EINVAL, ... , are defined in linux/errno.h). Take a look at how other system calls return error values to figure out how your code should do this.

Start by defining a the proc_info_struct in a new header file that you create in include/linux/. The struct should have the following fields:

  pid_t pid;                /* pid of process */
  pid_t parn_pid;           /* pid of its parent process */
  unsigned long user_time;  /* total CPU time in user mode*/
  unsigned long sys_time;   /* total CPU time in system mode*/
  long state;               /* its current state */
  unsigned long long sched_avg_running; /* its scheduled ave running time */
  unsigned int time_slice;   /* its scheduling time slice */
  unsigned int policy;       /* its scheduling policy */
  unsigned long num_cxs;     /* number of context switches it has had  (sum of voluntary and involuntary cxs) */
  pid_t gid;                 /* its group id */
  int num_children;          /* the number of child processes it has */
  char prog[16];             /* its exec'ed file name (e.g. a.out) */
You can fill in these values by accessing a process' task_struct that is defined in include/linux/sched.h. Field values in the proc_info_struct that correspond to null pointer values in the process' task_struct should be set to -1. Not all field values in your struct match the names of fields in the task_struct, so you may need to read through some code and/or try some things out before you get the right values for these fields. Additionally, some values may need to be obtain indirectly via task_struct fields. Types are defined in types.h files in archetecture neutral and archetecture specific subdirectories of include. For example: linux-source-2.6.32.44/include/linux/types.h. Errors are defined in linux-source-2.6.32.44/linux/asm-generic/errno.h

A few things to help you determine what to do and if your system call returns correct information:


Hand In

Submit the following via cs45handin before the due date:
  1. A README file with the following information:
    1. You and your partner's names
    2. The total number of late days that you have used so far
    3. A list of the linux source files you modified (list the complete path name of each file)
    4. A list of any features that you do not have fully implemented
  2. Copies of the kernel source and header files that you modified/added for this project (I only want the files you modified or added, do not submit a tar file containing all the kernel code).
  3. Your test programs that contains multiple calls to your new system call that demonstrate how your system call handles valid and invalid input.
In addition, you and your partner should sign up for a 15 minute demo time where you will run your test program for me and show me that it works. A demo sign-up sheet will be outside my office door.

Preparing for a Demo

During your 15 minute demo slot you and your partner will demonstrate that your solution works. It is up to you to determine how to demonstrate this to me. When I meet with you, your kernel should be up and running on QEMU (unless there is something that happens during the boot process that you want to show me), and your demo should be ready to run; if you spend your entire demo slot setting up QEMU, then I can only conclude that your solution does not work.

A demo is something that you and your partner should practice before you give it; you want to make sure that it runs correctly and that it demonstrates that your solution is correct and complete. Make sure that you are demo'ing both how your system call works under normal conditions and how you are handling error conditions. Also, be prepared to answer questions during your demo about your implementation and about your test programs.

Often times demonstrating that your solution works means that you will need a way to run a version of your kernel with debugging output enabled, and you may need to show via unix commands or /proc information that your system calls obtain the correct information or do the right thing. In addition, for most demos, you will want to write one or more interactive demo applications (menu driven program), where you choose from a menu of options for invoking your system call(s), execute a system call, examine system state or kernel outuput to verify that it did the right thing, then choose the next system call to execute, and so on; you want to be prepared to discuss and to demonstrate the effects after any single system call.