You are on page 1of 5

Implementing a Simple Char Device in Linux

Written by Thong D. Nguyen

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
calledmy_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.

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:
http://linuxgazette.net/125/misc/mishra/my_dev.tgz]
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.

my_dev.c:
#include<linux/module.h>
#include<linux/init.h>
#include"my_dev.h"

MODULE_AUTHOR("ranjmis");
MODULE_DESCRIPTION("A simple char device");
static int r_init(void);
static void r_cleanup(void);

module_init(r_init);
module_exit(r_cleanup);

static int r_init(void)


{
printk("<1>hi\n");
if(register_chrdev(222,"my_device",&my_fops)){
printk("<1>failed to register");
}
return 0;
}
static void r_cleanup(void)
{
printk("<1>bye\n");
unregister_chrdev(222,"my_device");
return ;
}

my_dev.h:
/*
* my device header file
*/
#ifndef _MY_DEVICE_H
#define _MY_DEVICE_H

#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <asm/segment.h>
#include <asm/uaccess.h>

char my_data[80]="hi from kernel"; /* our device */

int my_open(struct inode *inode,struct file *filep);


int my_release(struct inode *inode,struct file *filep);
ssize_t my_read(struct file *filep,char *buff,size_t
count,loff_t *offp );
ssize_t my_write(struct file *filep,const char *buff,size_t
count,loff_t *offp );

struct file_operations my_fops={


open: my_open,
read: my_read,
write: my_write,
release:my_release,
};

int my_open(struct inode *inode,struct file *filep)


{
/*MOD_INC_USE_COUNT;*/ /* increments usage count of module
*/
return 0;
}

int my_release(struct inode *inode,struct file *filep)


{
/*MOD_DEC_USE_COUNT;*/ /* decrements usage count of module
*/
return 0;
}
ssize_t my_read(struct file *filep,char *buff,size_t
count,loff_t *offp )
{
/* function to copy kernel space buffer to user space*/
if ( copy_to_user(buff,my_data,strlen(my_data)) != 0 )
printk( "Kernel -> userspace copy failed!\n" );
return strlen(my_data);

}
ssize_t my_write(struct file *filep,const char *buff,size_t
count,loff_t *offp )
{
/* function to copy user space buffer to kernel space*/
if ( copy_from_user(my_data,buff,count) != 0 )
printk( "Userspace -> kernel copy failed!\n" );
return 0;
}
#endif