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 ‐lmThere 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).
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:
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 | tailThe number at the beginning is the time stamp of when the message was written. To unload the module, run this command:
sudo rmmod minesweeperRe-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=1Examine 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.
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 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 permittedAs that
ms_read()
returns -EPERM
,
attempting to read from the device results in permission denied.
Create a new
function, game_reset()
. This function is responsible
for resetting the internal game state for a new game:
game_board
(see below).
user_view
to all dots, up
to PAGE_SIZE
bytes.
mines_marked
to 0.
game_over
to false
.
game_status
.
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()
.
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:
game_reset()
.
If the user is revealing a square,
update user_view
with the count of all
surrounding mine or mines. So as to not clutter the display,
if the count is zero, then show - instead.
If the user reveals a mine, the player loses the game.
If the square was marked or was already revealed, do nothing.
If the user is marking a square, update user_view
with a ? at the requested coordinate. If the square
was already marked, unmark it. If the square was already
revealed, do nothing.
When the user has marked exactly ten mines, and all of those
ten marks match the ten mines in game_board
, then
the player wins the game.
game_reveal_mine()
and quit the current game.
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! >
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.
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
mmap()
in minesweeper-test.c,
use PAGE_SIZE
as the number of bytes to map. (Only
the first 100 bytes are valid; ignore the rest of the mapped
page.) PAGE_SIZE
is defined in the
header <sys/user.h>.
Sorry, there is no extra credit available for this assignment.