Sunday, May 10, 2015

Preparing a test VM - Compile and run a module

Hi,

This is the final post in preparing our test environment. We will write a code piece and run it in the VM. You must have finished previous sections (filesystem, kernel and QEMU) to be able to carry out the instructions in this one. After finishing this you should be able to compile and run your own kernel level code.

I prefer to separate my main kernel code and module code. It is logical to configure and compile kernel source once and then use the image until a re-compile is necessary. However we need to compile the module code every time we make a change in the code we write. Therefore we will create a separate directory for the tiny module we are going to create now.

Make a new directory and cd to it. Open a new file named hello.c with your favourite text editor. Write the following code snippet inside:

#include <linux/module.h> //for module stuff
#include <linux/init.h>  //init and exit macros
#include <linux/kernel.h> //printk and other kernel stuff

static int __init hello_init(void)
{
 printk(KERN_ALERT "hello module loaded!\n");
 return 0;
}

static void __exit hello_exit(void)
{
 printk(KERN_ALERT "hello module removed...\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

And we will need a makefile since we will compile our code alongside with kernel. Create a file named Makefile and put the following line in it.

obj-m := hello.o

Now, a little explanation: We have only two functions, both of them writing something to kernel console via printk function. KERN_ALERT means that these messages have alert log level. Kernel prints out messages which have higher log level than kernel log level to the consoles it uses. You can read all kernel messages by using dmesg command. You can find more information about printk on Embedded Linux Wiki.

The __init macro tells the kernel that if this code is compiled into the main kernel binary (ie. not as a module) then it should be run during kernel init phase. Similarly __exit is run during shutdown. We did not have to define them since we will use our code as a module but it is good practice to mark init and exit functions.

module_init(func) macro tells that func should be run when the module is first loaded to memory with insmod. Similarly module_exit(func) macro marks func to be run if the module is removed by using rmmod.

Finally MODULE_LICENSE("GPL") marks the module as GPL. You can also mark a module as "Proprietary" and link it with Linux kernel in run-time. Linking and licensing are important if you are developing a module with a license other than GPL but I will not delve into details in this blog.

How do we compile our little piece of code against the kernel we are booting with QEMU? Normally modules are inside the kernel source directory and the ones which were selected in config are compiled when you issue "make modules" command.

We will do a small trick to compile only our code. First create a symbolic link to your kernel source directory with name "kerneldir" (inside your module dir). Then issue the command below:

make -C kerneldir/ M="$(pwd)" modules

This command tells make to run the Makefile inside kernel source directory with "modules" target but with modules directory as our current dir.

If you didn't introduced a typo your code should compile and you should have a file named "hello.ko" along with others.

Now boot your virtual machine.

A network interface named "tap0" should be created in the host (ie. the computer you are currently using). Give it an IP address and turn it on. I prefer iproute2 package whenever possible. You can use ifconfig too if you are comfortable with that.

sudo ip add address 192.168.1.1/24 dev tap0
sudo ip link set tap0 up

Login to the virtual machine over serial console. Inside it issue ifconfig -a. You should see various network interfaces depending on your kernel configuration. But one of them should have a specific MAC address and its name should be something like "enpXsY" where X and Y are numbers.

You can configure Gentoo to use conventional naming like eth0, eth1 etc. but I think (like many Gentoo users) this is the proper way to name network interfaces. It means the interface is the ethernet card on PCI bus X slot Y.

Now give an address to it and then run ssh deamon. It will create ssh keys under /etc/ssh automatically.

ifconfig enpXsY 192.168.1.41
/etc/init.d/sshd start

Then copy our module "hello.ko" in the host to the VM over ssh using scp utility:

scp hello.ko root@192.168.1.41:/root/

You should be able to see the hello.ko file inside the VM. Now just type insmod hello.ko and your module should be inserted. You should also see the message "hello module loaded" printed on the console if you set your log level low enough. If you don't see the message just type dmesg and it should be at the end of the output. Also you can check with lsmod and you should see your module "hello".

Now type rmmod hello and the module should be removed. You can check the message and lsmod just like before.

That's it. Now we can proudly say that we have a testing environment and we can run our kernel level code in our virtual machine.

I will write an extra post on how to let your VM to the Internet (or some other network outside the host) using an ethernet bridge or a wireless card.

No comments:

Post a Comment