Character Device Driver (for linux)
Little talk.....
This was my 3rd assignment in System programming lab(3rd year undergrad course). The main task of this assignment is to design a character driver which should exist in linux kernel. I make a kernel module which module implements a driver that exposes two character devices to user-space. Lets clarify some topics about this assignment.
Kernel Module
Modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system. For example, one type of module is the device driver, which allows the kernel to access hardware connected to the system. Without modules, we would have to build monolithic kernels and add new functionality directly into the kernel image. Besides having larger kernels, this has the disadvantage of requiring us to rebuild and reboot the kernel every time we want new functionality.
The Driver
Normally a device driver sits between some hardware and the kernel I/O subsystem. Its purpose is to give the kernel a consistent interface to the type of hardware it "drives". This way the kernel can communicate with all hardware of a given type through the same interface, even though the actual hardware differs.
About the driver
![]() |
The figure to the left illustrates how my driver works. Basically it solves the producer-consumer problem.
Here the two processes can be both producers and consumers. This resembles the functionality of a real hardware
device driver, where both the hardware and the kernel can produce and consume data. When a process writes (produces) to character device /dev/sakin-0 the data is stored in bounded buffer 1. If the buffer is full the process has to wait until another process has read from /dev/sakin-1. When a process reads (consumes) from character device /dev/sakin-0 the data is read from bounded buffer 0. If the buffer is empty the process has to wait until another process has written to /dev/sakin-1. Writes and reads to and from /dev/sakin-1 are handled in the same way. |
Click this link to see my code.
Compiling and inserting the module
Makefile for compiling and creating the module sakin_dev.ko:obj-m += sakin_dev.o all: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) cleanscript for loading the module sakin_dev.ko and creating the device file:
#file name sakin_load #!/bin/sh module_name="sakin_dev" device_prefix="sakin-" mode="664" group="root" # invoke insmod # use a pathname, as newer modutils don't look in . by default insmod ./${module_name}.ko # retrieve major number major=$(awk "\$2==\"$module_name\" {print \$1}" /proc/devices) # Remove stale nodes and replace them, then give gid and perms # Usually the script is shorter, it's scull that has several devices in it. rm -f /dev/${device_prefix}[0-1] mknod /dev/${device_prefix}0 c $major 0 mknod /dev/${device_prefix}1 c $major 1 chgrp $group /dev/${device_prefix}[0-1] chmod $mode /dev/${device_prefix}[0-1]script for unloading the module sakin_dev.o and removing the device file:
#file name sakin_unload #!/bin/sh module_name="sakin_dev" device_prefix="sakin-" # invoke rmmod with all arguments we got rmmod ${module_name} # Remove stale nodes rm -f /dev/${device_prefix}[0-1]Now compile the module by running make. If the compilation succeeds there will now be a file called sakin_dev.ko which is the module. Create the necessary devices and insert the module by executing
./sakin_loadTo remove the module and to delete the devices execute:
./sakin_unload
Functions and Macros:
unsigned int imajor(struct inode *inode):
unsigned long copy_to_user (void *to, const void *from, unsigned long count):
Flags that control how memory allocations are performed, from the least restrictive to the most.
The GFP_USER and GFP_KERNEL priorities allow the current process to be put to sleep to satisfy the request.
Semaphore related functions defined in <asm/semaphore.h>
Sleep related functions defined in <linux/wait.h>
DECLARE_WAIT_QUEUE_HEAD(queue):
The defined type for Linux wait queues. A wait_queue_head_t must be explicitly
initialized with either init_waitqueue_head at runtime or DECLARE_WAIT_QUEUE_HEAD at compile time.
Cause the process to sleep on the given queue until the given condition evaluates to a true value.
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait):
void wake_up_interruptible_nr(struct wait_queue **q, int nr);
void wake_up_all(struct wait_queue **q);
void wake_up_interruptible_all(struct wait_queue **q):
Wake processes that are sleeping on the queue q. The _interruptible form wakes
only interruptible processes. Normally, only one exclusive waiter is awakened,
but that behavior can be changed with the _nr or _all forms.
Externel References:
The Linux Kernel Module Programming GuideLinux Device Driver (Ch:2,3,5,6)