CSE 422S: Studio 3

Linux System Calls


`What does it mean by speak, friend, and enter?' asked Merry.

'That is plain enough,' said Gimli. `If you are a friend, speak the password, and the doors will open, and you can enter.'

The Fellowship of the Ring, Book 2, Chapter 4

System calls are the fundamental, most stable interface that is provided by the operating system. They are how user programs request the vast majority of kernel actions: creating, reading, and destroying files; allocating and freeing dynamic memory; executing new programs, etc.

In this studio, you will:

  1. Make system calls with the libc wrapper
  2. Make system calls with the native Linux interface
  3. Write your own system call

Please complete the required exercises below, as well as any optional enrichment exercises that you wish to complete.

As you work through these exercises, please record your answers, and when finished email your results to eng-cse422s@email.wustl.edu with the phrase System Calls Studio in the subject line.

Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.


Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.

  2. First we're going to make a system call through the libc wrapper. This is the standard way that user programs make calls to the kernel. You can find a complete list of system calls in the manual pages with the command man syscalls.

    Boot up your Raspberry Pi, open up a terminal window, and outside of the linux source directory in which you are keeping built versions of the Linux kernel make a new directory for your userspace programs (you will use sftp to transfer files from shell.cec.wustl.edu into this directory).

    Log into shell.cec.wustl.edu, and (outside the linux source directory there as well) also create a new directory for your user space programs. In that directory create a new file called lib_call.c. In Linux, all users have an associated user ID number, and the Linux library functions getuid and setuid can be used to get and set that id, respectively. In that file, write a short C program that reads the user ID and prints it out, attempts to set it to 0 (the root uid) and indicates whether or not that attempt was successful, and then again reads and prints out the user ID.

    Use the man pages at man getuid and man setuid to understand (1) what syntax is used to call those functions and what types of values they return, and (2) what header files you need to include in order for your program to compile.

    Since the call to setuid may fail, please do proper unix style error checking: store the return value from setuid into a variable, and follow it up with a test like:

    if( return_val != 0 ) printf("Error: setuid failed! Reason: %s\n", strerror(errno));

    Again, you can use the man pages to determine which header files to include for those additional functions: man printf, man strerror, and man errno.

    Save your program, and try to compile it on shell.cec.wustl.edu, as in:

    gcc lib_call.c -o lib_call

    which if successful will produce a binary file called lib_call that you can run in a terminal window. Fix any coding mistakes (or missing header files, etc.) in your program, until it compiles successfully with no errors or warnings, and then run your program on shell.cec.wustl.edu, as in:

    ./lib_call

    As the answer to this exercise, copy and paste the output from running your program on shell.cec.wustl.edu.

  3. From the userspace programs directory terminal window on your Pi, sftp into shell.cec.wustl.edu, navigate to the appropriate directory, and get the lib_call.c file. Quit sftp and compile and run your program. As the answer to this exercise, please copy and paste the output of running the program on your Raspberry Pi, along with a brief description of any differences you noticed in the output of the program (or the compilation, etc.) on your Pi vs. on shell.cec.wustl.edu.

  4. Now we're going to modify your program to use the native Linux syscall interface as documented in man syscall (singular - this is a different page than man syscalls - plural). You are strongly encouraged to continue to develop and store your code on shell.cec.wustl.edu so that if your Pi freezes up you don't lose your work, and then when it's time to move things over to the Pi, use sftp to do so.

    Copy your lib_call.c file into a new file called native_call.c, and in that new file replace the calls to getuid and setuid with calls to syscall. To do so, you will have to determine their ARM cd architecture specific system call numbers by looking at the linux source file arch/arm/include/uapi/asm/unistd.h. Note that it is good programming practice, and also makes your code more portable (e.g., between shell.cec.wustl.edu and your Raspberry Pi) if you use the manifest constants for them (e.g., __NR_getuid and __NR_setuid) instead of hard-coding the numbers for them directly in the code you write.

    Compile your program, fixing any problems and recompiling as needed, and run it on your Raspberry Pi. As the answer to this exercise, copy and paste the output from running your new (native call) program on your Raspberry Pi.

  5. Now we will begin the process of writing two new system calls into the Linux kernel, each of which will print a distinct message into the kernel log.

    Note:When making changes to the linux source code, add a comment like //CSE422 MMDDYY before each section (where MMDDYY is the month, day, and year on which the change is being made). This, along with generating file diffs, will make it easier for you to keep track of the changes made to the kernel, and to revert them if needed.

    There are five distinct tasks we need to accomplish to do this:

    1. Declare a C function prototype inside the kernel for each new syscall
    2. Write a C function implementation for each new syscall
    3. Define a new system call number for each new syscall
    4. Update the ARM architecture system call table with each new syscall
    5. Update the total number of syscalls value that stored by the kernel

    First, navigate into your linux source directory, then into the linux directory under it (or whatever you renamed it to, from the long name produced by the linux .tar.gz file) then into include and then into the linux directory Before modifying any files, at least make a backup of any file you will be changing, as in:

    cp syscalls.h syscalls.h.MMDDYY

    where MMDDYY is the current month, day, and year (or, use svn or git or another version control system, and commit whenever you have a working version).

    Declare two new function prototypes, one that take no arguments and one that takes a single integer, at the bottom of include/linux/syscalls.h (make sure to put them before the closing #endif). You can use the prototype for sys_getuid as a template for doing this. Make sure that you use void in the argument list to indicate no arguments, since in some versions of C a function declared f(void) (which takes no arguments) is not the same as a function declared f() (which inidicates that the function may take any number of parameters of unknown type).

    As the answer to this exercise, show the two function prototypes you've added.

  6. Second, create a new file for each new syscall's implementation, in arch/arm/kernel/. The naming convention for a file that only implements a system call is to call the file by the syscall name, e.g., if our function that takes no arguments is called sys_noargs, you would create the file sys_noargs.c (additionally, there are other places we could have put this file, but since we're only adding this call for the (ARM architecture) Raspberry Pi, this is an appropriate place to put this code). Make a second file for your second system call in the same directory, using the same naming convention.

    For the implementation of the function that takes no arguments, copy and paste the contents of the file found here. Take a moment to look through this file. Notice that the function declaration isn't a normal declaration, but actually is made through the macro SYSCALL_DEFINE0 (the zero in the name of the macro comes from the fact that this syscall takes zero arguments, and is defined in include/linux/syscalls.h).

    For the implementation of your second syscall, use the code in sys_noargs.c as a template. You'll need to change the SYSCALL_DEFINE0 macro to reference the function name you came up with, as well as the fact that this function accepts an integer parameter. You do this by passing the type and the name of the parameter to the macro, as in:

    SYSCALL_DEFINE1( your_name, int, param_name )

    In the body of this syscall, use the kernel function printk to print a message that contains the value of the parameter that was passed to it. You can use printk much as you would use printf. Be sure to return a proper return value from this function.

    As the answer to this exercise, show your implementation of the second function.

  7. We now need to make sure the two new files are included in the Linux build process. The kernel has a robust build system, so this is actually pretty easy. Edit the file named Makefile (after, of course first making a copy of it named Makefile.MMDDYY) in the same directory as your source code files (which should be arch/arm/kernel/) and add our two new files to the end of the object file list, which starts on the line with obj-y (make sure you do not add your files after a \ character, as this specifies the start of a new line). Then, change the file extensions for your files from .c to .o (this implicitly tells the build system to generate the object (.o) file it needs by compiling the source (.c) file that has the same name except for the file extension). Be sure to add only the names of the files, with space characters in between them - as you may guess from the instructions above about where to put the names, formatting rules for how a Makefile is laid out can be a bit picky.

    As the answer to this exercise, show the newly modified line of the Makefile, that lists the new .o files to generate.

  8. The implementation of your system calls is almost done, but now we need to add them into the kernel's system call interface. We will need to define two new system call numbers in arch/arm/include/uapi/asm/unistd.h.

    Before modifying this file, make a copy into unistd.h.MMDDYY. Then, open the file and write system call numbers 391 and 392 for your new system calls near the bottom of the file. Use the other system calls as a template for how to do so.

    Next we will modify arch/arm/kernel/calls.S. This is actually an ARM assembly language file that defines the system call table. As before, first make a backup of calls.S and then add two new invocations of the CALL macro near the end of the file for your new system call functions.

    Finally, we need to update the symbol that tells the kernel how many system calls that it has. Go to arch/arm/include/asm/unistd.h, make a backup copy, and update the value of __NR_syscalls from 392 to 396. This may seem strange, since we increased the number by 4 but are only adding 2 new syscalls, but on the ARM architecture the system call table can only be certain sizes. You might want to check out the enrichment exercises, for more info about this.

    As the answer to this exercise, show the new lines you added to arch/arm/include/uapi/asm/unistd.h and arch/arm/kernel/calls.S

  9. At this point the kernel is fully modified and can be recompiled. It's not wise to try and rebuild only part of the kernel, so in the base linux source directory (the one with subdirectories arch, init, drivers, etc.) you should first issue the command make clean to remove the artifacts from your previous build.

    Then, to differentiate your new kernel from the one you built in the last studio, we'll modify the kernel LOCALVERSION. Rather than using the menuconfig interface like we did last time, this time we will modify the configuration directly. In the base directory, edit the file called .config (the leading period means that this is a hidden file that is not normally displayed) and modify the CONFIG_LOCALVERSION string to reflect the fact that this new kernel implements the extensions from this syscall studio.

    You should now build and install your new kernel following the steps outlined in the previous studio (starting with step 4, after you had already unpacked the Linux distribution, set up the configuration, etc.). Once your new kernel is booted and running on your Raspberry Pi, open up a terminal window and run the command uname -a to confirm the new version is running (you should see the new LOCALVERSION string you used). As the answer to this exercise please give the output that was produced by running uname -a on your Pi.

  10. You are now ready invoke your new system calls. First, copy native_call.c to a new file called new_call.c, and in that new file replace the native calls that invoke the getuid and setuid syscalls with the appropriate native calls to the new functions you wrote (i.e., replace the syscall number for getuid with the one for the new syscall that takes no arguments and replace the syscall number for setuid with the one for the new syscall you added that takes one argument).

    On your Raspberry Pi, compile and run this new program, and after running it check the system log by issuing the command dmesg. As the answer to this exercise, copy and paste the lines of the system message log that show the messages that were output to it by your new syscall functions.

Turning in this studio

To submit this studio, include the answers to exercises given above, and attach the userspace programs you wrote, as well as diffs of the kernel source code files you modified. To create a diff, use the diff program with the backup versions of the source code you modified.

For example: diff -up unistd.h unistd.h.MMDDYY > unistd.h.diff

For this studio, you will submit copies of the following files:

If you don't have an original copy for any of these files, you can find them in the original source code package.


Optional Enrichment Exercises

    As the answer to any of these optional exercies that you would like to try, please briefly describe what you learned (and give answers to any questions the exercise contains).

  1. Look at arch/arm/kernel/entry-common.S lines 99-109 and the end of arch/arm/kernel/calls.S and parse out exactly how large the system call table is. A few questions you should be able to answer if you really understand the process: What does the CALL macro do (defined in entry-common.S)? What are the different roles of the symbols NR_syscall and __NR_syscall? Calculate the value of the symbol syscalls_padding at the end of arch/arm/kernel/calls.S for a few different values of NR_syscalls.

  2. If you want to know more about the machine-level actions that occur during a syscall, you can start with the detailed explanation given in the manual to the native system call interface, man syscall.

    The kernel code that contains the entry point for system calls is found in arch/arm/kernel/entry-common.S.