A helloworld kernel module

Today, I experimented with the linux kernel modules for the first time and I’ve written a simple module that prints a message (helloworld :P) every time that someone reads from the /dev/ktest (a custom character device) and counts how many times the device was opened for reading.

Here’s what I’ve done step-by-step:

  1. I made sure that the linux headers are installed. On Debian:
    # apt-get install linux-headers-`uname -r`
  2. I wrote a Makefile:
    obj-m += ktest.o
    
    .PHONY: all
    all:
     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    .PHONY: clean
    clean:
     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

    The Makefile runs make in in /lib/modules/`uname -r`/build. This directory contains another Makefile which is used to actually compile my module.

  3. After the Makefile, I wrote the code of the test module (ktest.c: https://bitbucket.org/eleni-hikiko/kernel-helloworld-test/src/dbbd63da261fcd4f10c5d39a80a8db3e8c04583f/ktestmodule/ktest.c). Let’s examine it line by line:Every module starts by calling some macros. Apart from those for the license, author, description that obviously set the license, the author and the description, we need to call module_init and module_exit. The module_init macro’s parameter is a function pointer to a module initialization callback that will be called at the begining, whereas the module_exit macro’s parameter is a function pointer to a callback that will be called at the end. Here the callbacks are the ktest_ini and ktest_fini functions:
    /* macros */
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("hikiko");
    MODULE_DESCRIPTION("my kernel helloworld");
    
    module_init(ktest_init);
    module_exit(ktest_fini);

    Their implementation:

    static int ktest_init(void)
    {
    	hello_counter = 0;
    
    	/* register character device */
    	if((dev_num = register_chrdev(0, DEV_NAME, &ops)) < 0) {
    		printk(KERN_ALERT "Failed to register character device.n");
    		return -1;
    	}
    	printk(KERN_INFO "Registered character device. Name: %s number: %d.n", DEV_NAME, dev_num);
    	return 0;
    }

    The initialization callback registers a character device (that we previously defined as DEV_NAME “/dev/ktest”) and prints information on success or failure.

    static void ktest_fini(void)
    {
    	unregister_chrdev(dev_num, DEV_NAME);
    	printk(KERN_INFO "Unregistered character device. Name: %s number: %d.n", DEV_NAME, dev_num);
    }

    The exit callback unregisters the device.In order to perform operations on this device, we need to fill a struct of type file_operations:

    static struct file_operations ops = {
    	.open = ktest_open,
    	.release = ktest_release,
    	.read = ktest_read
    };

    We don’t need to fill all the fields, but only those that interest us. The rest will be set to zero automatically. By populating .open, .release, .read we set the functions that are going to be called when we open, release and read the device, respectively.

    static int ktest_open(struct inode *inode, struct file *file)
    {
    	/* 1 device file only => we ignore the params ^ */
    	try_module_get(THIS_MODULE);
    	return 0;
    }

    This function is used to tell the kernel which device we want to open (inode = the filesystem node). Since we only have one device file we can ignore the parameters for simplicity and call try_module_get(THIS_MODULE). This call is supposed to be unsafe in general but I didn’t care much since this is only a helloworld example and I wanted to keep things simple.

    static int ktest_release(struct inode *inode, struct file *file)
    {
    	module_put(THIS_MODULE);
    	return 0;
    }
    

    Same here, I ignored the parameters and used module_put for simplicity.

    The function that is called when we read from the device is the following:

    static ssize_t ktest_read(struct file *file, char *buf, size_t buf_size,
    		loff_t *offset)
    {
    	/* buf = userspace buffer,
    	 * kbuf = kernel buffer */
    
    	char *kbuf;
    	int bytes;
    
    	if(!(kbuf = kmalloc(buf_size, GFP_KERNEL))) {
    		printk(KERN_ALERT "Failed to allocate memory.n");
    		return -ENOMEM;
    	}
    
    	/* fill kbuf and copy to userspace buf */
    	bytes = snprintf(kbuf, buf_size, "Hello world, num: %d.n", ++hello_counter);
    
    	if(copy_to_user(buf, kbuf, bytes))
    	{
    		kfree(kbuf);
    		return -EFAULT;
    	}
    
    	kfree(kbuf);
    	return bytes;
    }

    This function takes as a parameter a pointer to the device, a pointer to a userspace buffer and the buffer size and offset. What I’ve done here was to create a kernelbuffer (kbuf) in the size of the userspace buffer, fill it with some text, copy it to the userspace buffer (to pass it to the user program) and free the kernel buffer.

  4. Next step was to insert the module to the kernel and test it works.
    Firstly, I created a device /dev/ktest manually with:

    # mknod /dev/ktest

    There might be a better way to create devices using for example udev when the module is loaded or something else but I didn’t know how to do it in a generic way that works everywhere, so, I just called mknod to create the device quickly and I might do something more sophisticated in the next test, when I’ve learned more. :-p

    To insert the ktest module to the kernel:

    # insmod ktest.ko

    after running make. I used dmesg to see if the ktest device was registered, the message was something like:

    [524178.076365] Registered character device. Name: ktest number: 243.

    To test if the module works, I initially used cat but then I’ve written a short program myself to read the output once every time (cat reads from the device continuously):

    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main(void)
    {
    	char buf[512];
    	size_t size;
    	int fd;
    
    	if((fd = open("/dev/ktest", O_RDONLY)) == -1) {
    		fprintf(stderr, "Failed to open device ktest.n");
    		return 1;
    	}
    
    	if((size = read(fd, buf, sizeof buf)) == -1) {
    		fprintf(stderr, "Failed to read from device ktest.n");
    
    		close(fd);
    		return 1;
    	}
    	printf("num char: %dn", (int)size);
    
    	buf[size] = '0';
    	printf("quoting kernel: %s", buf);
    	close(fd);
    	return 0;
    }

    By running it I was able to see the helloworld output. To remove the module I ran:

    # rmmod ktest.ko

    End of story. 🙂

That was the first test. Now I intend to experiment with more complex code.

Test code: https://bitbucket.org/eleni-hikiko/kernel-helloworld-test/overview

Advertisements
A helloworld kernel module

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s