CMSC 421: Principles of Operating Systems

Project 1: Minesweeper Kernel Module

This project is due on Tuseday, November 22, at 11:59:59 PM (Eastern standard time). You must use submit to turn in your project like so: submit cs421_jtang proj1 minesweeper.c minesweeper-test.c

Your module code must be named minesweeper.c, and it will be compiled against a 4.4 Linux kernel source tree, via the Kbuild supplied below. It must not have any compilation warnings; warnings will result in grading penalties. This module 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:

    ~/linux/scripts/Lindent minesweeper.c
  

In addition, you will write a unit test program, minesweeper-test.c, and it will be compiled on Ubuntu 14.04 as follows:

  gcc ‐‐std=c99 ‐Wall ‐O2 ‐pthread ‐o minesweeper-test minesweeper-test.c ‐lm
There must not be any compilation warnings in your submission; warnings will result in grading penalties. In addition, this code must also have a file header comment and be properly indented. You will submit this test code along with your module code.

In this project, you are a software developer for a computer keyboard manufacturer. You have been tasked to write the computer game Minesweeper as a Linux kernel module. The user plays your Minesweeper game by typing commands in a terminal. The kernel module will track the game state and also display the game board via a terminal. As that children born today only use touch interfaces, your goal is to train them on how using keyboards by playing the game.

Minesweeper is played on a board 10 rows high by 10 columns wide. Upon a new game, the computer randomly places 10 mines on the board; the object of the game is to find those mines. The player slowly reveals the game board one square at a time. If the player reveals a mine, he loses the game immediately. Otherwise, the computer reports the number of mines adjacent (up, down, to the side, and diagonally) to that square. The player uses that number to deduce which square to next reveal. In addition, the player can mark a square, indicating where he believes a mine lies. The player wins when he has marked all 10 mines and no other squares.

Your module will create two miscellaneous devices, /dev/ms and /dev/ms_ctl. A player views the game board by reading from /dev/ms. The player then controls the game (marking a mine, revealing squares, or resetting game) by writing to /dev/ms_ctl. Reading from /dev/ms_ctl returns the current game status (number of mines marked, or if the game is over).

Part 1: Compile Minesweeper Module

All instructions hereforth assume you successfully completed the first homework. If you have not done so, go back and finish the homework before proceeding. You have been warned.

To begin, create a directory for your project and download the following files into that directory via wget:

http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj1/minesweeper.c
Skeleton code for your Minesweeper kernel module.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj1/minesweeper-test.c
Skeleton code for your unit test code.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj1/Kbuild
Read by Linux kernel's build system, defines what is being built. You do not need to modify this file, nor should you submit it with your work.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj1/Makefile
Builds the kernel module and unit test program, by simply running make. Also included is a clean target to remove all built objects. You do not need to modify this file, nor should you submit it with your work.
http://www.csee.umbc.edu/~jtang/cs421.f16/homework/proj1/play.sh
A simple shell script to make playing the game easier. After downloading this file, mark it as executable (e.g., chmod u+x play.sh). You do not need to modify this file, nor should you submit it with your work.

Now run make to compile everything. You will get some warnings about unused symbols; by the end of this project you will have used all of them. You should now have the kernel module minesweeper.ko. Load that module like so:

    sudo insmod minesweeper.ko (enter your password as necessary)
The module was inserted if the following returns a non-empty string:
    lsmod | grep minesweeper

So far, all this module does is write a message to the kernel's ring buffer. View the module messages like so:

    dmesg | tail
The number at the beginning is the time stamp of when the message was written. To unload the module, run this command:
    sudo rmmod minesweeper
Re-examine the ring buffer to see the message generated during module exit. Every time you make a change and recompile minesweeper.c, you will need to first unload the module and then reinsert it.

Like userspace applications, kernel modules may also take module parameters when they are inserted into the kernel. Try inserting the module like so:

    sudo insmod minesweeper.ko fixed_mines=1
  
Examine the ring buffer a third time. The reason for the parameter fixed_mines=1 will be explained in Part Three below.

When the minesweeper module is loaded, the kernel calls its init function (similar to a C program's main() function), where execution begins. Currently, this module's init function calls pr_info() and allocates some memory for itself. The pr_info() function is an easy way to generate logging messages within kernel code. It accepts a format string like printf(), but has additional format specifiers useful for kernel programming.

Part 2: Create Miscellaneous Devices

Creating a custom character device can be daunting, and in this project, you will create two of them. Fortunately, the Linux kernel has the miscellaneous devices subsystem to simplify this task. For this project, the minesweeper module will use miscdevice to control /dev/ms and /dev/ms_ctl.

Follow these steps to create the miscellaneous devices. In minesweeper.c, declare a global variable of type static const struct file_operations to handle /dev/ms. Set its read callback to the stub function ms_read() and mmap to ms_mmap(). Then declare another static const struct file_operations for /dev/ms_ctl. Set the second variable's read to ms_ctl_read() and write to ms_ctl_write().

Then declare two global variables of type static struct miscdevice. For each of these variables, set the minor field to MISC_DYNAMIC_MINOR. Set the first variable's name field to "ms", fops to the first struct file_operations, and its mode to 0444, Set the latter's name to "ms_ctl", fops to second struct file_operations, and mode to 0666.

In minesweeper_init(), call misc_register() twice to create the character devices. In minesweeper_exit() call misc_deregister() twice to undo the registrations.

Recompile and reinsert your module. You will now have character devices /dev/ms and /dev/ms_ctl:

    $ sudo insmod minesweeper.ko
    $ ls -l /dev/ms*
    cr‐‐r‐‐r‐‐ 1 root root 10, 56 Oct 31 12:34 /dev/ms
    crw‐rw‐rw‐ 1 root root 10, 55 Oct 31 12:34 /dev/ms_ctl
    $ cat /dev/ms
    cat: /dev/ms: Operation not permitted
  
As that ms_read() returns -EPERM, attempting to read from the device results in permission denied.

Part 3: Initialize Game State

Create a new function, game_reset(). This function is responsible for resetting the internal game state for a new game:

Call game_reset() from minesweeper_init(). Then implement ms_read() as per its comments.

Recompile and reinsert your module. Now execute cat /dev/ms; this time it should print exactly 100 dots. Run the script play.sh to start playing your game. Thus far, you will not be able to do anything other than fetch the contents of user_view.

Then update game_reset() to secretly place ten mines on game_board. First clear the board of all existing mines. Then repeatedly call get_random_bytes() to get ten unique locations for the mines. However, if fixed_mines variable is true, do not randomly place the mines. Instead put the mines along the diagonal (0, 0) to (9, 9) (i.e., from upper-left corner to lower-right corner).

Now add a function game_reveal_mines(). In this function, modify user_view with the locations of mines in game_board. Mark the mines with a star. Then temporarily modify game_reset() to call game_reveal_mines(). Recompile and reinsert your module. Run play.sh to view the board, this time showing all mines' placements. Test with and without fixed_mines set. Afterwards, revert this change to game_reset().

Part 4: Implement Game Play

Now is the fun part of this assignment. Implement the code for ms_ctl_write(). Parse the user's input as per the function's comments:

Update game_status as a result of the user's action. If the user loses the game, call game_reveal_mines() to show the correct solution.

Finally, implement the function ms_ctl_read() to return the game status. You should now have a fully functional game!

After implementing all of the code, recompile and reinsert the module. Here is an example output of the user running play.sh with fixed_mines set:

    0123456789
  0 ..........
  1 ..........
  2 ..........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters r10)
    0123456789
  0 .2........
  1 ..........
  2 ..........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters r20)
    0123456789
  0 .21.......
  1 ..........
  2 ..........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters r30)
    0123456789
  0 .21-......
  1 ..........
  2 ..........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters r01)
    0123456789
  0 .21-......
  1 2.........
  2 ..........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters r12)
    0123456789
  0 .21-......
  1 2.........
  2 .2........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 0 out of 10 mines marked
> (User enters m00)
    0123456789
  0 ?21-......
  1 2.........
  2 .2........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 1 out of 10 mines marked
> (User enters m11)
    0123456789
  0 ?21-......
  1 2?........
  2 .2........
  3 ..........
  4 ..........
  5 ..........
  6 ..........
  7 ..........
  8 ..........
  9 ..........
Status: 2 out of 10 mines marked
> (User enters r22)
    0123456789
  0 *21-......
  1 2*........
  2 .2*.......
  3 ...*......
  4 ....*.....
  5 .....*....
  6 ......*...
  7 .......*..
  8 ........*.
  9 .........*
Status: You lose!
> 

Part 5: Handle Multiple Threads

The final piece to your module is to make it thread-safe. In theory, multiple processes could read/write to the device nodes simultaneously. The easiest way to solve this is to have a single static spinlock. Use that spinlock to lock ms_ctl_write() function.

Be aware that the above spinlock blog post is out of date. The SPIN_LOCK_UNLOCKED macro no longer exists in modern kernels. It has been replaced by the DEFINE_SPINLOCK macro.

Part 6: Unit Tests and Documentation

Now that you have (in theory) a fully working module, you must then write your own unit tests. Modify minesweeper-test.c to open /dev/ms for reading and /dev/ms_ctl for reading/writing. Create a read-only memory mapping to /dev/ms. This program is to exercise all of the Minesweeper functionality as described above. This includes a mix of inputs when writing to /dev/ms_ctl, confirming the memory map contents, and verifying the status read from /dev/ms_ctl is correct.

You will need to create your own testing framework; as a suggestion, reuse the one employed in homework 4. The unit tests must have comments that explain what things are being tested. Your goal is to test boundary conditions of your miscellaneous devices; you will be graded based upon the thoroughness of the tests. For example, you are responsible for checking that all requested coordinates are within the game board.

Repeatable testing would normally be difficult because the game board is randomized. To provide for a standardized grading environment, the grader will execute your test program using the following commands:

  make
  sudo insmod minesweeper.ko fixed_mines=1
  ./minesweeper-test
  sudo rmmod minesweeper

Other Hints and Notes

Extra Credit

Sorry, there is no extra credit available for this assignment.