A Linux Kernel Primer

Writing System Calls and Kernel Loadable Modules

The aim of this primer assignment is for you to learn how to write system calls and modules that can be dynamically linked and loaded into the core kernel. This may well prove helpful with any kernel programming projects you may undertake.

Part 1 - Writing a Simple Module

The first part of this assignment requires you to implement a simple kernel loadable module that simply prints two strings S1 and S2 to the console.  S1  is  a string such as "Loading Module..." that is output to the console when your module is first loaded into the kernel, while S2 is is a string such as "Unloading module..." that is output when the module is removed from the kernel.

Observe that init_module() is invoked when a module is first loaded using the `insmod` (or `modprobe`) command. Likewise, cleanup_module() is called when a module is removed from the kernel using `rmmod modulename`.

HINT: A helpful reference for this work is "Linux Device Drivers", by A. Rubini O'Reilly, ISBN: 1-56592-292-1,  1998, or the recently released second edition.

To avoid having to specify the appropriate compiler options, the following preprocessor directives should be placed somewhere near the beginning of your module source file:

#ifndef MODULE
#define MODULE
#endif
#ifndef __KERNEL__
#define __KERNEL__
#endif

The following template Makefile will help with the compilation of your source file module_name.c into an object module_name.o :

INC_PATH        = /usr/src/linux/include
SRCS            =
CFLAGS          = -O -Wall -I$(INC_PATH)
CC              = gcc

all:    test_syscall_module.o test_syscall

.c.o:
        $(CC) $(CFLAGS) -c $*.c

clean:
        rm *~ *.o

You can use any symbols (i.e., functions, global variables etc) that are exported via kernel/ksyms.c in your module. Try `cat /proc/ksyms | more` to see the available symbols. Additionally, you can use the printk() kernel function to print your messages to the console. If they do not appear on the console, this may be due to an insufficiently high enough priority for the message to appear on the console (or logging of kernel messages has been disabled entirely). You may need to edit /etc/syslog.conf and insert a line such as:

kern.*                                                 /dev/console

to enable console logging of kernel messages. Note that when you reboot your (virtual) machine after editing this file, you may see many additional text messages, which should not be cause for alarm.

If you do not modify /etc/syslog.conf, you can always look at the messages in /var/log/messages or simply issue the command `dmesg`. Alternatively, you might want to insert the following code in your module and invoke it instead of using printk():

/* 'printk' version that prints to active tty. */
void my_printk(char *string)
{
  struct tty_struct *my_tty;

  my_tty = current->tty;

  if (my_tty != NULL) {
    (*(my_tty->driver).write)(my_tty, 0, string, strlen(string));
    (*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);
  }
}
 

Part 2 - Writing  a New System Call

This part of the assignment requires you to implement a new system call that takes a string argument and outputs the string to the first console (usually /dev/tty0) from within the kernel. If successful, it returns 0 else it returns -1. The system call will be:
int kprint (char *msg);
You will need* to add a new system call entry in arch/i386/kernel/entry.S and include/asm-i386/unistd.h. If you create a new "stub" entry for your system call in kernel/sys.c you will be able to build your modified kernel without any unresolved symbol errors. The true system call (defined as sys_kprint()) will actually go in the body of your module and will override the stub routine when the module is loaded.

You will then need to rebuild your new kernel image, modifying /etc/lilo.conf or /etc/grub.conf as necessary (NOTE: if using lilo as your primary boot loader, you will need to rerun `lilo` before changes to /etc/lilo.conf take place).
A good source of information on this subject can be found in the Linux Document Project. This is a great source of information on many aspects of Linux but of particular interest for this assignment is the HOWTO document on building and configuring a new kernel.

The next step is to write a simple program that makes use of your system call. Observe that there is no reference to this system call from within the C library (libc), so you'll have to explicitly declare its prototype in the correct format, using one of the syscall macros. Specifically, this will be the _syscall1() macro defined in linux/unistd.h, since your system call takes one argument.

*Footnote: technically speaking, you can actually implement a system call without modifying the core kernel. I'll leave this for you to investigate, if you so wish. In any case, you should practice builing the Linux kernel if you haven't done this before.
 

Miscellaneous

Helper files and more advanced examples will be discussed in due course. Stay tuned...