mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-26 19:51:35 +01:00
kernel module: fix fops... and move its documentation into README
Sometimes I wonder if anyone has ever run this tutorial, otherwise how can such basic bugs persist for so long? test_all.sh: crete
This commit is contained in:
54
README.adoc
54
README.adoc
@@ -2877,8 +2877,52 @@ You should then look up if there is a branch that supports that kernel. Staying
|
|||||||
|
|
||||||
=== Pseudo filesystems
|
=== Pseudo filesystems
|
||||||
|
|
||||||
|
Pseudo filesystems are filesystems that don't represent actual files in a hard disk, but rather allow us to do special operations on filesystem-related system calls.
|
||||||
|
|
||||||
|
Some notable examples include:
|
||||||
|
|
||||||
|
* procfs, often mounted at: `/proc`
|
||||||
|
* sysfs, often mounted at: `/sys`
|
||||||
|
* devtmpfs, often mounted at: `/dev`
|
||||||
|
* debugfs, often mounted at: `/sys/kernel/debug/`
|
||||||
|
|
||||||
|
What each pseudo-file does for each related system call does is defined by its <<file-operations>>.
|
||||||
|
|
||||||
|
Bibliography:
|
||||||
|
|
||||||
|
* https://superuser.com/questions/1198292/what-is-a-pseudo-file-system-in-linux
|
||||||
|
* https://en.wikipedia.org/wiki/Synthetic_file_system
|
||||||
|
|
||||||
|
==== File operations
|
||||||
|
|
||||||
|
In guest:
|
||||||
|
|
||||||
|
....
|
||||||
|
/fops.sh
|
||||||
|
echo $?
|
||||||
|
....
|
||||||
|
|
||||||
|
Outcome: the test passes:
|
||||||
|
|
||||||
|
....
|
||||||
|
0
|
||||||
|
....
|
||||||
|
|
||||||
|
Sources:
|
||||||
|
|
||||||
|
* link:kernel_module/fops.c[]
|
||||||
|
* link:rootfs_overlay/fops.sh[]
|
||||||
|
|
||||||
|
File operations is the main method of userland driver communication.
|
||||||
|
|
||||||
|
`struct file_operations` determines what the kernel will do on filesystem system calls of <<pseudo-filesystems>>.
|
||||||
|
|
||||||
|
No, there no official documentation: http://stackoverflow.com/questions/15213932/what-are-the-struct-file-operations-arguments
|
||||||
|
|
||||||
==== Character device
|
==== Character device
|
||||||
|
|
||||||
|
In guest:
|
||||||
|
|
||||||
....
|
....
|
||||||
/character_device.sh
|
/character_device.sh
|
||||||
echo $?
|
echo $?
|
||||||
@@ -7579,7 +7623,15 @@ Should:
|
|||||||
* make a network request
|
* make a network request
|
||||||
* shutdown gracefully
|
* shutdown gracefully
|
||||||
|
|
||||||
TODO automate all of this with a `/test-all.sh` script in guest which outputs to stdout `LKMC_TEST_PASS` or `LKMC_TEST_FAIL` and grep that from host.
|
We are slowly automating testable guest tests with:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run -F '/test_all.sh;/poweroff.out' | grep lkmc_test
|
||||||
|
....
|
||||||
|
|
||||||
|
which outputs to stdout `lkmc_test_pass` or `lkmc_test_fail` to stdout, which we can grep from host to automate testing.
|
||||||
|
|
||||||
|
Source: link:rootfs_overlay/test_all.sh[].
|
||||||
|
|
||||||
===== Host testing
|
===== Host testing
|
||||||
|
|
||||||
|
|||||||
2
configure
vendored
2
configure
vendored
@@ -109,11 +109,11 @@ fi
|
|||||||
# In particular:
|
# In particular:
|
||||||
# - `shallow = true` on the submodule has no effect for the non default educational branches of our submodules
|
# - `shallow = true` on the submodule has no effect for the non default educational branches of our submodules
|
||||||
# - QEMU's submodules point to commits that are neither under branches nor tags, and so `--shallow-submodules` fails
|
# - QEMU's submodules point to commits that are neither under branches nor tags, and so `--shallow-submodules` fails
|
||||||
#
|
|
||||||
git submodule update --depth 1 $gitjobs --init -- $submodules
|
git submodule update --depth 1 $gitjobs --init -- $submodules
|
||||||
if "$qemu"; then
|
if "$qemu"; then
|
||||||
cd qemu
|
cd qemu
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
fi
|
fi
|
||||||
) &
|
) &
|
||||||
|
# https://unix.stackexchange.com/questions/65532/why-does-set-e-not-work-inside-subshells-with-parenthesis-followed-by-an-or
|
||||||
wait $! || git submodule update --init --recursive -- $submodules
|
wait $! || git submodule update --init --recursive -- $submodules
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
. Pseudo filesystems
|
. Pseudo filesystems
|
||||||
.. link:anonymous_inode.c[]
|
.. link:anonymous_inode.c[]
|
||||||
.. link:debugfs.c[]
|
.. link:debugfs.c[]
|
||||||
.. link:fops.c[]
|
|
||||||
.. link:ioctl.c[]
|
.. link:ioctl.c[]
|
||||||
.. link:mmap.c[]
|
.. link:mmap.c[]
|
||||||
.. link:poll.c[]
|
.. link:poll.c[]
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ static int myinit(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Created on the toplevel of the debugfs mount,
|
/* Created on the toplevel of the debugfs mount,
|
||||||
* and with explicit fops instead of a fixed integer value. */
|
* and with explicit fops instead of a fixed integer value.
|
||||||
|
*/
|
||||||
toplevel_file = debugfs_create_file(
|
toplevel_file = debugfs_create_file(
|
||||||
"lkmc_debugfs_file", S_IWUSR, NULL, NULL, &fops);
|
"lkmc_debugfs_file", S_IWUSR, NULL, NULL, &fops);
|
||||||
if (!toplevel_file) {
|
if (!toplevel_file) {
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
/*
|
|
||||||
Basic fops example, with a fixed size static data buffer.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
/fops.sh
|
|
||||||
|
|
||||||
The buffer can be written and read from. If data overflows, data is thrown away.
|
|
||||||
|
|
||||||
No, there ain't no official docs:
|
|
||||||
http://stackoverflow.com/questions/15213932/what-are-the-struct-file-operations-arguments
|
|
||||||
|
|
||||||
fops define what the kernel will do on filesystem system calls on all of
|
|
||||||
/dev, /proc, /sys, and consistute the main method of userland communication
|
|
||||||
in drivers (syscalls being the other one).
|
|
||||||
|
|
||||||
Here we use debugfs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/errno.h> /* EFAULT */
|
#include <linux/errno.h> /* EFAULT */
|
||||||
#include <linux/fs.h> /* file_operations */
|
#include <linux/fs.h> /* file_operations */
|
||||||
@@ -39,7 +20,7 @@ static int open(struct inode *inode, struct file *filp)
|
|||||||
* We must increment this by the ammount of bytes read.
|
* We must increment this by the ammount of bytes read.
|
||||||
* Then when userland reads the same file descriptor again,
|
* Then when userland reads the same file descriptor again,
|
||||||
* we start from that point instead.
|
* we start from that point instead.
|
||||||
* */
|
*/
|
||||||
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
|
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
|
||||||
{
|
{
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
@@ -65,7 +46,8 @@ static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off
|
|||||||
/* Similar to read, but with one notable difference:
|
/* Similar to read, but with one notable difference:
|
||||||
* we must return ENOSPC if the user tries to write more
|
* we must return ENOSPC if the user tries to write more
|
||||||
* than the size of our buffer. Otherwise, Bash > just
|
* than the size of our buffer. Otherwise, Bash > just
|
||||||
* keeps trying to write to it infinitely. */
|
* keeps trying to write to it infinitely.
|
||||||
|
*/
|
||||||
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
|
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
|
||||||
{
|
{
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
@@ -92,10 +74,9 @@ static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Called on the last close:
|
||||||
Called on the last close:
|
* http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
|
||||||
http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
|
*/
|
||||||
*/
|
|
||||||
static int release(struct inode *inode, struct file *filp)
|
static int release(struct inode *inode, struct file *filp)
|
||||||
{
|
{
|
||||||
pr_info("release\n");
|
pr_info("release\n");
|
||||||
|
|||||||
@@ -1,43 +1,30 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
set -x
|
# Setup
|
||||||
|
f=/sys/kernel/debug/lkmc_fops
|
||||||
insmod /fops.ko
|
insmod /fops.ko
|
||||||
cd /sys/kernel/debug/lkmc_fops
|
|
||||||
|
|
||||||
## Basic read.
|
# read
|
||||||
cat f
|
[ "$(cat "$f")" = abcd ]
|
||||||
# => abcd
|
|
||||||
# dmesg => open
|
|
||||||
# dmesg => read
|
|
||||||
# dmesg => len = [0-9]+
|
|
||||||
# dmesg => close
|
|
||||||
|
|
||||||
## Basic write
|
# write
|
||||||
|
printf 01 > "$f"
|
||||||
|
[ "$(cat "$f")" = 01cd ]
|
||||||
|
|
||||||
printf '01' >f
|
# ENOSPC
|
||||||
# dmesg => open
|
printf abcd > "$f"
|
||||||
# dmesg => write
|
set +e
|
||||||
# dmesg => len = 1
|
printf 12345 > "$f"
|
||||||
# dmesg => buf = a
|
exit_status="$?"
|
||||||
# dmesg => close
|
set -e
|
||||||
|
[ "$exit_status" -eq 8 ]
|
||||||
|
[ "$(cat "$f")" = abcd ]
|
||||||
|
|
||||||
cat f
|
# seek
|
||||||
# => 01cd
|
printf 1234 > "$f"
|
||||||
# dmesg => open
|
printf z | dd bs=1 of="$f" seek=2
|
||||||
# dmesg => read
|
[ "$(cat "$f")" = 12z4 ]
|
||||||
# dmesg => len = [0-9]+
|
|
||||||
# dmesg => close
|
|
||||||
|
|
||||||
## ENOSPC
|
# Teardown
|
||||||
printf '1234' >f
|
rmmod fops
|
||||||
printf '12345' >f
|
|
||||||
echo "$?"
|
|
||||||
# => 8
|
|
||||||
cat f
|
|
||||||
# => 1234
|
|
||||||
|
|
||||||
## seek
|
|
||||||
printf '1234' >f
|
|
||||||
printf 'z' | dd bs=1 of=f seek=2
|
|
||||||
cat f
|
|
||||||
# => 12z4
|
|
||||||
|
|||||||
14
rootfs_overlay/test_all.sh
Executable file
14
rootfs_overlay/test_all.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
(
|
||||||
|
set -ex
|
||||||
|
/character_device.sh
|
||||||
|
/character_device_create.sh
|
||||||
|
/fops.sh
|
||||||
|
)
|
||||||
|
if [ "$?" -eq 0 ]; then
|
||||||
|
echo lkmc_test_pass
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo lkmc_test_fail
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user