Homework 4
In this homework you will get to know the basics of administering and creating Linux Kernel modules.
Overview
- Documentation (Human Language))
- Preparation
- Assignments
- Linux Kernel
- Busybox
- Dropbear (does not have builtin SFTP support)_)
- Introduction To Cross-Compiled Out-Of-Tree Kernel Modules
- Linux Kernel Modules Driver Development
~ General Instructions for all modules
~ Test Instructions for all modules
- simple_chardev ~ Implementation ~ Test
- openclose ~ Implementation ~ Test
- hello ~ Implementation ~ Test
- mynull ~ Implementation ~ Test
- myzero ~ Implementation ~ Test
- mybuffer ~ Implementation ~ Test
- InitRamDisk
- Run Linux in a Virtual Machine (with qemu))
- Result To Be Submitted (tracked by Git))
- Bonus Assignments
Documentation (Human Language)
A non-technical assignment is to write documentation and answer questions. You can use either English oder German while working on technical assignments. The suggestion is to use English for practicing purposes and to avoid awkward mixins of English technical terms with German ;-)
hw4/QnA.md Question Boxes
Throughout the document you will find several question boxes.
These questions are meant to help you think through what you did and how you can solve the current part of the assignment.
Please keep a protocol for answering these questions in your project repository at $REPO_DIR/hw4/QnA.md
.
The file must contain the questions with brief answers written in your own words.
hw4/README.md (optional)
You can use this document to make the following notes:
- difficulties throughout the homework
- design decisions that are necessary to explain or you think are important to emphasize
Preparation
- Complete Homework 3
- Have SSH/X2Go access to your group's syslab container
Skills You Will Acquire
During this assignment you'll gain experiences in the following activities:
- Use SSH to transfer files (via pipes)
- Install/Load/Unload Linux Kernel Modules
- Write Linux Kernel Modules
Pre-Requisites
Before you proceed, please research the following topics. There is no need get into great detail at this point, but simply get an overview of what they are and how they are related.
- SYSO Lecture Slides - Linux Devices Driver I
- Book (DE): Linux-Treiber entwickeln https://www.dpunkt.de/buecher/12176/9783864902888-linux-treiber-entwickeln.html
Assignments
The following table lists the assignments and their interdependencies.
Assignment | Dependencies |
---|---|
Linux Kernel | - |
Busybox | - |
Dropbear | - |
InitRamDisk | Busybox, Dropbear |
Hello kworld Kernel Module | Linux Kernel Module |
Simple Character Device Kernel Module | Linux Kernel |
OpenClose Kernel Module | Linux Kernel |
Hello Kernel Module | Linux Kernel |
myzero Kernel Module | Linux Kernel |
mynull Kernel Module | Linux Kernel |
mybuffer Kernel Module | Linux Kernel |
qemu for aarch64 (network enabled) | Kernel, InitRamDisk |
Tests | qemu, initrd, All Kernel Modules |
Linux Kernel
You can reuse your previous kernel configuration as a base for this homework.
Configure Linux to support modules
The only change that is required in the kernel configuration is to support kernel module loading.
[success] Questions In the Kernel menuconfig there are two modes of selecting an option now:
- What is the difference between selecting something as Builtin [*] and Module [M]?
- What is the file format of compiled Kernel modules?
Enable Preemptive Systemcalls
Please configure the kernel to enable preemptive systemcals (CONFIG_PREEMPT
) in order to allow the parallel use of the resources exposed by the kernel modules.
Busybox
Configure Busybox to contain the applets for dealing with kernel modules and displaying kernel messages:
- modprobe
- modinfo
- rmmod
- lsmod
- insmod
- dmesg
- depmod
Dropbear (does not have builtin SFTP support)
The kernel modules will be transfered to the running virtual machine via SSH. On prodduction systems the SFTP is used for this, but since dropbear does not have an integrated SFTP server you will use SSH and pipes to transfer files. This doesn't require any additional configuring. The drawbacks are that it is less efficient when transferring multiple files, and does not natively support the transfer of directories.
Example: transfer a file via SSH and pipes
The following example assumes that hw4.sh defines a function called ssh_cmd that wraps the SSH call with the necessary parameters (key, target, port).
$ echo "This is the file content" > tmp/file.txt
$ ./hw4.sh ssh_cmd "cat > /root/myfile.txt" < tmp/file.txt
$ ./hw4.sh ssh_cmd "cat /root/myfile.txt"
This is the file content
If you want to use the SFTP instead of the demonstrated method, you have to implement the bonus assignment for installing the OpenSSH SFTP-Server.
Introduction To Cross-Compiled Out-Of-Tree Kernel Modules
The Linux kernel source code contains hundreds (or thousands?) of modules that you can either build in the kernel image or build as external modules. The file structure of a kernel module can either live within the kernel source directory our outside of it. You will develop a couple of small Linux kernel modules that will be stored in your git repository.
The following Makefile can be used to cross-compile an out-of-tree kernel module:
ifneq ($(KERNELRELEASE),)
M ?= $(shell basename $(PWD))
obj-m := $(M).o
else
ARCH ?= aarch64
KERNEL_ARCH ?= arm64
CROSS_COMPILE ?= ${ARCH}-unknown-linux-gnu-
CC ?= ${CROSS_COMPILE}gcc
PWD ?= $(shell pwd)
KDIR ?= $(PWD)/../../kernel/src
default:
ARCH=$(KERNEL_ARCH) CROSS_COMPILE=$(CROSS_COMPILE) $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
endif
clean:
rm -rf *.ko *.o *.mod.c .*.cmd .tmp_versions *.symvers *.order
[success] Questions
- Which Makefile is called in consequence of this Makefile's default target?
- What are the differences between this Makefile and the one shown in the slides?
Hello Kworld Example Module
Coupled with the above Makefile, this C program can be compiled to a kernel module.
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
static int __init ModInit(void)
{
printk(KERN_ALERT "Hello, kworld\n");
return 0;
}
static void __exit ModExit(void)
{
printk(KERN_ALERT "Goodbye, cruel kworld\n");
}
module_init(ModInit);
module_exit(ModExit);
Create a diretory $REPO/hw4/modules/hello_kworld/ with the Makefile from above and the below C code saved as hello_kworld.c.
Module Life Cycle Excercise
The life cycle of a module in this assignment is the following:
- Develop the module
make
it- From the container to the VM, transfer the kernel object file and store it in the directory
/lib/modules/$(uname -r)/
[success] Questions
The
uname -r
is to be evaluated on the virtual machine side!- Why doesn't it work when it is evaluated in your development container?
- In the VM, load the module using either
modprobe
(requires previousdepmod
) orinsmod
[success] Questions
- What is the difference between the two?
- What does
depmod
do?
- In the VM, use the module:
- Check the kernel log using
dmesg
- Run any commands to use the functionality of the module
- Check the kernel log using
- In the VM, unload the module using
rmmod
Excercise this life cycle with the hello_kworld module from above.
Linux Kernel Modules Driver Development
At this point you should be familiar with the process outlined above, and you are ready to develop and test more complex modules.
General Instructions for all modules
- Use the new device number system to register your device drivers. See lecture slides 54 and 55
- Use the MODULE_ makros to insert author and description into your modules.
You can check this information for each module in the running virtual machine with the
modinfo
command. - You can safely skip any instructions you might encounter for registering udev/mdev as a hotplug manager, since your system uses the kernel managed devtmpfs.
- You can also skip any instructions of using
mknod
, as the node creation is handled by the devtmpfs
- You can also skip any instructions of using
- Create a subdirectory $REPO/hw4/modules/$MODULE with the C code and the Makefile for each of the following modules.
[warning]
- All implemented methods should printk debug messages about what has been called, enriched with information about the specific function call
- Every module must clean up the allocated resources on unload (if possible, else exit non-zero). This is not explicitly mentioned in the module description!
Test Instructions for all modules
Because each module is different, you will write a functional test for each module. Effectively the functional tests will be transferred to the virtual machine in form of an an executable.
[warning]
All module tests should verify that all character device paths exist and the files have the type character device. This can be achieved using the syscall
stat()
or thestat
command line tool.
To your liking, you can use either a shell script or write a program in a language that needs to be compiled to a binary.
For this to work the same for both all test implementations, add a Makefile target called test to each module which produces the test executable.
- For a shell script, this can be a NOOP if the script is named test.
- For compiled programs ensure to use the cross compiler
Each test executable must be copied to the artifacts/$MODULENAME.ko.test.
No matter which language you choose, the tests will (implicitly) call one or more of the following syscalls, depending on the modules functionality and what needs to be tested
open()
,read()
,write()
,close()
simple_chardev
- Learn the basics of creating a character device.
Implementation
Load
- Allocate a character device Region for 1 device
- (Let the system) Create a character device at /dev/simple_chardev
Test
- Verify that the path given above exists and is a character device after the module is loaded.
openclose
- Learn how to handle multiple character devices that share the same major number.
- Learn the basics of handling
open()
andclose()
calls
Implementation
Load
- Allocate a character device Region for N (at least 257) devices
- (Let the system) Create N character devices named /dev/openclose[0, N-1] using the major number of the allocated region. Each device has a different number, which equals its minor number.
Open/Close
- Track how many simultaneous opens each devices has, and only allow minor-times opens per device
[warning] Please use the kernel's atomic type and operations for counting.
- If the limit on a device is reached upon open, EBUSY should be returned
Test
- Demonstrate the simultaneous open limit:
- Each device can only be successfully
open()
ed minor-times and the nextopen()
fails - a succeeding
close()
allows anotheropen()
- Each device can only be successfully
hello
- Learn the basics of returning data for
read()
calls.
Implementation
Load
- Allocate a character device Region for 1 device
- (Let the system) Create a character device at /dev/hello
Read
- Returns a statically defined string on read.
"Hello, from grp$NR"
[success] Questions
- Is it possible to the read from the device without opening it?
Test
- The expected string is read from the device
mynull
- Learn how the
/dev/null
device works in Linux by reimplementing it
Implementation
See man null
:-)
Test
Behaves like /dev/null
, but specifically:
- Read returns EOF
- Writes to the device simply succeed
myzero
- Learn how the
/dev/zero
device works in Linux by reimplementing it
Implementation
See man zero
:-)
Test
Behaves like /dev/zero
, but specifically:
- Random amount of zero characters can be read from the device
- Writes to the device simply succeed
mybuffer
- Implement very simple, unsynchronized buffer with a fixed size
- Learn to use signals in kernel drivers
Implementation
Load
- Allocate a character device region for one device
- (Let the system) Create a character device at /dev/mybuffer
Write
- Write data from the caller into the buffer starting from the beginning, regardless whether the buffer has unread content or is empty
- Signal any waiting reader
Read
- If the buffer is empty, the caller shall wait until a write is signaled
- Read data in the buffer and pass it to the caller;
[info] do not remove the data on read
[success] Questions
- How is the memory for your buffer allocated? When is it freed?
- Which race conditions exist in this implementation? / What happens if more than one process use the buffer simultaneously?
Test
- Demonstrate these default cases:
- Read from empty buffer (and wait)
- Write to an empty buffer (wakes up waiting reader)
- Read full buffer
- Write to full buffer
- Demonstrate how simultaneous access from multiple concurrent readers and writers exposes the existence of states with inconsistent data, i.e. expose the race-condition
[warning] The race condition needs to be triggered reliably, as you will reuse this test to prove that the synchronized implementation in the next assignment doesn't contain this race condition.
InitRamDisk
Optionally, you can create the directory at /lib/modules/$(uname -r)/
in your init script.
Run Linux in a Virtual Machine (with qemu)
In order to increase concurrency within the system, please use qemu's -smp N
argument, where N should be at least 2 in order to simulate multiple processors.
Result To Be Submitted (tracked by Git)
This section gives you information which files are part of the submission for this homework. Results for bonus assignments are not covered within this section.
Test-Suite and Continous Integration
Merge and Run the Continous-Integration test suite.
Documentation files
(not shown in the above tree) Please include the documentation files that are explained in the beginning.
Build Instructions (hw4/hw4.sh)
A shell script that reproduces the final result of your homework. This shell script will be used to verify your results, and does not need to include commands that run the interactive menuconfig. However, you may implement such functionality for working conveniently within your homework repository.
Arguments and Script behavior
Arguments | Function |
---|---|
(called without any arguments) | Build all artifacts starting with just the files that are checked in to git |
qemu | Run qemu-system-aarch64 , booting your system with the initrd and network |
modules_build | Build all modules and their tests (if build is needed) and copy *.ko and *.ko.test files to the artifacts/ directory |
modules_copy | Copy all kernel modules, test executables (and shared libraries if necessary) via SSH; should use ssh_cmd |
modules_load | Load all copied kernel modules |
modules_test | Run the test for each module |
modules_unload | Unload all kernel modules (should not error if none are loaded) |
modules | Run modules_{build,copy,load,test,unload} in the given order |
clean | Remove all files not tracked by git |
ssh_cmd cmd [args...] | Establish a connection to the VM's SSH server via authorized_key authentication. It runs the specified command with all arguments inside the VM. An example would be ssh_cmd "echo Hello, World" , as found in the CI scripts. |
Build Artifacts (Binary Files)
The following files must be present in at hw4/artifacts/
after the build is complete but not tracked by git!
File | Target Architecture | Purpose |
---|---|---|
Image.gz | aarch64 (arm64) | Kernel binary used for qemu |
One *.ko file per module | aarch64 (arm64) | Kernel Objects |
One *.ko.test file per module | aarch64 (arm64) | Executable to test the given Kernel Object |
sysinfo | aarch64 | Statically (dynamically, if you completed HW3/Bonus1) linked binary of your little C program |
dropbearmulti | aarch64 | Statically (dynamically, if you completed HW3/Bonus1) linked multicall binary for Dropbear |
initrd.cpio | aarch64 | Initial RamDisk file in the form of a cpio archive |
Other Files and Git
[warning] Please do not add binary files to your git repository. Only add files that represent configuration, source code and build commands.
Bonus Assignments
These optional assignments allow you to dig in a little deeper! They don't depend on each other so you can cherry-pick the ones you are interested in.