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 more recent kernels can be found here (tested for kernel 5.6-rc1, original code see here). The example code from LDD for the scull driver that you have to study for this assignment was updated to work with kernel 5.6-rc1 (see FAQ - the main changes in comparison to the version as presented in the book are related to handling of credentials). You can also use The Linux Kernel Module Programming Guide (pdf, 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

image

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 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 5.6-rc1

  • 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).

  • A simple blocking mechanism has to be implemented (see Chapter 6 of LDD, page 147ff). You could for example consider to put processes to sleep using simple sleeping (see the LDD book), if too many processes want to read from the buffer. Then you could use ioctl to wake these processes up. However, this is just a suggestion, any reasonable simple mechanism where sleeping is used is fine.

  • 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 5.6-rc1

  • udev and devfs 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, 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 *);

long dm510_ioctl(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 how and when you put processes asleep, and how you wake them up again.

  • 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 can again use the tool gtk-recordMyDesktop if you record on a Linux system. 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.

Recommendations to record under Windows and Mac OS will follow (we expect Zoom will do).

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.

Obviously, no printout submission is needed.

Package and compress with the directory with zip -r assignment2 assignment2. Submit the created zip file in blackboard. The strict deadline for submission (electronically) is April 16th, 16: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 last required assignment is scheduled to be announced before the submission deadline of this assignment. The last required assignment will require quite some omplementation work. Therefore I recommend to submit this assignment before the strict deadline.

Frequently Asked Questions (FAQ)

  • Are we allowed to work in groups?

    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 four required assignments are part of your final exam, i.e., you have to fully understand all parts of what you have done.

  • Kernel changes made it necessary to change the scull driver code and the templates for this assignment (compared to the original LDD versions). What follows are questions/changes based on these changes:

    If you try to compile the examples from the LDD book and you get errors like error: struct task_struct has no member named uid , the following is the reason :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. init_MUTEX is deprecated, however due to the simple macro that was used (#define init_MUTEX(_m) sema_init(_m,1)) this is easy to fix and was done in the scull driver. The ioctl ops in struct file_operations is deprecated since kernel version 2.6.36. It needs to be converted to unlocked_ioctl ops. This was done for the scull driver. See for example here. This also infuenced the declaration of dm510_ioctl in the templates given for this assignment compared to assignments of previous years. In 2012 there was the start of distinguishing between internal kernel uids and gids (details see here) and values that userspace can use. This was done by introducing two new types: kuid_t and kgid_t. These types and their associated functions are declared in the new header uidgid.h. For the scull driver we needed to map uids, e.g., current->cred->uid was changed to __kuid_val(current->cred->uid) (and similar). This was modified in access.c.