This post is the first post in linux kernel series. Writing code to run in the kernel is different from user application. While developing in the kernel, you don’t write code from scratch, you need to implement one or more interfaces and register your implementation within the a kernel subsystem.

Kernel Interfaces

The kernel is written in C , so create an interface we use a structure with function pointers

struct somedevice { int (*fn1)(int,int); void (*fn2)(int); int (*fn3)(void); .... }; 1 2 3 4 5 6 struct somedevice { int ( * fn1 ) ( int , int ) ; void ( * fn2 ) ( int ) ; int ( * fn3 ) ( void ) ; . . . . } ;

Also, the subsystem provides a function(s) that accept that interface and register it as a new object in the kernel:

int register_somedevice(struct somedevice *device); 1 int register_somedevice ( struct somedevice * device ) ;

You need to implement some functions from the interface (usually not all) and create an object from the structure, initialize it with the functions and send it to the register_xxx function.

Simple Example – Real Time Clock

To add a new real time clock to the kernel, you need to implement the following interface (taken from rtc.h)

struct rtc_class_ops { int (*ioctl)(struct device *, unsigned int, unsigned long); int (*read_time)(struct device *, struct rtc_time *); int (*set_time)(struct device *, struct rtc_time *); int (*read_alarm)(struct device *, struct rtc_wkalrm *); int (*set_alarm)(struct device *, struct rtc_wkalrm *); int (*proc)(struct device *, struct seq_file *); int (*set_mmss64)(struct device *, time64_t secs); int (*set_mmss)(struct device *, unsigned long secs); int (*read_callback)(struct device *, int data); int (*alarm_irq_enable)(struct device *, unsigned int enabled); int (*read_offset)(struct device *, long *offset); int (*set_offset)(struct device *, long offset); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct rtc_class_ops { int ( * ioctl ) ( struct device * , unsigned int , unsigned long ) ; int ( * read_time ) ( struct device * , struct rtc_time * ) ; int ( * set_time ) ( struct device * , struct rtc_time * ) ; int ( * read_alarm ) ( struct device * , struct rtc_wkalrm * ) ; int ( * set_alarm ) ( struct device * , struct rtc_wkalrm * ) ; int ( * proc ) ( struct device * , struct seq_file * ) ; int ( * set_mmss64 ) ( struct device * , time64_t secs ) ; int ( * set_mmss ) ( struct device * , unsigned long secs ) ; int ( * read_callback ) ( struct device * , int data ) ; int ( * alarm_irq_enable ) ( struct device * , unsigned int enabled ) ; int ( * read_offset ) ( struct device * , long * offset ) ; int ( * set_offset ) ( struct device * , long offset ) ; } ;

The minimum implementation is read_time and set_time, use the examples from the source (drivers/rtc) to find it.

To register a new Real time clock, call the function:

struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner) 1 2 3 4 struct rtc_device * rtc_device_register ( const char * name , struct device * dev , const struct rtc_class_ops * ops , struct module * owner )

So to implement a new RTC in linux create 2 functions for read_time and set_time , declare a structure object and call rtc_device_register:

static int my_rtc_read_time(struct device *dev, struct rtc_time *tm) { tm->tm_sec = ... // read from the hardware tm->tm_min = ... // and fill this structure tm->tm_hour = ... tm->tm_wday = ... tm->tm_mday = ... tm->tm_mon = ... tm->tm_year = ... return rtc_valid_tm(tm); } static int my_rtc_set_time(struct device *dev, struct rtc_time *tm) { // map hwregs to the RTC hardware registers hwregs.tsec = ... hwregs.sec = ... hwregs.min = ... hwregs.hour = ... regs.wday = ... regs.month = ... regs.year = ...; return 0; } static const struct rtc_class_ops my_ops = { .read_time = my_rtc_read_time, .set_time = my_rtc_set_time, }; int some_init_function(void) { .... .... res = rtc_device_register( "myrtc", NULL, &my_ops, THIS_MODULE); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static int my_rtc_read_time ( struct device * dev , struct rtc_time * tm ) { tm -> tm_sec = . . . // read from the hardware tm -> tm_min = . . . // and fill this structure tm -> tm_hour = . . . tm -> tm_wday = . . . tm -> tm_mday = . . . tm -> tm_mon = . . . tm -> tm_year = . . . return rtc_valid_tm ( tm ) ; } static int my_rtc_set_time ( struct device * dev , struct rtc_time * tm ) { // map hwregs to the RTC hardware registers hwregs . tsec = . . . hwregs . sec = . . . hwregs . min = . . . hwregs . hour = . . . regs . wday = . . . regs . month = . . . regs . year = . . . ; return 0 ; } static const struct rtc_class_ops my_ops = { . read_time = my_rtc_read_time , . set_time = my_rtc_set_time , } ; int some_init_function ( void ) { . . . . . . . . res = rtc_device_register ( "myrtc" , NULL , & my_ops , THIS_MODULE ) ; }

As you can see the task is simple, write some functions , implement them like everyone does (look at the source for many examples) , create an interface object and call the register function

When you write a kernel module it can be:

Device driver

Network module

File system

Security module

and more …

It doesn’t matter what you want to write, its always the same process: implement one or more interfaces and register it using functions provided by the sub system. Most of the times, you will find some documentation files in /Documentation folder and the key point to success is – USE THE SOURCE CODE – you can find many working examples in the code – use it

Writing a Simple Module

As mentioned above, after implementing an interface, we need to register it with the system. To do that we need a code that runs on init.

The simplest module must declare 2 functions – on for init and one for exit. The module can be loaded with the kernel on startup (and unloaded on shutdown) or explicitly using insmod command (and rmmod for unload) – this is called a Loadable Kernel Module

The simplest module looks like this:

simp.c

#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Liran B.H"); static int simple_init(void) { printk(KERN_ALERT "hello...

"); return 0; } static void simple_cleanup(void) { printk(KERN_WARNING "bye ...

"); } module_init(simple_init); module_exit(simple_cleanup); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> MODULE_LICENSE ( "Dual BSD/GPL" ) ; MODULE_AUTHOR ( "Liran B.H" ) ; static int simple_init ( void ) { printk ( KERN _ ALERT "hello...

" ) ; return 0 ; } static void simple_cleanup ( void ) { printk ( KERN _ WARNING "bye ...

" ) ; } module_init ( simple_init ) ; module_exit ( simple_cleanup ) ;

The module declares 2 functions

simple_init – runs when we use insmod command

simple_cleanup – runs we use rmmod command

Both functions use printk – to write a message to the kernel log

To build the module we need the following Makefile:

obj-m := simp.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: default default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions 1 2 3 4 5 6 7 8 9 10 11 12 obj - m : = simp . o KERNELDIR ? = / lib / modules / $ ( shell uname - r ) / build PWD : = $ ( shell pwd ) all : default default : $ ( MAKE ) - C $ ( KERNELDIR ) M = $ ( PWD ) modules clean : rm - rf * . o * ~ core . depend . * . cmd * . ko * . mod . c . tmp_versions

On any linux distribution , you will find the kernel Makefile and headers in /lib/modules/[version]/build

Our Makefile calls the kernel one to build the module. run make in the directory and it will build simp.ko file

To load the module to the kernel use:

# sudo insmod ./simp.ko 1 # sudo insmod ./simp.ko

To see the kernel log use dmesg

# dmesg | tail -1 [89087.329507] hello... 1 2 # dmesg | tail -1 [ 89087.329507 ] hello . . .

To unload the module use rmmod command:

# sudo rmmod simp 1 # sudo rmmod simp

And again, you will see the output in the kernel log using dmesg command

printk function

printk write message to the kernel log.

The printk function is similar to stdlib’s printf(3) but No floating point format. Log message are prefixed with a “<0>” , where the number denotes severity, from 0 (most severe) to 7. Macros are defined to be used for severity levels: KERN_EMERG, KERN_ALERT, KERT_CRIT, KERN_ERR, KERN_WARNING, KERN_NOTICE, KERN_INFO, KERN_DEBUG For example

printk(KERN_ALERT "hello...

"); 1 printk ( KERN _ ALERT "hello...

" ) ;

is simply writing <1>hello to the kernel log

if you run dmesg on ubuntu you will see that the output is coloured different for the init and exit functions:

Thats because we logged the init message with ALERT and the cleanup with WARNING

You can control the display filter to the system console using the file /proc/sys/kernel/printk