This project is due on Tuesday, November 21, at 11:59:59 PM (Eastern standard time). You must use submit to turn in your project like so: submit cs421_jtang proj1 mastermind.c mastermind-test.c
Your module code must be named mastermind.c, and it will be compiled against a 4.12 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 mastermind.c
In addition, you will write a unit test program, mastermind-test.c, and it will be compiled on Ubuntu 16.04 as follows:
gcc ‐‐std=c99 ‐Wall ‐O2 ‐pthread ‐o mastermind-test mastermind-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.
Mastermind is a code breaking game invented in the early 1970s, based upon an earlier game called Bulls and Cows. In both games, the codemaker selects a 4 digit code, where each digit ranges from 0 through 5, inclusive. The code can have repeating digits. The opposing player, the codebreaker, then tries to guess the code. For each guess, the codemaker marks which digit is correct in both value and position (a black peg) and which is simply correct in value (a white peg). There are many variations of this game playable online; the one closest to the spirit of this assignment uses colors instead of digits.
In this project, you will write a Linux kernel module that implements the basics of Mastermind. The Linux kernel itself will take the role of the codemaker, while human users will be codebreakers. Your module will create two miscellaneous devices, /dev/mm and /dev/mm_ctl. The user controls the game state by writing to /dev/mm_ctl, then make guesses by writing to /dev/mm. The module calculates the number of black and white pegs, and generates a status message. The user retrieves the status message by reading from /dev/mm, then continues guessing. The user can also obtain a history of the current game, showing all guesses and responses, by opening a memory map to /dev/mm.
All instructions henceforth 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 mastermind.ko. Load that module like so:
sudo insmod mastermind.ko (enter your password as necessary)The module was inserted if the following returns a non-empty string:
lsmod | grep mastermind
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 mastermindRe-examine the ring buffer to see the message generated during module exit. Every time you make a change and recompile mastermind.c, you will need to first unload the module and then reinsert it.
When the mastermind 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.
You are about to make changes to your Linux kernel. There is a slight chance of accidentally erasing your virtual machine's hard drive. Create a snapshot of your virtual machine before proceeding.
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 mastermind module will use miscdevice to control /dev/mm and /dev/mm_ctl.
Start off by examining mastermind.c, specifically the stub functions
mm_read()
and mm_write()
, and the
prewritten function
mm_mmap()
. Follow these steps to create the
device /dev/mm:
static const struct
file_operations
to handle /dev/mm. Set
its read
callback
to mm_read
, write
callback
to mm_write
, and mmap
callback to mm_mmap
.
static struct
miscdevice
for /dev/mm. Set
its minor
field
to MISC_DYNAMIC_MINOR
, name
field
to "mm"
, fops
field
to point to the previously created struct
file_operations
, and mode
callback
to 0666
.
mastermind_init()
,
call misc_register()
to create the character
device. In mastermind_exit()
call misc_deregister()
to undo the registration.
mastermind_init()
, you are responsible
for freeing all resources in case of an
error. Specifically, mastermind_init()
should be
structured with goto
statements to
clean up resources.
If all of the above works, when the module is loaded, you will now have a character device /dev/mm:
$ sudo insmod mastermind.ko $ ls -l /dev/mm crw-rw-rw- 1 root root 10, 56 Oct 28 18:38 /dev/mm $ echo -n 'Hi there' > /dev/mm bash: echo: write error: Operation not permittedIf implemented incorrectly, your kernel ring buffer may contain a message that looks like this:
[27656.939815] Oops: 0000 [#1] SMP [27656.939815] Modules linked in: mastermind(O+) ntfs msdos vboxsf(O) snd_intel8x0 snd_ac97_codec ac97_bus vboxvideo(O) snd_pcm drm_kms_helper snd_seq_oss snd_seq_midi_event syscopyarea snd_seq sysfillrect snd_seq_ device sysimgblt snd_timer fb_sys_fops ttm snd drm soundcore vboxguest(O) serio_raw autofs4 hid_generic usb hid hid psmouse ohci_pci e1000 ohci_hcd [last unloaded: mastermind] [27656.939815] CPU: 3 PID: 3624 Comm: insmod Tainted: G O 4.12.9+ #3 [27656.939815] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006 [27656.939815] task: ffff9d41e5d4e040 task.stack: ffffa47200ce8000 [27656.939815] RIP: 0010:misc_register+0xd/0x170 [27656.939815] RSP: 0018:ffffa47200cebc80 EFLAGS: 00010246 [27656.939815] RAX: ffffffffc03a8420 RBX: ffffffffc03a80c0 RCX: 0000000000000000 [27656.939815] RDX: 0000000000000000 RSI: ffffffffc03a70cf RDI: 0000000000000000 [27656.939815] RBP: ffffa47200cebc98 R08: ffffa47200c60000 R09: 00003fffffe00000 [27656.939815] R10: ffff9d421625aa90 R11: ffffa47200c5ffff R12: 0000000000000018 [27656.939815] R13: 0000000000000000 R14: ffffffffc03a8110 R15: 0000000000000001 [27656.939815] FS: 00007f49009cc700(0000) GS:ffff9d421fd80000(0000) knlGS:0000000000000000 [27656.939815] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [27656.940466] CR2: 0000000000000000 CR3: 000000016ea8b000 CR4: 00000000000406a0 [27656.940484] Call Trace: [27656.940494] ? 0xffffffffc024e000 [27656.940505] mastermind_init+0x59/0x1000 [mastermind] [27656.940522] do_one_initcall+0x4e/0x180Read the call trace to determine where within your code caused the fault that the Linux kernel had detected. Most errors are unrecoverable; a symptom of an unrecoverable condition is when the kernel refuses to unload the module:
$ sudo rmmod mastermind rmmod: ERROR: Module mastermind is in useIn this case, your only recourse is to reboot the virtual machine.
After creating the /dev/mm miscellaneous device, similarly
create the /dev/mm_ctl device. This second device has only
one callback, write
, which should be set
to mm_ctl_write
. As before, ensure that all calls
within mastermind_init()
are checked and resources are
released upon error.
The next step is to implement the
callback mm_ctl_write()
. Read its comments. This
function needs to parse the user's input. If the user
writes start to /dev/mm_ctl, then start a new
game:
user_view
.game_active
to true
.mm_ctl_write()
starts a game when the
user enters start, not start
plus a newline character.
If instead the user enters quit (again, without the
trailing newline), then set game_active
to false
and the game status message with the target
code.
When constructing game statuses, use the Linux kernel-specific
function scnprintf()
. This
function prevents common buffer overflow problems inherent
to snprintf()
.
Now implement mm_read()
. This function simply copies
the game status to the user. It is responsible for handling partial
reads and non-zero offsets.
Recompile and reinsert your module. You should now be able to minimally interact with your game:
$ sudo insmod mastermind.ko $ cat /dev/mm No game yet $ echo "start" > /dev/mm_ctl bash: echo: write error: Invalid argument $ echo -n "start" > /dev/mm_ctl $ cat /dev/mm Starting game $ echo -n "quit" > /dev/mm_ctl $ cat /dev/mm Game over. The code was 0012.Note how the above passes the ‐n flag to echo to suppress the trailing newline.
The next function to implement is mm_write()
. Parse the
user's input as per the function's comments:
NUM_PEGS
characters. Convert
the ASCII characters in the guess to their numeric equivalent.
user_view
a string (null-terminated) with
the guessed digits, number of black pegs, and number of white
pegs.
Recompile and reinsert your module. You should now be able to play the game:
$ sudo insmod mastermind.ko $ echo -n "start" > /dev/mm_ctl $ echo -n "0022" > /dev/mm $ cat /dev/mm Guess 1: 3 black peg(s), 0 white peg(s) $ echo -n "2010" > /dev/mm $ cat /dev/mm Guess 2: 2 black peg(s), 2 white peg(s) $ echo -n "5432" > /dev/mm $ cat /dev/mm Guess 3: 1 black peg(s), 0 white peg(s) $ echo -n "I WIN" > /dev/mm bash: echo: write error: Invalid argument $ echo -n "0012" > /dev/mm $ cat /dev/mm Guess 4: 4 black peg(s), 0 white peg(s)
Write for yourself a minimal C program that creates a read-only
memory map to /dev/mm. Use PAGE_SIZE
as the
number of bytes to map. PAGE_SIZE
is defined in the
header <sys/user.h>. For the above sequence of
guesses, the memory map contents should thus be:
Guess 1: 0022 | B3 W0 Guess 2: 2010 | B2 W2 Guess 3: 5432 | B1 W0 Guess 4: 0012 | B4 W0
Now that you have (in theory) a fully working module, you must then write your own unit tests. Modify mastermind-test.c to open /dev/mm and /dev/mm_ctl. Again, create a read-only memory mapping to /dev/mm. This program is to exercise all of the functionality as described above. This includes a mix of inputs when writing to the device nodes, reading and verifying the contents of the memory map, and verifying the calculations of black and white pegs are 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 user cannot read past the length of the game status.
As that your tests will perform multiple reads and writes from the devices, you will probably need to reposition your file pointer after each operation.
Normally, the kernel module always picks the same target code of 0012. This does not make for an interesting game after the first play. You may earn an additional 10% on this assignment by randomly selecting the target code. Add the following features to your code:
random_code
, that defaults
to false
.
random_code
is true
,
then randomly
select 4 digits.
If you choose to perform this extra credit, put a comment at the top of your file, alerting the grader.