9:04 PM
Sayef Azad Sakin
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) clean
script 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_load
To remove the module and to delete the devices execute:
./sakin_unload
Functions and Macros:
int sakin_init_module( void ):
init_function, Called when module is loaded into the kernel.
void sakin_cleanup_module( void ):
cleanup_function, Called when module is unloaded from the kernel.
module_init(init_function):
Macros that designate a modules initialization, defined in <linux/types.h>.
module_exit(cleanup_function):
Macros that designate a modules cleanup functions, defined in <linux/types.h>.
static int sakin_open( struct inode*, struct file* ):
Called when a process tries to open the device file.
static int sakin_release( struct inode*, struct file* ):
Called when a process closes the device file.
static ssize_t sakin_read( struct file*, char*, size_t, loff_t* ):
Called when a process, which already opened the sakin_dev file, attempts to read from it.
static int sakin_getwritespace(struct sakin_dev *dev, struct file *filp, const int c_minor):
Wait for space for writing. Caller must hold device semaphore. On error the semaphore will be released before returning.
static int spacefree(struct sakin_dev *dev, const int c_minor):
Return how much space is free.
static ssize_t sakin_write( struct file*, const char*, size_t, loff_t* ):
Called when a process, which already opened the sakin_dev file, attempts to write to it.
dev_t MKDEV(unsigned int major, unsigned int minor):
Macro that builds a dev_t data item from the major and minor numbers. Declared in <linux/types.h>.
int register_chrdev_region(dev_t first, unsigned int count, char *name):
Allocating the device number, which is declared in <linux/fs.h>
void unregister_chrdev_region(dev_t first, unsigned int count):
Freeing the device number, which is declared in <linux/fs.h>
container_of(pointer, type, field): A convenience macro that may be used to obtain a pointer to a structure from a
pointer to some other structure contained within it.
void *kmalloc(size_t size, int flags):
For allcating memory, which is declared in <linux/slab.h>
void kfree(void *ptr);
For freeing memory, which is declared in <linux/slab.h>
static void sakin_setup_cdev(struct sakin_dev *dev, int index):
Local function to setup the char device.
void cdev_init(struct cdev *cdev, struct file_operations *fops):
Initializing and setting up the char device. Declared in <linux/cdev.h>
int cdev_add(struct cdev *dev, dev_t num, unsigned int count):
Tell the kernel about the char device. Declared in <linux/cdev.h>
void cdev_del(struct cdev *dev):
To remove a char device from the system. Declared in <linux/cdev.h>
unsigned int iminor(struct inode *inode),
unsigned int imajor(struct inode *inode):
used to obtain the major and minor number from an inode.
unsigned long copy_from_user (void *to, const void *from, unsigned long count),
unsigned long copy_to_user (void *to, const void *from, unsigned long count):
Copy data between user space and kernel space. Declared in <asm/uaccess.h>
void schedule(void):
Selects a runnable process from the run queue. The chosen process can be current or a different one.
bool signal_pending(current):
Tells whether current were awakened by a signal.
GFP_USER & GFP_KERNEL:
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>
void sema_init(struct semaphore *sem, int val): Semaphore initialization.
void down(struct semaphore *sem):
down puts the calling process into an uninterruptible sleep if need be.
int down_interruptible(struct semaphore *sem):
down_interruptible, instead, can be interrupted by a signal.
int down_trylock(struct semaphore *sem):
down_trylock does not sleep; instead, it returns immediately if the semaphore is unavailable.
void up(struct semaphore *sem):
Code that locks a semaphore must eventually unlock it with up.
Sleep related functions defined in <linux/wait.h>
void init_waitqueue_head(wait_queue_head_t *queue);
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.
void wait_event(wait_queue_head_t q, int condition); //uninterruptible wait
int wait_event_interruptible(wait_queue_head_t q, int condition); //interruptible wait
int wait_event_timeout(wait_queue_head_t q, int condition, int time); //uninterruptible timeout wait
int wait_event_interruptible_timeout(wait_queue_head_t q, int condition, int time): //interruptible timeout wait
Cause the process to sleep on the given queue until the given condition evaluates to a true value.
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state),
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait):
Helper functions that can be used to code a manual sleep.
void wake_up(struct wait_queue **q); //uninterruptible
void wake_up_interruptible(struct wait_queue **q); //interruptible
void wake_up_nr(struct wait_queue **q, int nr);
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 Guide
Linux Device Driver (Ch:2,3,5,6)