-
Reference:
-
"Linux Device Drivers", Alessandro Rubini, O'Reilly
-
Pointers:
-
http://www.redhat.com:8080/
-
http://www.kernel.org/
-
ftp://ftp.kernel.org/
-
ftp://sunsite.unc.edu/pub/Linux/docs/
-
ftp://tsx-11.mit.edu/pub/linux/docs/
-
http://www.ssc.com/
-
http://www.conecta.it/linux/
-
Driver examples:
-
ftp://ftp.ora.com/pub/examples/linux/drivers/
-
Device drivers provide
mechanisms
, not
policy
.
-
Mechanism
: "What needs to be done by the driver in order to make the hardware available for use by applications and the kernel?"
-
Policy
: "How can the driver be used by applications and the kernel?"
-
This strategy allows
flexibility
.
-
The driver controls the hardware and provides an abstract interface to its capabilities.
-
The driver ideally imposes
no
restrictions (or
policy
) on how the hardware should be used by applications.
-
For example, a floppy controller provides the abstraction that the floppy disk is a continuous array of bytes.
-
Applications determine the policy, e.g.
tar
writes it sequentially and
mkfs
creates a file system and prepares the disk for mounting.
-
Utilities that help applications build policies on top of the driver should be written as
libraries
, independent of the driver.
-
Kernel Parts:
-
Process management
:
-
Kernel is responsible for creating, destroying and scheduling processes.
-
Memory management
:
-
Kernel implements a virtual memory space on top of the limited physical resources.
-
File systems
:
-
Almost everything in Unix can be treated as a file (file abstraction).
-
The kernel builds a structured filesystem on top of unstructured hardware.
-
Device control
:
-
The kernel must have a device driver for every peripheral present on the system.
-
Networking
:
-
Kernel collects, identifies, dispatches and receives data packets to/from network interfaces and user programs.
-
Modules:
-
A method by which you can
expand
the kernel code at run time.
-
A module is made up of
object code
(not stand-alone code) that can be linked (
insmod
) and unlinked (
rmmod
) to a running kernel.
-
This provides a nice way for you to install and test device drivers.
-
Device classification:
-
Most device drivers can be classified into one of
three
categories.
-
Character devices
.
-
Console and parallel ports are examples.
-
Implement a stream abstraction with operations such as
open
,
close
,
read
and
write
system calls.
-
File system nodes such as
/dev/tty1
and
/dev/lp1
are used to access character devices.
-
Differ from regular files in that you usually cannot step backward in a stream.
-
Device classification:
-
Block devices
.
-
A device that can host a filesystem, e.g. disk.
-
Linux allows users to treat block devices as character devices (
/dev/hda1
) with transfers of
any
number of bytes.
-
Block and character devices differ primarily in the way data is managed
internally
by the kernel at the
kernel/driver
interface.
-
The driver interface
to the kernel
is both a character and block-oriented interface.
-
Network interfaces
.
-
In charge of sending and receiving data packets.
-
Network interfaces are not stream-oriented and therefore, are not easily mapped to a node in the filesystem, such as
/dev/tty1
.
-
Communication between the kernel and network driver is completely different from that used with char and block drivers.
-
Hello World:
#define MODULE
#include <linux/module.h>
int init_module(void)
{
printk("<1>Hello, world\n"); /* <1> is priority. */
return 0;
}
int cleanup_module(void)
{
printk("<1>Goodbye cruel world\n");
}
-
Hello World:
-
You must be superuser to install and remove modules.
-
To compile and run this code saved in a file named
hw.c
, use:
-
root#
gcc -c hw.c
-
root#
insmod hw.o
-
root#
rmmod hw
-
On my version of Linux (Redhat distribution 6.1, kernel version 2.2.12), the printk messages are saved at the bottom of the log file:
-
Module versus applications:
-
Unlike an application, a module registers itself (so it can be invoked by the kernel when needed).
-
init-module()
and
cleanup_module()
are module entry points.
-
They take care of initialization and cleanup.
-
Modules versus applications:
-
insmod
links the program to the kernel.
-
The link step for an application gives the program access to library functions, such as those defined in
libc
.
-
Note that modules do NOT have access to
libc
functions, only those exported by the kernel.
-
For example,
printk
is the kernel version of
printf
(except there's no floating point support).
-
Modules can NOT include standard header files in
/usr/include
except the kernel headers in
/usr/include/linux
and
/usr/include/asm
.
-
Kernel code in these headers are protected with
#ifdef __KERNEL__
since applications can include these as well.
-
Note these directories are symbolic links to
/usr/src/linux/include
.
-
Modules versus applications:
-
Name space pollution
:
-
Name space pollution results from the presence of many (badly named) functions and global variables that are difficult to track.
-
Remember, a module is added to a really big application, the kernel.
-
Declare most symbols as
static
and use a
well-defined prefix
for global symbols.
-
You can also declare a
symbol table
to avoid this (discussed later).
-
Fault handling
:
-
Application segmentation faults are harmless, since you can print out the core dumps or use a debugger :)
-
A kernel fault is
fatal
to the current process and sometimes the whole system !
-
Modules versus applications:
-
Applications run in
user space
while modules run in
kernel space
.
-
In user mode, the processor inhibits direct access to hardware and unauthorized access to memory.
-
Applications switch to kernel mode through a limited number of gates, implemented as
system calls
and
hardware interrupts
.
-
System calls
allow the kernel to access a process's data (the process that called it.)
-
Hardware interrupts
are asynchronous and are not associated with a particular process.
-
Drivers typically provide code for both of these tasks.
-
Concurrency in the kernel:
-
Drivers should support
concurrency
, e.g., the ability to support read calls by two different processes.
-
This requires the driver (and kernel) to maintain
distinct data structures
for each process, since the same code is executed.
-
Driver code should also be
reentrant
, and must be if it calls
schedule
or
sleep_on
(which, in turn, calls
schedule
).
-
Reentrant
code is code that does not keep status in global variables, but rather in local (stack allocated) variables.
-
This allows the process executing to suspend (e.g., wait for keyboard data) and other processes to execute the same code.
-
Although it is true that context switches cannot occur due to timeouts while kernel code is executing,
unanticipated
suspensions can occur, e.g., a page fault caused by reference to user space.
-
Concurrency in the kernel:
-
There are several approaches to keeping data separate.
-
Kernel
global
variables is one approach:
-
current
is a pointer to
struct task_struct
(
linux/sched.h
), which refers to the currently executing user process.
-
A module can refer to this global variable as in:
printk("The process is \"%s\" (pid %i)\n",
current->comm, current->pid);
-
This wont link, of course, without
#include <linux/sched.h>
.
-
Makefile:
-
Define statements:
#define __KERNEL__
-
This allows header declarations included between
#ifdef __KERNEL__
to be compiled.
#define MODULE
-
Define this before the include
</linux/module.h>
.
-
Compiler flags:
-
-O
must be specified, since many function are declared as
inline
in the headers, and gcc doesn't expand them unless optimization is turned on.
-
Don't use
-O2
!
-
-g
can be used for debugging.
-
-Wall
for warnings is suggested.
-
Makefile:
-
Multiple source files:
-
If you choose to split your code across multiple source files, then
ld -r
is required to link them.
-
A makefile for a simple driver called
skull
which uses two source files.
INCLUDEDIR = /usr/include
CFLAGS = -D__KERNEL__ -DMODULE -O -Wall -I$(INCLUDEDIR)
#Extract the version number from the headers.
VER = $(shell awk -F' '/REL/ {print $$2}' \
$(INCLUDEDIR)/linux/version.h)
OBJS = skull.o
all: $(OBJS)
skull.o: skull_init.o skull_clean.o
$(LD) -r $^ -o $@
install:
install -d /lib/modules/$(VER)/misc /lib/modules/misc
install -c skull.o /lib/modules/$(VER)/misc
install -c skull.o /lib/modules/misc
-
Loading:
-
insmod
is similar to
ld
.
-
It links
unresolved
symbols in the module to the symbol table of the running kernel.
-
insmod
also differs from
ld
.
-
It doesn't modify the disk image, only the
in-memory image
.
-
insmod
takes parameters that allows
on-the-fly
configuration of the driver, which is preferred over compile time configuration.
-
Version dependency:
-
You will likely need to recompile the module for each version of the kernel that it is linked to.
-
Each module defines a symbol called
kernel_version
.
-
insmod
matches this against the version number of the current kernel.
-
Version dependency:
-
Newer kernels define it for you in
<linux/module.h>
-
However, portable code that can be compiled and run on any kernel version uses the following:
#define __NO_VERSION__ /* Dont define kernel_version
in module.h */
#include <linux/module.h>
#include <linux/version.h>
char kernel_version [] = UTS_RELEASE;
-
This prevents the automatic declaration of
kernel_version
.
-
This is particularly useful for multiple source file includes of
<linux/module.h>
, which
ld -r
will complain about.
-
insmod
rules for finding the module are look in the current directory, then a version dependent directory (
/lib/modules/$(VER)/misc
), followed by
/lib/modules/misc
.