Sunteți pe pagina 1din 8

We will be dealing with

Introduction of device drivers

Creating a device file

The driver code

Compiling the driver

Loading and unloading the driver

Testing the driver

We would be writing a Linux device driver for a hypothetical character device which reverses
any string that is given to it. i.e. If we write any string to the device file represented by the
device and then read that file, we get the string written earlier but reversed (for eg., myDev
being our device, echo hello >/dev/myDev ; cat /dev/ myDev would print olleh).
We will be simulating the functionality of the device in the driver itself (and this is precisely
what is done in emulation tools like Daemon Tools, Alcohol etc).
We have summarized how to write a device driver, the significance of a device file and its role in
the interaction between a user program and device driver.

The devices in UNIX fall in two categories- Character devices and Block devices. Character
devices can be compared to normal files in that we can read/write arbitrary bytes at a time
(although for most part, seeking is not supported).They work with a stream of bytes. Block
devices, on the other hand, operate on blocks of data, not arbitrary bytes. Usual block size is 512
bytes or larger powers of two. However, block devices can be accessed the same was as
character devices, the driver does the block management. (Networking devices do not belong to
these categories, the interface provided by these drivers in entirely different from that of
char/block devices)
The beauty of UNIX is that devices are represented as files. Both character devices and block
devices are represented by respective files in the /dev directory. This means that you can read
and write into the device by manipulating those file using standard system calls like open, read,
write, close etc.
For eg, you could directly write or read the hard disk by accessing /dev/sd* file a dangerous act
unless you know what you are doing (for those interested, try hexdump C /dev/sda n 512
what you see then is the boot sector of your hard disk !). As another example, you could
directly see the contents of the RAM by reading /dev/mem.
Every device file represented in this manner is associated with the device driver of that device
which is actually responsible for interacting with the device on behalf of the user request. So
when you access a device file, the request is forwarded to the respective device driver which
does the processing and returns the result.
For instance, you might be knowing about the files /dev/zero (an infinite source of zeroes),
/dev/null (a data black hole), /dev/random (a source of random numbers) etc. When you actually
read these files, what happens is that a particular function in the device driver registered for the
file is invoked which returns the respective data.
In our example, we will be developing a character device represented by the device file
/dev/myDev. The mechanisms for creating this file will be explained later.

Under the hood


Now how does Linux know which driver is associated with which file? For that, each device and
its device file has associated with it, a unique Major number and a Minor number. No two
devices have the same major number. When a device file is opened, Linux examines its major
number and forwards the call to the driver registered for that device. Subsequent calls for
read/write/close too are processed by the same driver. As far as kernel is concerned, only major
number is important. Minor number is used to identify the specific device instance if the driver
controls more than one device of a type.
To know the major, minor number of devices, use the ls l command as shown below.

ls -l
The starting c means its a character device, 1 is the major number and 8 is the minor number.
A Linux driver is a Linux module which can be loaded and linked to the kernel at runtime. The
driver operates in kernel space and becomes part of the kernel once loaded, the kernel being
monolithic. It can then access the symbols exported by the kernel.
When the device driver module is loaded, the driver first registers itself as a driver for a
particular device specifying a particular Major number.
It uses the call register_chrdev function for registration. The call takes the Major number,
Minor number, device name and an address of a structure of the type file_operations(discussed
later) as argument. In our example, we will be using a major number of 89 . The choice of major
number is arbitrary but it has to be unique on the system.
The syntax of register_chrdev is
int register_chrdev(unsigned int major,const char *name,struct file_operations *fops)
Driver is unregistered by calling the unregister_chrdev function.
Since device driver is a kernel module, it should implement init_module and cleanup_module
functions. The register_chrdev call is done in the init_module function and unregister_chrdev
call is done in the cleanup_module function.
The register_chrdev call returns a non-negative number on success. If we specify the Major
number as 0, the kernel returns a Major number unique at that instant which can be used to create
a device file.
A device file can be created either before the driver is loaded if we know the major and minor
number beforehand or it can be created later after letting the driver specify a major number for
us.
Apart from those, the driver must also define certain callback functions that would be invoked
when file operations are done on the device file. Ie. It must define functions that would be
invoked by the kernel when a process uses open, read, write, close system calls on the file. Every
driver must implement functions for processing these requests.
When register_chrdev call is done, the fourth argument is a structure that contains the addresses
of these callback functions, callbacks for open, read, write, close system calls. The structure is of
the type file_operations and has 4 main fields that should be set read,write,open and
release. Each field must be assigned an address of a function that would be called when open,
read,write , close system calls are called respectively. For eg

file_operations structure initialisation


It is important to note that all these callback functions have a predefined prototype although the
name can be any.

Creating a device file


A device file is a special file. It cant just be created using cat or gedit or shell redirection for that
matter. The shell command mknod is usually used to create device file. The syntax of mknod is
mknod path type major minor
path:-path where the file to be created. Its not necessary that the device file needs to be created
in the /dev directory. Its a mere convention. A device file can be created just about anywhere.
type: -c or b . Whether the device being represented is a character device or a block device. In
our example, we will be simulating a character device and hence we choose c.
major, minor:- the major and minor number of the device.
Heres how

mknod command
chmod, though not necessary is done because, if not done, only processes will root permission
can read or write to our device file.

The driver code


Given below is the code of the device driver

Device Driver Code


For debugging, I have included some printk messages in the code. To see those messages while
the driver is in action, do dmesg|tail

Compiling the driver


A Linux module cannot just be compiled the way we compile normal C files. cc filename.c
wont work. Compiling a Linux module is a separate process by its own. We use the help of
kernel Makefile for compilation. The makefile we will be using is.

makefile for module compilation


Here, we are making use of the kbuild mechanism used for compiling the kernel.
The result of compilation is a ko file (kernel object) which can then be loaded dynamically when
required.

Loading and Unloading the Driver


Once the compilation is complete, we can use either insmod or modprobe command ( insmod
myDev.ko or modprobe myDev.ko, of course assuming the current directory contains the
compiled module). The difference between insmod and modprobe is that modprobe
automatically reads our module to find any dependencies on other modules and loads them
before loading our module (these modules must be present in the standard path though!). insmod
lacks this feature.
To test if the driver has been loaded successfully, do cat /proc/modules and cat
/proc/devices. We should see our module name in the first case and device name in the second.

cat /proc/modules

cat /proc/modules
To unload the driver, use rmmod command. (rmmod myDev.ko)

Testing the driver


To test the driver, we try writing something to the device file and then reading it. For example,

Testing the driver

See the output. (The reason for the ugly output is because echo automatically writes a newline
character to the end of string. When the driver reverses the string, the newline is shifted to the
front of the string and there is no newline at the end. Hence the result being ugly)
To see how this can be done from our program, I wrote a demo program given below

Interacting with the driver


Compile it normally(or run make test) and run ./test some_string and see the output.

Testing the driver


Note: You need to be root to compile the module, load the module and unload the module.

S-ar putea să vă placă și