CMSC 421: Principles of Operating Systems

Project 2: Babbler Kernel Module

This project is due on Thursday, May 14, at 11:59:59 PM (Eastern daylight time). You must use the submit to turn in your homework like so: submit cs421_jtang proj2 babbler.c

Your driver code must be named babbler.c. It will be compiled against a 3.14 Linux kernel source tree, via the Kbuild supplied below. It must not have any compilation warnings; warnings will result in grading penalties.

In addition, your code must be properly indented and have a file header comment, as described on the coding conventions page. Prior to submission, use the kernel's indentation script to reformat your code, like so:

    /usr/src/linux/scripts/Lindent babbler.c
  

In this project, you are a software developer for a social networking startup company, Babbler. When a Babbler user sends a babble (a short message consisting of up to 140 characters), the babble.ko kernel driver will forward that message to all Babbler listerners. Your driver will create a miscellaneous device /dev/babbler. When a process reads from this device from user space, the driver will return the most recent babble. Subsequent reads from the same process or any additional processes will block until a new babble is received. Babbles are sent by writing to /dev/babbler or via the automated BabbleBot service.

Part 1: Preparing Your Kernel

To begin, you will need to update your kernel to the latest version and apply some custom code to the kernel.

  1. The following instructions will remove the custom changes you made for the first homework. Be sure you have a backup of the patch that you had submitted.
  2. Run the following commands:
    cd /usr/src/linux
    git fetch
    git reset ——hard origin/linux-3.14.y
    (Note the above is dash, dash, "hard".)
  3. Reapply the changes you made from homework 1:
    git am $HOME/hw1/customize-uname.patch
  4. Use the wget command to fetch these patches:
    0001-x86-irq-Allow-for-software-triggered-IRQ.patch
    This adds a new function trigger_irq() that allows a driver to simulate a hardware interrupt by calling the INT opcode associated with an IRQ. In this way, drivers can be tested within a virtual environment.
    0002-babblebot-device-that-randomly-generates-babbles.patch
    Adds the babblebot driver, that generates random babbles.
  5. Apply both patches with this one command:
    git am 000*patch
  6. Because you have updated the kernel source tree, you need to update your kernel configuration. Run the command:
    make oldconfig
    If Kconfig asks if you want to build the BabbleBot driver, be sure to select Y.
  7. Now rebuild the kernel, install it, and reboot:
    make
    sudo make modules_install install
    sudo reboot
    (Be sure you select your new kernel from the GRUB menu.)
  8. After rebooting, be sure you reinstall VirtualBox tools.

Part 2: Building Initial Babble Driver

Your next task is to create the initial babble driver. Begin by creating a new directory for your project within your home directory. Next, download the following files into that directory via wget:

Kbuild
Read by Linux kernel's build system, defines what is being built.
Makefile
Hooks your module into kernel source tree. Build your module by simply running make. Also included is a clean target to remove all built objects.
babbler.c
Source file containing skeleton code for babbler.c. This file will be made available after Project 1 is due. If you successfully completed Project 1, you should instead copy your rot13.c into this directory and rename it babbler.c.
babble-reader.c
User space C program that is a Babbler client.

First, examine the header file include/linux/babblebot.h in the kernel source tree. This file was added specifically for this project. As in true Linux kernel style, the documentation for those functions are in the source file drivers/platform/x86/babblebot.c. In your driver babbler.c, add the line #include <linux/babblebot.h> to the top.

As with Project 1, your code will create a miscellaneous device. This time call it /dev/babbler instead of /dev/rot13; make the necessary changes to your code to create this device. Now run make to compile both the babbler.ko driver as well as babble-reader program. Ensure that you can load and unload the driver and that it creates the proper device node.

Part 3: Reading and Writing to Device Node

As with the first project, the babbler_open() and babbler_release() callbacks do nothing.

Change the babbler_read() and babbler_write() callbacks to behave as follows:

/**
 * babbler_read() - callback invoked when a process reads from this
 * character device
 * @filp: process's file object that is reading this device
 * @ubuf: location to write incoming babble
 * @count: number of bytes requested by process
 * @ppos: file offset (ignored)
 *
 * Write to @ubuf the lesser of @count and size of the incoming
 * babble.
 *
 * While there are no babbles, if the requesting process is performing
 * a non-blocking read (see @filp), then return -EAGAIN. Otherwise
 * block this process (as interruptible) until a babble is
 * received. See wait_for_completion_interruptible().
 *
 * The first time any process successfully reads the babble, clear the
 * babble. Subsequent reads from the same process or from any process
 * blocks until a new babble arrives.
 *
 * Return: number of bytes written to @ubuf, or negative on error
 */
static ssize_t babbler_read(struct file *filp, char __user * ubuf, size_t count,
                            loff_t * ppos);

/**
 * babbler_write() - callback invoked when a process writes to this
 * character device
 * @filp: process's file object that is writing this device (ignored)
 * @ubuf: source buffer of bytes to copy into internal buffer
 * @count: number of bytes in @ubuf
 * @ppos: file offset (ignored)
 *
 * Read from @ubuf up to the lesser of @count bytes or BABBLE_MAX_SIZE
 * bytes. Then wake up any readers (via complete() or complete_all())
 * that were blocked.
 *
 * Return: number of bytes read from @ubuf, or negative on error
 */
static ssize_t babbler_write(struct file *filp, const char __user * ubuf,
                             size_t count, loff_t * ppos);

When a process writes to /dev/babbler, copy the user buffer into an internal buffer of size BABBLE_MAX_SIZE. Any writes to /dev/babbler replaces the internal buffer. Reading from /dev/babbler returns the most recent babble and clears the internal buffer.

Test this by running the following:
echo -n 'I <3 #CS421' > /dev/babbler
cat /dev/babbler
(returns "I <3 #CS421")

Part 4: Add Blocking Read

Your next task is to allow for multiple readers of /dev/babbler. The first time any process reads from /dev/babbler, return the most recent babble. All subsequent reads, including those from the same process, block until a new babble is received. First add a completion variable to your driver.

Next, in babbler_read(), if no babble is available the code needs to do one of two things. If the caller is non-blocking (see the file object filp's field f_flags; if the bit O_NONBLOCK is set it is non-blocking), then simply return the value -EAGAIN.

Otherwise enter into a while loop until a babble is received. Use a spinlock to guard the while loop, as if you were using a Pthread condition variable. Call wait_for_completion_interruptible() against your completion variable. Prior to the call, release the spinlock; when the call returns, be sure to reaquire the spinlock and then reinitialize the completion variable.

In babbler_write(), call complete() or complete_all() after it has updated the internal buffer so as to awake all waiting read processes. Be sure babbler_write() uses the same spinlock to protect all shared resources.

Test these change by opening several terminals. In each terminal, run the program babble-reader. In your original terminal, run the previous echo command. You should see your babble appear in one of the terminals.

Do the following to test your driver's non-blocking behavior. First, unload and then reload the driver. Next, run babble-reader with the ——nb flag. Assuming that babblebot has not generated any babbles yet, babble-reader should immediately quit, with the error message Resource temporarily unavialable. Third, echo a string to /dev/babbler. Run babble-reader ——nb again. This time, babble-reader should display the echoed string and again display the same Resource temporarily unavailable error message.

Part 5: Integrate with BabbleBot

The final task is to add interrupt handling. Read the code drivers/platform/x86/babblebot.c. The BabbleBot, when enabled, waits randomly before generating interrupts. It is your driver's responsibility to catch those interrupts and copy the babble that was generated by the bot. Those babbles are then sent to all readers, just as if a user had written to /dev/babble.

In babbler_init(), install a threaded interrupt handler against BABBLE_IRQ. Remove that handler in babbler_exit().

The top-half of your interrupt handler does two things: calls babblebot_disable() and then returns the value indicating that the bottom-half should run.

The bottom-half copies the babble from BabbleBot into the internal buffer, via a combination of babblebot_read() and babblebot_size() calls. Just like babbler_write(), call complete_all() to awaken all readers. Finally, it reenables the bot by calling babblebot_enable(). Again, this bottom half must correctly use the spinlock to guard shared resources.

Once you are confident your ISR works, add babblebot_enable() to babbler_init() (and likewise disable the bot in babbler_exit()). Install your module. Run the following command in a terminal to show all interrupts:

while /bin/true; do grep 6: /proc/interrupts; sleep 1; done
The interrupt count should increment within 10 seconds. Run multiple copies of babble-reader; those readers should now display babbles from BabbleBot.

Other Hints and Notes

Extra Credit

You may earn an additional 20% credit for this assignment by augmenting your babble handling. Whenever a babble is received, either via babbler_write() or from BabbleBot, record the current hour, minute, and second. Modify babbler_read() to return the timestamp, expressed as colon separated two digits, in addition to the babble. For example, this would result in the following babbles:

20:19:07 To be or not to be -- that is the #question:
20:19:12 The #slingsandarrows of #outrageousfortune
20:19:20 And by opposing end them.