CSE791 IoT: Notes for Mar 27, 2020
27 Mar 2020Why are we stuyding Linux drivers?
- To write TUN/TAP equivalence for arbitrary protocol.
- Allows tunneling for custom protocol.
Resources
Three types of devices in Linux:
- char devices
- A device that can be accessed as a stream of bytes.
- Implements
open
,close
,read
,write
system calls. - Accessed by means of filesystem nodes (e.g.
/dev/ttyS0
,/dev/tun
). - Usually only allows sequantial access, not random access
- block devices
- A device that can hold a filesystem.
- Operates on blocks (e.g. 512 KB).
- Provides similar API as char devices to the user.
- network interface
- Can be hardware or pure software device.
- Network drivers know nothing about the connections. They only handle packets.
Kernel programming
- Kernel programs do not link against
libc
libraries. It only links against the kernel. Therefore, nearly all common headers we use are not allowed in kernel programs. (That is, nomalloc
, nomemset
, noprintf
, etc.) This also makes C++ totally unfeasible in the kernel. - Kernel programs must be concurrent. Data structures must be designed to keep multiple threads of execution separate.
- Kernel programs share kernel’s stack, which is very small (usually 4096B). Large structures needs to be dynamically allocated.
- Kernel programs do not support floating point arithmetic. Only integer types are allowed.
A “correct” kernel build file
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
- This Makefile is actually called twice in the building process.
- In the first pass, the
$(MAKE)
command executes the kernel build system.-C
changes directory to the kernel source directory,M
moves back into module source directory before buildingmodules
target. - In the second pass, the kernel build system examines the Makefile to determine build targets and dependencies.
A kernel module example
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_int(void){
printk(KERN_ALERT "Hello\n");
}
static void hello_exit(void){
printk(KERN_ALERT "Goodbye\n");
}
static void module_function(void){
// ...
}
module_init("hello_init");
module_exit("hello_exit");
-
Race condition: race condition is everywhere in the kernel. It is possible that during the halfway of your initialization function, after you register a module function, the function is called right away before the initialization function finishes. Therefore, a lot of work needs to be done to ensure a kernel module works properly.
-
Sometimes, there will be tokens before kernel function declarations:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int __init hello_int(void){ printk(KERN_ALERT "Hello\n"); } static void __exit hello_exit(void){ printk(KERN_ALERT "Goodbye\n"); } module_init("hello_init"); module_exit("hello_exit");
-
__init
token: the module loader will drop the function after initialization to save memory space. -
__exit
token: specify that this function is to be called when unloading. If this kernel module is built directly into the kernel, the function body will be discarded. -
If you do not define a exit function, the kernel will not allow the module to be unloaded.
-
Module parameters: a module can take the following types of parameters when loaded:
bool
,charp
, and integer types. It is registered in the kernel module code. This allows the user to change the behavior of the kernel module when loading it.static char *whom; static int howmany; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);
For example, when we are loading the virtual WPAN interface, we can specify number of interfaces with module parameters.
modprobe fakelb numlbs=2
Metadata
A kernel module usually contains some descriptive metadata. We can show the metadata of a kernel module using modinfo
.
Showing kernel module metadata:
(base) user@machine:~$ modinfo /lib/modules/4.15.0-88-generic/kernel/drivers/acpi/video.ko filename: /lib/modules/4.15.0-88-generic/kernel/drivers/acpi/video.ko license: GPL description: ACPI Video Driver author: Bruno Ducrot srcversion: 1E58EF1A1E4284895F3AF07 alias: acpi*:LNXVIDEO:* depends: retpoline: Y intree: Y name: video vermagic: 4.15.0-88-generic SMP mod_unload signat: PKCS#7 signer: sig_key: sig_hashalgo: md4 parm: brightness_switch_enabled:bool parm: allow_duplicates:bool parm: disable_backlight_sysfs_if:int parm: report_key_events:0: none, 1: output changes, 2: brightness changes, 3: all (int) parm: hw_changes_brightness:Set this to 1 on buggy hw which changes the brightness itself when a hotkey is pressed: -1: auto, 0: normal 1: hw-changes-brightness (int) parm: device_id_scheme:bool parm: only_lcd:int
A well-known one is MODULE_LICENSE
. If MODULE_LICENSE
is not called with a open source license, the kernel module will be considered proprietary. This would put restrictions on the kernel module.
Kernel symbol table
The table contains global kernel items—functions and variables—that are needed to implement modularized drivers. The public symbol table can be read in text form from the file /proc/kallsyms
.
(base) user@machine:/proc$ sudo more /proc/kallsyms 0000000000000000 A irq_stack_union 0000000000000000 A __per_cpu_start 0000000000004000 A cpu_debug_store 0000000000005000 A cpu_tss_rw 0000000000008000 A gdt_page 0000000000009000 A exception_stacks 000000000000e000 A entry_stack_storage 000000000000f000 A espfix_waddr 000000000000f008 A espfix_stack 000000000000f010 A cpu_llc_id
A kernel module can put new symbols into the symbol table with EXPORT_SYMBOL
or EXPORT_SYMBOL_GPL
. the latter call will make the symbol visible to GPL-licensed modules only.
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
Once a symbol is put into the table, it is available across all kernel modules. For example, if the kernel module is a USB driver, it will add an entry for read_usb
function into the symbol table for other drivers to use.
Loading a kernel module
- There are two utilities for loading kernel modules:
insmod
andmodprobe
. Kernel module loaders act likeld
(the dynamic linker) for user-space programs. They would link the compiled kernel module against the functions provided by the kernel. No user-space library is involved.
- The difference between
insmod
andmodprobe
is:insmod
will only load the current module. If the module has dependencies, it will stop and report an unresolved reference error.modprobe
however, will automatically load dependencies.
Difference between network interface and char/block device
- Unix philosophy: everything is a file. However, network interfaces cannot be designed as simple files.
- Multiple
read
,write
operations on sockets can happen on one network interface. - Block drivers operate only in response to the kernel. Network drivers receives packets asynchronously from outside, and asks the kernel to act.
- Network drivers also have to support administrative tasks, such as setting addresses, modifying transmission parameters and maintaining statistics.
- Multiple
- The Linux network subsystem is designed to be protocol independent. Therefore, it should be easy to write a driver for custom protocol.
Analyzing loopback.c
skb_orphan
: If a buffer currently has an owner then we call the owner’s destructor function and make theskb
unowned. The buffer continues to exist but is no longer charged to its former owner.skb_dst_force
: it’s used for “refcounting” , krefs or refcounting is a kernel design pattern where a counter is incremented by 1 when some method references it , and decremented when he reference is released , to thenfree()
the object.eth_type_trans
: Used to determine the packet protocol id and insert it back inskb-> protocol