Please follow the following submission criteria:
Please see the Assignment 2 submission guidelines for further information on how to set up a VM for these kernel programming exercises.
In Assignments 2 and 4, we wrote some basic kernel modules that can be dynamically loaded into the kernel at runtime. While kernel modules are powerful, it is sometimes necessary to make direct modifications to the kernel source code. For example, Google's Android operating system, likely one of the most successful Linux distributions to date, uses its own fork of the Linux kernel to implement a number of features related to, e.g., power management, memory management, logging, and interprocess communication.
In this assignment, we look at how to modify system calls in the Linux kernel. It is in fact possible to alter the behaviour of system calls from a kernel module by intercepting them using a technique called hooking, but we will look instead at how to directly modify the system call source.
For the previous assignments, we only needed the kernel headers, which allowed us to compile our modules against the kernel version that we were using. Linux distributions generally provide tools to build the kernel source of the existing version that comes pre-installed with the distribution. For example, with Ubuntu you can run apt-get source linux-image-$(uname -r)
to download the source for the kernel that you are currently running. However, we're going to do something more fun - we're going to download the bleeding-edge source code from Linus Torvalds' git repository. If you're not familiar with git, I highly recommend this free book.
Before you continue, you may want to make sure that you have all the prerequisites installed on your VM. You should run:
sudo apt-get install build-essential
to install the gcc compiler and some other libraries (this is probably already installed)sudo apt-get install git
to install gitsudo apt-get install libncurses5-dev
, which is needed for the text-based menu system for configuring the kernelsudo apt-get build-dep linux
to install any remaining prerequisite packages for building the kernel
Now that you are ready, download the kernel source to your working directory. You can find the URL for Linus Torvalds' repository on this page. Next, we need to configure the kernel to specify which modules we want to include in it. There are various ways in which this can be done, the most tedious of which is to run make menuconfig
and manually go through all the options. Note that including more options will make the kernel larger and it will also take much longer to compile. When you are finished modifying your configuration, it will be saved to the .config
file in your kernel source directory. There are a couple of other commands that can make your life easier when configuring your kernel. Running make oldconfig
will copy the configuration file for the kernel currently installed on your system, which is usually stored in /boot/config-$(uname -r)
, and will prompt you for any new options that might be available in the more recent kernel that you are building (just accept the default by pressing Enter for any new options that you are prompted with). This will save you the manual labour of having to go through all the options yourself, and will ensure that you will be able to boot successfully from your new kernel.
The default Ubuntu configuration will leave many modules enabled which you probably don't need. If you want to cut down on your compilation time, you can run make localmodconfig
, which will look at all the modules that are currently running and will disable everything but those modules in your configuration file. This will drastically cut down your compilation time, but does have certain pitfalls (so only use this option if you really want to cut down compilation time, and are willing to take the risk that you may face some problems): If you have any hardware that isn't currently plugged in, any modules that may be required to drive that hardware will probably be disabled in your configuration file. For example, the CD-ROM/DVD file system module will probably be disabled, so you should mount a CD image before running make localmodconfig
. You can do this by clicking on the "Device" menu in Virtualbox and selecting "Insert Guest Additions CD Image...". You will likely need to re-install the Guest Additions (by running autorun.sh from the CD image) after installing your new kernel, if you want to restore some VirtualBox features such as mouse/clipboard integration, drag and drop support, etc.
If you run into problems with missing modules, you can always run make menuconfig
again, select the missing modules that you need, and re-run make modules
and make modules_install
. If your kernel fails to boot, you can reboot your VM and hit the "Escape" key before Ubuntu starts booting to bring up the GRUB menu, where you can choose an older kernel to boot from.
After configuring your kernel, you will need to run make bzImage
to compile the kernel, followed by make modules
to compile the modules. You may then install your kernel by running sudo make modules_install
and sudo make install
(in that order).
To get full marks for this part, please complete the following steps:
uname -r
in a terminal). Hint: You will find what you need under "General setup"..config
file (2 marks).dmesg
log showing that you have successfully booted into your newly-compiled kernel (3 marks).execve()
(16 marks)Our goal for this exercise is to modify the execve()
system call so that any command that starts with "comp3000" will automatically be given a higher priority. To figure out how to do this, let us first look at a couple of useful tools.
In Unix-based systems, a process' priority is determined by its nice value, which has a minimum value of -20 and maximum value of 19. New processes inherit the nice value of their parent, and typically any command run by a user will have a default nice value of 0. Note that the higher the nice value, the lower the priority of the process (hence the term "nice", since the process is being "polite" to the rest of the processes running on the system). This value can be changed either:
nice -n [nice value] [command]
to execute your command with the given nice valuerenice [nice value] -p [pid]
to change the nice value of the process with the given pid
Another useful tool is strace, which is used to execute any command and output all the system calls that have been made. For example, if you run strace ls
in a terminal, it will display all the system calls that the ls
command invokes to list the contents of your current directory.
You may want to type man nice
or man strace
in a terminal to see more detailed instructions on how to use these tools.
The first step in this exercise is to find which system call is used to change a process' priority. To do this, you may use strace
to execute any command through nice
and observe the resulting output. Note that the nice
command simply changes its own nice value and then invokes execve()
to replace its own process image with whatever command you give it. Please submit the following:
-o
flag to save the output to a text file) (2 marks).execve()
is defined (2 marks).Make use of the Linux Cross-Reference, which we used in Assignments 2 and 4, and note that all system calls are defined using the macro SYSCALL_DEFINEX()
, where X is the number of arguments that the system call requires.
After completing the above steps, you should be ready to make the necessary modifications to your kernel source. Modify the execve()
system call so that any file name that contains "comp3000" is given a nice value of -10 (8 marks). Please follow the instructions below for submitting your code:
git checkout -b mybranchname
.git commit -a -m "Your commit message"
.git format-patch -1 HEAD
to generate a patch file containing the changes that you have made in your most recent commit.You cannot invoke a system call from within another system call. You will need to read through the source of the system call that changes the nice value of a process, understand how it is done, and make the necessary modifications to execve()
.
Unfortunately, it seems that the VM image is configured to use a very small disk image. The root partition is only 6.8GB, and there is only 2.1GB of space available when the VM is freshly imported. My linux source directory is about 2GB, so it's unlikely that you will have enough space to compile the kernel without adding more disk space. There are two ways to work around this issue:
You can check this using the ps
tool. Typing ps lax
in a terminal will list all the processes, and the nice values will be listed under the "NI" column. Type man ps
if you would like to learn more about the different options that you can use.