DM510 Required Assignment 2: Kernel Module
In this assignment, your task is to make a kernel module. This module must implement a driver that exposes two character devices to user-space. Their function will be explained below.
To complete this task it will be necessary to study the following sources:
- Chapters 2, 3, 5 (especially for semaphores) and 6 (especially for
blocking and non-blocking I/O and for device control with ioctl)
in the online book Linux
Device Drivers (LDD), 3rd edition (for 2.6 kernels) hold all
the information you need to complete this assignment. The original
source code of all examples from LDD can be
found here.
At the time of writing LDD, the Linux kernel 2.6.10 was used. An
updated version of all files that should work with kernel 2.6.28
can be found here. The example code
from LDD for
the
scull
driver that you have to study for this assignment was updated to work with kernel 2.6.32.3 (see FAQ - the main changes are related to handling of credentials). - You can also use The Linux Kernel Module Programming Guide [pdf, ps.gz, html] as a source of information.
The files that you can use as a starting point for you kernel module can be found here.
The Driver
Normally a device driver sits between some hardware and the kernel I/O subsystem. Its purpose is to give the kernel a consistent interface to the type of hardware it "drives". This way the kernel can communicate with all hardware of a given type through the same interface, even though the actual hardware differs.
In this assignment we will not interface to hardware because of the complexity this would impose. Instead we will make two processes communicate trough a device driver.
The figure to the left illustrates how the driver should work. Basically it solves the producer-consumer problem. Here the two processes can be both producers and consumers. This resembles the functionality of a real hardware device driver, where both the hardware and the kernel can produce and consume data.
When a process writes (produces) to character device
/dev/dm510-0
the data is stored in bounded buffer
1
. If the buffer is full the process has to wait until another
process has read from /dev/dm510-1
.
When a process reads (consumes) from character device
/dev/dm510-0
the data is read from bounded buffer
0
. If the buffer is empty the process has to wait until another
process has written to /dev/dm510-1
.
Writes and reads to and from /dev/dm510-1
are handled in the
same way.
In the code from LDD there is an example (scull/pipe.c
) which does almost the
same (chapter 6 of LDD). The difference is that only one character device has been used (one
minor). This means that the data you write to it can be read back from the
same device.
Requirements
The requirements are:
- Your module has to be made for kernel 2.6.32.3.
- You should either use a major number of 254 and minor numbers of 0 and 1 or automatically assigned major/minor numbers (see Chapter 3 of LDD).
- Blocking and Non-blocking I/O must be supported (see Chapter 6 of LDD, page 147ff).
- Several processes at a time must be able to open each device for reading, only one process at the time should be able to open a device for writing.
- Simple device control has to be possible via
the
ioctl
system call. Implement i.) adjusting the buffer sizes and ii.) setting the maximal number of processes that are allowed to read the device at a time via ioctl (see Chapter 6 of LDD, page 135ff or Chapter 7 of The Linux Kernel Module Programming Guide). - The device should support multiple processes executing simultaneously in the device driver at the same time. As this could lead to race conditions and other problems, you should use semaphores (or other synchronisation methods) to avoid potential problems.
This is not required:
- Backward compatibility. The module does only have to be compatible with kernel 2.6.32.3.
udev
anddevfs
support.- Poll, select and asynchronous notification support (see Chapter 6 of LDD).
Attacking the Problem
The hardest part of most kernel programming is to understand the environment that your code should run in. Therefore, most of your time should be spent studying how character devices work. This will minimize the testing and debugging phase. Recommended is to start with reading the first chapters of LDD. Here is a starting point for your implementation:
Compiling and inserting a simple module
Create a working directory for your project as a subdirectory of
the directory dm510
, that you created for installing
UML. Fetch the files that are the starting point for your
implementation (dm510_dev.c, Makefile, dm510_load
,
and
dm510_unload
), store them in the working directory,
change into the working directory containing these files and edit
Makefile
if necessary. (You may have to change
the KERNELDIR
variable with the path to your UML
kernel, if you are using a non-standard location).
Now you can compile the module by running make
. If the
compilation succeeds there will now be a file
called dm510_dev.ko
which is the module. Start the UML
kernel. Create the necessary devices and insert the module by
executing
./dm510_load
To remove the module and to delete the devices execute:
./dm510_unload
See LDD chapter 4 and check the two scripts for details.
Making the Driver
The file dm510_dev.c
is the code for the simple module above.
It has almost empty declarations of all the functions that you need to
modify in order to complete this assignment. These are:
int dm510_init_module(void); void dm510_cleanup_module(void); int dm510_open(struct inode *, struct file *); int dm510_release(struct inode *, struct file *); ssize_t dm510_read(struct file *, char *, size_t, loff_t *); ssize_t dm510_write(struct file *, const char *, size_t, loff_t *); int dm510_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
To get an idea of how to implement these functions you should study
the implementation of the scull
device driver,
especially the pipelined version of the driver. This driver is
explained in detail in LDD, especially pages 153ff. The source code
including the necessary script to load and unload the driver can be
found here
. Study
this example thoroughly,
especially pipe.c
.
Report / Submission
The report submission procedure is similar to the submission procedure of the first required assignment. The report must be short and precise (maximum 10 pages, English language, without source code). Remember that you must prove that you have understood the problems and solutions. It must include the following:
- A small introduction.
- A description of the design decisions you have made.
- A description of your implementation.
- A description of the tests you have made and the motivation for these. The tests must be able to verify that your solution works correctly, but you do not have to test all possible error scenarios. Remember it is better to document a bug rather than just ignoring it. An inspiring test program for one test can be found here.
- A discussion of what would happen if multiple processes use the devices simultaneously.
- A small conclusion.
Furthermore, your report has to be supplemented again by a recorded
desktop session, in which you show that your implementation is working
correctly. During the session you should also perform all or some of
the tests that you described in your report and show that device
control via ioctl
is possible. In your report you should refer to
the tests you performed by giving the precise time in the video
file. The session you record should start when you start UML with the
command ./linux
in a shell. The recorded session should
be maximally 5 minutes long and have a maximal size of
20MB. If one of these constraints is not fulfilled the video file
will be considered as not submitted.
For recording the desktop session you should again use the
tool gtk-recordMyDesktop
. Do not forget to change
the "Video Quality" to 10 (using the slider), and the "Frames per
Second" to be recorded to 5 (Advanced Setting / Performance) to reduce
the file size. Sound recording is not required.
For submitting the report, the sources, and the desktop session, proceed as follows: Create the following directory structure
assignment2/ assignment2/report/ assignment2/sources/ assignment2/video/
Put your report, sources, and video in the corresponding
directory. The sources should not include the source code of
the complete Linux kernel, but only the files you
changed and those files you added. The report
directory and the sources
directory should not contain more than 10MB of data. Similar to the
size of the video file: If this constraint is not fulfilled the
submission will be not considered.
Change to the directory assignment2
and type aflever DM510
.
Additionally, in contrast to the first required assignment, you shall hand in a printout of your report and of your source code. (Department secretaries office).
The strict deadline for submission (electronically and printouts) is Wednesday, April 21th, 12:00.
As this project is quite resource consuming, please take care, that you clean up your home directory and that you remove unneeded files as soon as possible.
Note that the third required assignment is scheduled to be announced before the submission deadline of this assignment. The last required assignment will require a significantly larger amount of implementation. Therefore I recommend to submit this second required assignment before the strict deadline.
Frequently Asked Questions (FAQ)
- Are we allowed to work in groups?
- When compiling the exampled from the LDD book I get errors like
error: struct task_struct has no member named uid
.
For everything except the report and recording the desktop session, you are allowed to work in groups of at most three people. Write the members of your group on the front page of your report. The report you have to write on your own. Remember that the three required assignments are part of your final exam, i.e., you have to fully understand all parts of what you have done.
Recent kernels use a different handling
of credentials. The
important fact for this assignment is that accessing credentials is
now done via wrappers (for example uid_t
current_uid(void)
for the current's real UID). This used to be
accessible via fields in the structure task_struct
. The
scull driver and especially the file access.c
have been
adapted.