...making Linux just a little more fun!

<-- prev | next -->

Implementing a Simple Char Device in Linux

By Ranjeet Mishra

Device

For the purpose of this article, let's consider a device to be a virtual represention, within Linux, of hardware that one would like to drive by using a piece of software. In the Linux world, devices are implemented in the form of modules. By using modules, we can provide device functionality that can be accessed from userspace.

A userspace entry point to a device is provided by a file node in the /dev directory. As we know, most of the things in Linux world are represented in the form of files. We can do [ls -l] on any device file, which will report the device type - character or block device, as well as its major number and minor number.

The type of device indicates the way data is written to a device. For a character device, it's done serially, byte by byte, and for a block device (e.g., hard disk) in the form of chunks of bytes - just as the name suggests.

The major number is assigned at the time of registering the device (using some module) and the kernel uses it to differentiate between various devices. The minor number is used by the device driver programmer to access different functions in the same device.

Looking at the number of files in the /dev directory, one might think that a very large number of devices are up and running in the system, but only few might be actually present and running. This can be seen by executing [cat /proc/devices]. (One can then see the major numbers and names of devices that are passed at the time of registering.)

Modules

Every device requires a module. Information about the currently loaded modules can be extracted from the kernel through [cat /proc/modules]. A module is nothing more than an object file that can be linked into a running kernel; to accomplish this, Linux provides the [insmod] utility. As an example, let's say that my module's object file is called my_dev.o; we can link it to the kernel using [insmod my_dev.o]. If insmod is successful we can see our module's entry using [cat /proc/modules], or [lsmod]. We can remove the module using the rmmod utility, which takes the object file name as an argument.

Writing a Module to register a Char device

First of all, we should know the basics of generating a module object file. The module uses kernel space functions and since the whole kernel code is written inside the __KERNEL__ directive we need to define it at time of compiling, or in our source code. We need to define the MODULE directive before anything else because Module functions are defined inside it. In order to link our module with the kernel, the version of the running kernel should match the version which the module is compiled with, or [insmod] will reject the request. This means that we must include the [include] directory present in the Linux source code of the appropriate version. Again, if my module file is called my_dev.c, a sample compiler instruction could be [gcc -D__KERNEL__ -I/usr/src/linux.2.6.7/linux/include -c my_dev.c]. A -D is used to define any directive symbol. Here we need to define __KERNEL__, since without this kernel-specific content won't be available to us.

The two basic functions for module operations are module_init and module_exit. The insmod utility loads the module and calls the function passed to module_init, and rmmod removes the module and calls function passed to module_exit. So inside module_init, we can do whatever we wish using our kernel API. For registering the char device, the kernel provides register_chrdev which takes three arguments, namely: the major number, the char string (which gives a tag name to the device), and the file operations struct address which defines all the stuff we would like to do with our char device. struct file_operations is defined in $(KERNELDIR)/linux/include/fs.h which declares the function pointers for basic operations like open, read, write, release, etc. One needs to implement whatever functions are necessary for the device. Finally, inside the function passed to module_exit, we should free the resources using unregister_chrdev which will be called when we do rmmod.

Below is the code listing where the device is nothing but an 80 byte chunk of memory.

Program Listing

Playing with the char device

Load the device using [insmod my_dev.o]. Look for the entry through /proc/modules and /proc/devices. Create a file node in /dev directory using [mknod /dev/my_device c 222 0]. Look inside the code, we have given the major number as 222. You might think that this number may clash with some other device - well, that's correct, but I have checked whether this number is already occupied by some other device. One could use dynamic allocation of the major number; for that we have to pass 0 as the argument.

Now we can read the data in the device using [cat /dev/my_device] and can write to our device using [echo "something" > /dev/my_device]. We can also write full-fledged userspace code to access our device using standard system calls of open, read, write, close, etc. Sample code is presented below.

-------------------------------------------
/* Sample code to access our char device */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	int fd=0,ret=0;
	char buff[80]="";
	
	fd=open("/dev/my_device",O_RDONLY);
	
	printf("fd :%d\n",fd);
	
	ret=read(fd,buff,10);
	buff[ret]='\0';
	
	printf("buff: %s ;length: %d bytes\n",buff,ret);
	close(fd);
}

-------------------------------------------
Output
fd: 3
buff: hi from kernel ;length: 14 bytes
-------------------------------------------

Conclusion

[ Note: a tarball containing all the code in this article can be downloaded here. ]

In this article I have tried to show how to use the kernel functions to register a character device, and how to invoke it from userspace. There are many issues that have not been touched upon here, such as the concurrency problem where we need to provide a semaphore for the device to do mutual exclusion as more than one process may try to access it. I will try to cover these issues in my future articles.

Talkback: Discuss this article with The Answer Gang


[BIO]

I am from New Delhi, India and am a great Linux fan and love the way Linux gives freedom to control the hardware gizmos. I am using Linux since the start of the new millennium but started digging into kernel sources recently after completing the B-Tech from IIT-Guwahati. It all began with a desire to create modules to control the peripheral devices and since then there is no turning back.

I would like to share my experiences and any interesting thing that comes across me during this Linux journey through Linux Gazette Articles.


Copyright © 2006, Ranjeet Mishra. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 125 of Linux Gazette, April 2006

<-- prev | next -->
Tux