As with previous assignments, we will be using GitHub to distribute skeleton code and collect submissions. Please refer to our Git Workflow guide for a more details. Note that we will be using multiple tags for this assignment: one for each deliverable part.

For students on ARM Mac computers (e.g. with M1 chip): if you want your submission to be built/tested for ARM, you must create and submit a file called .armpls in the top-level directory of your repo; feel free to use the following one-liner:

cd "$(git rev-parse --show-toplevel)" && touch .armpls && git add .armpls && git commit -m "ARM pls"

You should do this first so that this file is present in all parts.

Code Style

There is a script in the skeleton code named It is a wrapper over linux/scripts/, which is a Perl script that comes with the linux kernel that checks if your code conforms to the kernel coding style.

Execute to see if your code conforms to the kernel style – it’ll let you know what changes you should make. We recommend you make those changes.

Passing with no warnings and no errors is NOT required for this assignment, but will be for the next one. We recommend you get familiar with this workflow now: run the script and fix what it suggests before pushing a tag.

Part 1: Building a Kernel in Debian Linux



Kernel Compilation in Debian Linux

First, follow the above guide and compile yourself a pristine (unmodified) kernel from the 5.10.57 Linux source provided in the skeleton repo. You should name it 5.10.57-cs4118, and keep it around as your fallback kernel for all future assignments (including this one), in case you run into any trouble booting into the kernel you’re working on.

Additionally, make sure that the CONFIG_BLK_DEV_LOOP option is set to y in your .config file before you build and install your pristine kernel. This will come in handy in later assignments.


Part 2: Reducing Kernel Build Time

In this part, you will reduce your kernel build time drastically.



A large amount of time is spent compiling and installing kernel modules you never use. You can regenerate .config so that it contains only those modules you are currently using. This will drastically cut down the number of modules. This is how:

  1. First, backup your .config to something like .config.<UNI>-from-lts.

    • Make sure to keep your local version the same as what it was in part 1; that is, your kernel should still be named 5.10.57-cs4118.
  2. Run make localmodconfig in your Linux kernel source tree.

    • This will take your current .config and turn off all modules that you are not using.

    • It will ask you a few questions. You can hit ENTER to accept the defaults, or just have yes do so for you:

        $ yes '' | make localmodconfig
    • Make sure that CONFIG_BLK_DEV_LOOP is still set to y before building and installing this kernel.

Now you have a much smaller .config. You can follow the rest of the steps starting from make.

Don’t Reinstall Modules

When you are hacking kernel code, you’ll often make simple changes to only a handful of .c files. If you didn’t touch any header files, the modules will not be rebuilt when you run make; thus there is no reason to reinstall all modules every time you rebuild your kernel.


Part 3: Set the Table - Adding a Syscall

You’re about to make changes to the pristine kernel source. This means that what you build from it might not even boot. In order to make sure that you always have the -cs4118 pristine kernel as a fallback to boot into, you should avoid overwriting it by setting the local version in your .config file to something else, like your UNI (for example, -abc1234). Make sure to verify your changes:

$ scripts/diffconfig .config.old .config
LOCALVERSION "-cs4118" -> "-abc1234"

You should use your UNI as the local version for all modified kernels you build for this course from now on.

You are now ready to add system calls to your kernel.



In the next few parts, we will implement a new system call: inspect_table(). It retrieves the file descriptor table of a specified process. This syscall should work on both x86 and arm64 architectures.

At this time of writing, the Linux kernel has about 400 system calls, though new system calls are constantly being added to the Linux kernel. Let’s leave some room and use 500 as the syscall number for inspect_table().

The system call should have the following interface:

long inspect_table(pid_t pid, struct fd_info *entries, int max_entries);

where struct fd_info is defined as follows:


struct fd_info {
    int fd;
    unsigned int flags;
    long long pos;

We will build up its functionality over the next few parts. For this part, inspect_table() should do the following:

Your syscall will use this task_struct in subsequent parts of the assignment.

A few things that you might find helpful:


We’ve provided a userspace test program under user/test/table-inspector. To test this part’s functionality, run it like this:

./table-inspector <pid> 0

Here is some sample output for this part:

$ ./table-inspector -1 0
inspect_table (0): Success
$ ps aux | grep '/usr/sbin/sshd'
root         490  0.0  0.0  13292  7704 ?        Ss   Feb05   0:00 sshd: /usr/sbin/sshd -D
$ ./table-inspector 490 0
inspect_table (-1): Operation not permitted
$ sudo ./table-inspector 490 0
inspect_table (0): Success
$ ./table-inspector -420 0
inspect_table (-1): Invalid argument
$ ./table-inspector 50000 0 # this pid isn't in use
inspect_table (-1): No such process

You may optionally submit your own test program under user/test/. To learn how to invoke a syscall, read:

man 2 syscall



To submit this part, push the hw4p3handin tag with the following:

$ git tag -a -m "Completed hw4 part3." hw4p3handin
$ git push origin master
$ git push origin hw4p3handin

Part 4: Moving a system call into a kernel module

By now, you must be tired of rebooting your VM every time you make a small change in system call code. In this part, we will move the code for inspect_table() into a dynamically loadable kernel module. Our goal is to be able to make modifications to the system call code without having to reboot the machine.



There are two ways to implement a system call using a module. You can try to modify the system call table from the module initialization code. Changes in recent kernels on the mechanics of setting up system calls make this method more cumbersome than it used to be, so we are not going to do this.

Another way is to leave the system call definition in the static kernel code, but have it call another function defined in a module. This is our approach.

Move your implementation of the inspect_table() syscall to the provided module skeleton code:

Big hint: use function pointers.


Make sure you’re still able to run your test program you wrote in Part 3 before you load your module (deactivated), after you load your module (activated), and after you unload your module (deactivated again). You should check that errors are gracefully handled (i.e. the appropriate errnos are set and checked for).



To submit this part, push the hw4p4handin tag with the following:

$ git tag -a -m "Completed hw4 part4." hw4p4handin
$ git push origin master
$ git push origin hw4p4handin

Part 5: Appetizer - Listing Open File Descriptors

Now that we have a working syscall implemented in a kernel module, we will begin adding more functionality. Here, we will have the syscall print the target process’s open file descriptors to the kernel log buffer (in addition to the functionality specified in part 3).


Before getting started, we should understand the data structures related to the file descriptor table. This article implements a simple kernel module that prints the calling task’s open file descriptors with full paths. This code is almost sufficient for this part but it has a major bug. What will this module do if there is a hole in the file descriptor table? For example, a process may have opened file descriptor 3 and 4 and then closed 3.

Another problem with the code from the above article is that it completely ignores synchronization and resource management while accessing the data structures. This is fine for the sake of this assignment. This article attempts to handle synchronization and resource management properly. It also provides a more in-depth explanation with diagrams of the data structures. We recommend reading this article, if not, at least studying its diagrams.


In this part, you will add the following functionality to inspect_table() in addition to the functionality implemented in part 3:


Here is some sample output from inserting the module, running table-inspector for two processes, and then removing module. Your output format must match EXACTLY.

$ sudo dmesg -Hw
[Feb 7 22:37] Loading tabletop
[  +7.318509] Open fds for 36944:
[  +0.000010] 0
[  +0.000008] 1
[  +0.000008] 2
[Feb 7 22:38] Open fds for 36949:
[  +0.000020] 0
[  +0.000009] 1
[  +0.000009] 2
[  +0.000010] 4
[  +5.033228] Removing tabletop



To submit this part, push the hw4p5handin tag with the following:

$ git tag -a -m "Completed hw4 part5." hw4p5handin
$ git push origin master
$ git push origin hw4p5handin

Part 6: Let’s Eat! - Flags, Pos, and Path


In this final part, we complete the syscall implementation by making use of the entries and max_entries parameters. In addition to the functionality described in parts 3 and 5, your syscall should now do the following:



Here is a sample program and its table-inspector output:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
        int fd1 = open("/tmp/tabletop.tmp", O_WRONLY | O_CREAT | O_APPEND, 0644);
        write(fd1, "hello", 5);

        int fd2 = open("/tmp/tabletop.tmp", O_RDONLY);
        int fd3 = open("/tmp/tabletop.tmp", O_RDONLY | O_CLOEXEC);

        pause(); // table-inspector is run while the program is blocked on pause()

$ ./table-inspector 38302 10
inspect_table (5): Success

fd: 0
path: /dev/pts/1
pos: 0
flags: (02002) O_RDWR O_APPEND
fd: 1
path: /dev/pts/1
pos: 0
flags: (02002) O_RDWR O_APPEND
fd: 2
path: /dev/pts/1
pos: 0
flags: (02002) O_RDWR O_APPEND
fd: 3
path: /tmp/tabletop.tmp
pos: 5
flags: (0102001) O_WRONLY O_APPEND
fd: 5
path: /tmp/tabletop.tmp
pos: 0
flags: (02100000) O_RDONLY O_CLOEXEC



To submit this part, push the hw4p6handin tag with the following:

$ git tag -a -m "Completed hw4 part6." hw4p6handin
$ git push origin master
$ git push origin hw4p6handin

Good luck!


The Tabletop assignment was designed and implemented by the following TAs of COMS W4118 Operating Systems I, Spring 2022, Columbia University:

Last updated: 2022-02-22