Sei sulla pagina 1di 34

ACKNOWLEDGEMENT

I take this opportunity to express my sincere thanks and deep gratitude to all
those people who extended their whole hearted co-operation and have helped
me in completing the Training and project successfully.

First of all, I would to thank Mr.Sagar gupta for creating opportunities to


undertake me in the esteemed organization. I express my sincere gratitude to
Mr.Anchul kundra, for giving me an opportunity to work on embedded
systems and linux device driver.

Special thanks to Mrs.Neha mam for all the help and guidance extended to
me by her in every stage during my training. Their inspiring suggestions and
timely guidance enabled me to perceive the various aspects of the project in a
new light.

I would also like to thank all senior members of CMC for having guided and
encouraged me throughout the duration of the project. In all I found a
congenial work environment in and this completion of the project and training
will mark a new beginning for me in the coming days.

I convey my sincere thanks to Mrs. Neha mam, HOD(ECE TRAINING


Department),Assistant trainer (ECE Department) and Mr.Anchul
kundra (Lecturer) for their help in my project.

MD SAKIF UDDIN
CMC Company Profile

Your Trusted IT Partner

CMC Limited is a leading IT solutions company and a subsidiary of Tata Consultancy Services Limited (TCS Ltd), one of
the world's leading information technology consulting, services and business process outsourcing organisations. We are
a part of the Tata group, India's best-known business conglomerate.

With 18 offices, 150 service locations, 520 non-resident locations and over 5,551 employees worldwide, we provide a
wide spectrum of unique Information Technology solutions and services to a clientele of premier organisations in the
government and private sectors.

CMC Americas, our subsidiary, services clients in the US, while our branch offices in the UK and Dubai market our
products and services in Europe, Africa and the Middle East.

Large and complex project management capabilities

Since its incorporation in 1975, CMC has an enviable record of successfully building IT solutions for massive and
complex infrastructure and market projects.

Take, for instance, just three of the many major projects undertaken by us:

End-to-end solutions capability

We manage turnkey projects, and have built, managed and supported our customers' IT systems across the value chain
— infrastructure, applications and business processes. That is because our capabilities span the entire IT spectrum: IT
architecture; hardware; software (including systems and application software, development or implementation,
maintenance, and frameworks); network consulting; and IT-enabled processing services.

Extensive domain expertise and technological competencies

We have proven expertise in a wide array of applications, including real-time systems, online systems, embedded
systems, process control, transaction processing, image processing, data communications, networking, parallel
architectures, e-commerce technologies and e-governance applications.

Our competitive edge comes from combining our technology competencies with our understanding of verticals, straddling
a range of sectors from banking and insurance, power, mining and defence to education. Our high quality, high value IT
solutions have helped reshape businesses and delivered measurable results to our customers.
Sizable resource pool with diverse skill sets

We achieve this with the help of our resource pool of engineers trained in diverse technologies, with vast domain
knowledge and varied skill sets. Of our 5,551 employees, over 14 per cent have more than ten years of work
experience. We have an impeccable educational profile; as many as 26 per cent of our employees have postgraduate
degrees.

We also conduct significant research into emerging technologies and competence areas at our state-of-the-art, ISO
9001-certified R&D centre in Hyderabad, India. That is what gives us an edge in complex, high technology projects.

The Systems Integration Business Unit's all four regions are at Level 5 (optimising level) of the SEI's Capability Maturity
Model (software).

Strong customer relationships

CMC's customer orientation and service culture lead to enduring bonds with clients. Our distinct value proposition and
service culture, coupled with our track record of successful service delivery, are reflected in our long-standing customer
relationships with dominant players in key infrastructure, services and government sectors.

Our customers include some of the biggest organisations in India: Reserve Bank of India, Indian Railways, Indian Oil
Corporation Limited, Bharat Petroleum Corporation Limited, Oil and Natural Gas Corporation Limited, United Western
Bank, Bank of India and Bank of Baroda.

The trust reposed in us by our clients is reflected in our growing revenues from operations such as post-implementation
maintenance and support activities.

Growth strategies

CMC's growth strategy focuses on capitalising on its unique skill sets and leveraging the synergies with TCS and other
Tata group companies, for growth in revenue and profitability of our operations.

We will continue to focus on consolidating our dominant position in India, while expanding our reach globally.
FRONT PAGE OF CMC
INTODUCTION

EMBEDDED SYSTEM

An embedded system is a computer system designed to perform one or a few dedicated functions, [1]
[2]
 often with real-time computing constraints. It is embedded as part of a complete device often including
hardware and mechanical parts. In contrast, a general-purpose computer, such as a personal computer,
is designed to be flexible and to meet a wide range of an end-user's needs. Embedded systems control
many of the common devices in use today.[3]

Embedded systems are controlled by a main processing core that is typically either a microcontroller or
a digital signal processor(DSP)[4]

Since the embedded system is dedicated to specific tasks, design engineers can optimize it, reducing the
size and cost of the product, or increasing the reliability and performance. Some embedded systems are
mass-produced, benefiting from economies of scale.

Physically, embedded systems range from portable devices such as digital watches and MP3 players, to
large stationary installations like traffic lights, factory controllers, or the systems controlling nuclear
power plants. Complexity varies from low, with a single microcontroller chip, to very high with multiple
units, peripherals and networks mounted inside a large chassis or enclosure.

In general, "embedded system" is not an exactly defined term, as many systems have some element of
programmability. For example, Handheld computers share some elements with embedded systems —
such as the operating systems and microprocessors which power them — but are not truly embedded
systems, because they allow different applications to be loaded and peripherals to be connected.
LINUX-DEVICE-DRIVER
Basically today we work out with the hardware by connecting it through the PC bcoz things are going to
be dynamically now a days . we have to change the things like updating , adding the new features in the
hardware We prefer it through the use of OPERATING SYSTEM bcoz different things are previously
present in the OS we dont have to start everything from the scrap but if we use or interface the hardware
device thru the micro controller or processor it needs a interfacing problems ,we have to add the features
of each and every component

OS -:An operating system (OS) is software, consisting of programs and data, that runs on computers
and manages the computer hardware and provides common services for efficient execution of various
application software.For hardware functions such as input and output and memory allocation, the
operating system acts as an intermediary between application programs and the computer hardware,[1]
[2] although the application code is usually executed directly by the hardware, but will frequently call the
OS or be interupted by it. Operating systems are found on almost any device that contains a computer 庸
rom cellular phones and video game consoles to supercomputers and web servers.
Components of LINUX OS.
1.KERNEL
2.SHELL
They both work together to provide the perfect utilisation of the resources like memory ,
harddidk,RAM,processor,ports

KERNEL:-
The kernel is a program that constitutes the central core of a computer operating system. It has
complete control over everything that occurs in the system.

A kernel can be contrasted with a shell (such as bash, csh or ksh in Unix-like operating systems), which
is the outermost part of an operating system and a program that interacts with user commands. The
kernel itself does not interact directly with the user, but rather interacts with the shell and other
programs as well as with the hardware devices on the system, including the processor (also called the
central processing unit or CPU), memory anddiskdrives.

The kernel is the first part of the operating system to load into memory during booting (i.e., system
startup), and it remains there for the entire duration of the computer session because its services are
required continuously. Thus it is important for it to be as small as possible while still providing all the
essential services needed by the other parts of the operating system and by the various application
programs.

Because of its critical nature, the kernel code is usually loaded into a protected area of memory, which
prevents it from being overwritten by other, less frequently used parts of the operating system or by
application programs. The kernel performs its tasks, such as executing processes and handling interrupts,
in kernel space, whereas everything a user normally does, such as writing text in a text editor or running
programs in a GUI (graphical user interface), is done in user space. This separation is made in order to
prevent user data and kernel data from interfering with each other and thereby diminishing performance
or causing the system to become unstable (and possibly crashing).

When a computer crashes, it actually means the kernel has crashed. If only a single program has crashed
but the rest of the system remains in operation, then the kernel itself has not crashed. A crash is the
situation in which a program, either a user application or a part of the operating system, stops
performing its expected function(s) and responding to other parts of the system. The program might
appear to the user to freeze. If such program is a critical to the operation of the kernel, the entire
computer could stall or shut down.

The kernel provides basic services for all other parts of the operating system, typically including memory
management, process management, file management and I/O (input/output) management (i.e.,
accessing the peripheral devices). These services are requested by other parts of the operating system or
by application programs through a specified set of program interfaces referred to as system calls.

Process management, possibly the most obvious aspect of a kernel to the user, is the part of the kernel
that ensures that each process obtains its turn to run on the processor and that the individual processes
do not interfere with each other by writing to their areas of memory. A process, also referred to as a
task, can be defined as an executing (i.e., running) instance of a program.

The contents of a kernel vary considerably according to the operating system, but they typically include
(1) a scheduler, which determines how the various processes share the kernel's processing time
(including in what order), (2) a supervisor, which grants use of the computer to each process when it is
scheduled, (3) an interrupt handler, which handles all requests from the various hardware devices (such
as disk drives and the keyboard) that compete for the kernel's services and (4) a memory manager,
which allocates the system's address spaces (i.e., locations in memory) among all users of the kernel's
services.

The kernel should not be confused with the BIOS (Basic Input/Output System). The BIOS is an
independent program stored in a chip on the motherboard (the main circuit board of a computer) that is
used during the booting process for such tasks as initializing the hardware and loading the kernel into
memory. Whereas the BIOS always remains in the computer and is specific to its particular hardware, the
kernel can be easily replaced or upgraded by changing or upgrading the operating system or, in the case
of Linux, by adding a newer kernel or modifying an existing kernel.

Most kernels have been developed for a specific operating system, and there is usually only one version
available for each operating system. For example, the Microsoft Windows 2000 kernel is the only kernel
for Microsoft Windows 2000 and the Microsoft Windows 98 kernel is the only kernel for Microsoft Windows
98. Linux is far more flexible in that there are numerous versions of the Linux kernel, and each of these
can be modified in innumerable ways by an informed user.

A few kernels have been designed with the goal of being suitable for use with any operating system. The
best known of these is the Mach kernel, which was developed at Carnegie-Mellon University and is used
in the Macintosh OS X operating system.

It is not necessary for a computer to have a kernel in order for it to be usable, the reason being that it is
not necessary for it to have an operating system. That is, it is possible to load and run programs directly
on bare metal machines (i.e., computers without any operating system installed), although this is usually
not very practical.

In fact, the first generations of computers used bare metal operation. However, it was eventually realized
that convenience and efficiency could be increased by retaining small utility programs, such as program
loaders and debuggers, in memory between applications. These programs gradually evolved into
operating system kernels.

The term kernel is frequently used in books and discussions about Linux, whereas it is used less often
when discussing some other operating systems, such as the Microsoft Windows systems. The reasons are
that the kernel is highly configurable in the case of Linux and users are encouraged to learn about and
modify it and to download and install updated versions. With the Microsoft Windows operating systems, in
contrast, there is relatively little point in discussing kernels because they cannot be modified or replaced.

Categories of Kernels

Kernels can be classified into four broad categories: monolithic kernels, microkernels, hybrid kernels and
exokernels. Each has its own advocates and detractors.

Monolithic kernels, which have traditionally been used by Unix-like operating systems, contain all the
operating system core functions and the device drivers (small programs that allow the operating system
to interact with hardware devices, such as disk drives, video cards and printers). Modern monolithic
kernels, such as those of Linux and FreeBSD, both of which fall into the category of Unix-like operating
systems, feature the ability to load modules at runtime, thereby allowing easy extension of the kernel's
capabilities as required, while helping to minimize the amount of code running in kernel space.

A microkernel usually provides only minimal services, such as defining memory address spaces,
interprocess communication (IPC) and process management. All other functions, such as hardware
management, are implemented as processes running independently of the kernel. Examples of
microkernel operating systems are AIX, BeOS, Hurd, Mach, Mac OS X, MINIX and QNX.

Hybrid kernels are similar to microkernels, except that they include additional code in kernel space so
that such code can run more swiftly than it would were it in user space. These kernels represent a
compromise that was implemented by some developers before it was demonstrated that pure
microkernels can provide high performance. Hybrid kernels should not be confused with monolithic
kernels that can load modules after booting (such as Linux).

Most modern operating systems use hybrid kernels, including Microsoft Windows NT, 2000 and XP.
DragonFly BSD, a recent fork (i.e., variant) of FreeBSD, is the first non-Mach based BSD operating
system to employ a hybrid kernel architecture.

Exokernels are a still experimental approach to operating system design. They differ from the other types
of kernels in that their functionality is limited to the protection and multiplexing of the raw hardware, and
they provide no hardware abstractions on top of which applications can be constructed. This separation of
hardware protection from hardware management enables application developers to determine how to
make the most efficient use of the available hardware for each specific program.

Exokernels in themselves they are extremely small. However, they are accompanied by library operating
systems, which provide application developers with the conventional functionalities of a complete
operating system. A major advantage of exokernel-based systems is that they can incorporate multiple
library operating systems, each exporting a different API (application programming interface), such as
one for Linux and one for Microsoft Windows, thus making it possible to simultaneously run both Linux
and Windows applications.

Virtual-device-drivers
Virtual device drivers represent a particular variant of device drivers. They are used to emulate a
hardware device, particularly in virtualization environments, for example when a DOS program is run on
a Microsoft Windows computer or when a guest operating system is run on, for example, a Xen host.
Instead of enabling the guest operating system to dialog with hardware, virtual device drivers take the
opposite role and emulate a piece of hardware, so that the guest operating system and its drivers
running inside a virtual machine can have the illusion of accessing real hardware. Attempts by the guest
operating system to access the hardware are routed to the virtual device driver in the host operating
system as e.g. function calls. The virtual device driver can also send simulated processor-level events like
interrupts into the virtual machine.

Virtual devices may also operate in a non-virtualized environment. For example a virtual network adapter
is used with a virtual private network, while a virtual disk device is used with iSCSI.

Types of device drivers


1 char  i.e keyboard driver , mouse driver
2 block  hard disk , usb
3 network  network adapter

These are the basic category used

Device files -:

A device file is a special type of file. Rather than pointing to an inode which points to some data blocks, a
device file points to an inode that contains some associated information: a major device number (which
defines the type of device it is connected to), and a minor device number (which identifies a particular
device of that type). When you carry out a file operation on a device file, the system uses the major
number to determine which device driver to use to read data from or write data to the device. (The minor
number is used internally by the device driver.)

Device files are created with \ mknod(1M) and cannot be manipulated like ordinary files, although you
can rename them or create links to them.

Device files are typically kept in the directory /dev. They include identifiers that can be used to read from
and write to kernel memory (/dev/kmem), hard disk drives (in “raw'' or “block'' mode, for example
/dev/rdsk/0s0 raw access to drive unit 0), and all the terminals, floppy disk drives, tape drives, and other
components of the computer. Block devices write through the cache, providing fast, high level access.
Raw device files bypass the buffer cache but are more flexible. Raw devices are therefore sometimes
used by special applications like databases, which maintain their own high performance cache for the
hardware they use for data storage.

This has some useful applications. You can send messages to a terminal by redirecting the output of a
cat(1) command to that terminal's device file. Alternatively, you can mount a filesystem on a
subdirectory of your root filesystem, transparently adding another disk drive to the system. All you need
do is create an empty subdirectory, then issue the mount command with the device file that refers to the
additional filesystem as one of the parameters. The new filesystem is then invisibly attached to the root
filesystem at the mount point you created.

Links between files are simply filenames that share the same inode. Inodes are only unique within a
given filesystem. It is therefore impossible for a normal link to cross a filesystem boundary. However, a
symbolic link can be used instead. A symbolic link is created using the -s flag to ln(1) instead of pointing
to the inode of the file, the symbolic link points to a short file containing a reference to the filesystem and
inode of the linked file.

While device files may seem somewhat obscure, they are one of the most important features of the
operating system because they allow you to extend it. Your system administrator can add components to
the computer, then create a device file through which they can be accessed. This is one of the ways in
which the UNIX system is uniquely scalable.

Modules -:
What exactly is a 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 .

loading and unloading a driver


● insmod some_object_file.o
– init_module() is called

● rmmod some_object_file

● notice there is no .o
– cleanup_module is then called
– remember to clean everything up

● or else your kernel will panic when it tries to access your stuff that has been removed

Kernel-mode vs user-mode

Device drivers, particularly on modern Windows platforms, can run in kernel-mode (Ring 0) or in user-mode (Ring 3).
[3]
The primary benefit of running a driver in user mode is improved stability, since a poorly written user mode
device driver cannot crash the system by overwriting kernel memory.[4] On the other hand, user/kernel-mode
transitions usually impose a considerable performance overhead, thereby prohibiting user mode-drivers for low
latency and high throughput requirements.

Kernel space can be accessed by user module only through the use of system calls. End user programs like the
UNIX shell or other GUI based applications are part of the user space. These applications interact with
hardware through kernel supported functions.

Pre-requisites
In order to develop Linux device drivers, it is necessary to have an understanding of the
following:
C programming. Some in-depth knowledge of C programming is needed, like pointer usage,
bit
manipulating functions, etc.
·
Microprocessor programming. It is necessary to know how microcomputers work internally:
memory addressing, interrupts, etc. All of these concepts should be familiar to an assembler
programmer.
·
There are several different devices in Linux. For simplicity, this brief tutorial will only cover type
char
devices loaded as modules. Kernel 2.6.x will be used (in particular, kernel 2.6.8 under Debian
Sarge, which is
now Debian Stable).

User space and kernel space


When you write device drivers, it’s important to make the distinction between “user space” and
“kernel
space”.
Kernel space. Linux (which is a kernel) manages the machine’s hardware in a simple and
efficient
manner, offering the user a simple and uniform programming interface. In the same way, the
kernel,
and in particular its device drivers, form a bridge or interface between the end-
user/programmer and
the hardware. Any subroutines or functions forming part of the kernel (modules and device
drivers,
for example) are considered to be part of kernel space.
·
User space. End-user programs, like the UNIX shell or other GUI based applications
(kpresenter for example), are part of the user space. Obviously, these applications need to
interact
with the system’s hardware . However, they don’t do so directly, but through the kernel
supported
functions.
·
All of this is shown in figure 1.
Figure 1: User space where applications reside, and kernel space where modules or device
drivers reside

Interfacing functions between user space and


kernel space
The kernel offers several subroutines or functions in user space, which allow the end-user
application
programmer to interact with the hardware. Usually, in UNIX or Linux systems, this dialogue is
performed
through functions or subroutines in order to read and write files. The reason for this is that in
Unix devices are
seen, from the point of view of the user, as files.
On the other hand, in kernel space Linux also offers several functions or subroutines to perform
the low level
interactions directly with the hardware, and allow the transfer of information from kernel to
user space.
Usually, for each function in user space (allowing the use of devices or files), there exists an
equivalent in
kernel space (allowing the transfer of information from the kernel to the user and vice-versa).
This is shown in
Table 1, which is, at this point, empty. It will be filled when the different device drivers concepts
are
introduced.
Events User functions Kernel functions
Load module
Open device
Read device
Write device
Close device
Remove module
Table 1. Device driver events and their associated interfacing functions in kernel
space and user space.

Interfacing functions between kernel space


and the
hardware device
There are also functions in kernel space which control the device or exchange information
between the kerneland the hardware. Table 2 illustrates these concepts. This table will also be
filled as the concepts are
introduced.

Interfacing functions between kernel space and the hardware device Events Kernel functions
Read data
Write data
Table 2. Device driver events and their associated functions between kernel space and
the hardware
device.

The first driver: loading and removing the


driver in
user space
I’ll now show you how to develop your first Linux device driver, which will be introduced in the
kernel as a
module.
For this purpose I’ll write the following program in a file named nothing.c
<nothing.c> =

#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");
Since the release of kernel version 2.6.x, compiling modules has become slightly more
complicated. First, youneed to have a complete, compiled kernel source-code-tree. If you have
a Debian Sarge system, you canfollow the steps in Appendix B (towards the end of this article).
In the following, I’ll assume that a kernelversion 2.6.8 is being used.

Next, you need to generate a makefile. The makefile for this example, which should be named
Makefile,
will be:
<Makefile1> =
obj-m := nothing.o
Unlike with previous versions of the kernel, it’s now also necessary to compile the module using
the samekernel that you’re going to load and use the module with. To compile it, you can type:
$ make -C /usr/src/kernel-source-2.6.8 M=pwd modulesThis extremely simple module belongs
to kernel space and will form part of it once it’s loaded.
In user space, you can load the module as root by typing the following into the command line:
# insmod nothing.ko

The insmod command allows the installation of the module in the kernel. However, this
particular moduleisn’t of much use.

It is possible to check that the module has been installed correctly by looking at all installed
modules:
# lsmod

The first driver: loading and removing the driver in user space

Finally, the module can be removed from the kernel using the command:
# rmmod nothing

By issuing the lsmod command again, you can verify that the module is no longer in the kernel.
The summary of all this is shown in Table 3.

Events User functions Kernel functions


Load module insmod
Open device
Read device
Write device
Close device
Remove module rmmod

Table 3. Device driver events and their associated interfacing functions between
kernel space and user
space.

The “Hello world” driver: loading and removing


the driver in kernel space
When a module device driver is loaded into the kernel, some preliminary tasks are usually
performed like resetting the device, reserving RAM, reserving interrupts, and reserving
input/output ports, etc.

These tasks are performed, in kernel space, by two functions which need to be present (and
explicitly declared): module_init and module_exit; they correspond to the user space
commands insmod and rmmod , which are used when installing or removing a module. To sum
up, the user commands insmod and rmmod use the kernel space functions module_init and
module_exit.

Let’s see a practical example with the classic program Hello world:
<hello.c> =
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void) {
printk("<1> Hello world!\n");
return 0;
}
static void hello_exit(void) {
printk("<1> Bye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);

The “Hello world” driver: loading and removing the driver in kernel space

The actual functions hello_init and hello_exit can be given any name desired. However, in order
for them to be identified as the corresponding loading and removing functions, they have to be
passed as parameters to the functions module_init and module_exit.

The printk function has also been introduced. It is very similar to the well known printf apart
from the fact that it only works inside the kernel. The <1> symbol shows the high priority of
the message (low number). In this way, besides getting the message in the kernel system log
files, you should also receive this message in the system console.

This module can be compiled using the same command as before, after adding its name into
the Makefile.
<Makefile2> =
obj-m := nothing.o hello.o

In the rest of the article, I have left the Makefiles as an exercise for the reader. A complete
Makefile that will compile all of the modules of this tutorial is shown in Appendix A.
When the module is loaded or removed, the messages that were written in the printk statement
will be displayed in the system console. If these messages do not appear in the console, you
can view them by issuing the dmesg command or by looking at the system log file with cat
/var/log/syslog.

Table 4 shows these two new functions.


Events User functions Kernel functions
Load module insmod module_init()
Open device
Read device
Write device
Close device
Remove module rmmod module_exit()

Table 4. Device driver events and their associated interfacing functions between
kernel space and user
space.
The complete driver “memory”: initial part of
the driver
I’ll now show how to build a complete device driver: memory.c. This device will allow a
character to be read from or written into it. This device, while normally not very useful,
provides a very illustrative example since it is a complete driver; it’s also easy to implement,
since it doesn’t interface to a real hardware device (besides the computer itself).
To develop this driver, several new #include statements which appear frequently in device
drivers need to be added:

<memory initial> =

/* Necessary includes for device drivers */

The complete driver “memory”: initial part of the driver

#include <linux/init.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_from/to_user */
MODULE_LICENSE("Dual BSD/GPL");
/* Declaration of memory.c functions */
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);
/* Structure that declares the usual file */
/* access functions */
struct file_operations memory_fops = {
read: memory_read,
write: memory_write,
open: memory_open,
release: memory_release
};
/* Declaration of the init and exit functions */
module_init(memory_init);
module_exit(memory_exit);
/* Global variables of the driver */
/* Major number */
int memory_major = 60;
/* Buffer to store data */
char *memory_buffer;
After the #include files, the functions that will be defined later are declared. The common
functions which
are typically used to manipulate files are declared in the definition of the file_operations
structure.
These will also be explained in detail later. Next, the initialization and exit functions—used when
loading and
removing the module—are declared to the kernel. Finally, the global variables of the driver are
declared: one
of them is the major number of the driver, the other is a pointer to a region in memory,
memory_buffer, which will be used as storage for the driver data.

The “memory” driver: connection of the device


with its files
In UNIX and Linux, devices are accessed from user space in exactly the same way as files are
accessed. These device files are normally subdirectories of the /dev directory.

To link normal files with a kernel module two numbers are used: major number and minor
number.
The major number is the one the kernel uses to link a file with its driver. The minor number is
for internal use of the device and for simplicity it won’t be covered in this article.

To achieve this, a file (which will be used to access the device driver) must be created, by
typing the following command as root:

# mknod /dev/memory c 60 0

In the above, c means that a char device is to be created, 60 is the major number and 0 is the
minor number.

Within the driver, in order to link it with its corresponding /dev file in kernel space, the
register_chrdev function is used. It is called with three arguments: major number, a string of
characters showing the module name, and a file_operations structure which links the call with
the file functions it defines. It is invoked, when installing the module, in this way:

<memory init module> =

int memory_init(void) {
int result;
/* Registering device */
result = register_chrdev(memory_major, "memory", &memory_fops);
if (result < 0) {
printk(
"<1>memory: cannot obtain major number %d\n", memory_major);
return result;
}
/* Allocating memory for the buffer */
memory_buffer = kmalloc(1, GFP_KERNEL);
if (!memory_buffer) {
result = -ENOMEM;
goto fail;
}
memset(memory_buffer, 0, 1);
printk("<1>Inserting memory module\n");
return 0;
fail:
memory_exit();
return result;
}

Also, note the use of the kmalloc function. This function is used for memory allocation of the
buffer in the device driver which resides in kernel space. Its use is very similar to the well
known malloc function.

Finally, if registering the major number or allocating the memory fails, the module acts
accordingly.

The “memory” driver: removing the driver


In order to remove the module inside the memory_exit function, the function unregsiter_chrdev
needs to be present. This will free the major number for the kernel.

The “memory” driver: opening the device as a


file
The kernel space function, which corresponds to opening a file in user space (fopen), is the
member open:of the file_operations structure in the call to register_chrdev. In this case, it is
the memory_open function. It takes as arguments: an inode structure, which sends information
to the kernel regarding the major number and minor number; and a file structure with
information relative to the different operations that can be performed on a file. Neither of these
functions will be covered in depth within this article.

When a file is opened, it’s normally necessary to initialize driver variables or reset the device. In
this simple example, though, these operations are not performed.

The memory_open function can be seen below:

<memory open> =

int memory_open(struct inode *inode, struct file *filp) {


/* Success */
return 0;
}

This new function is now shown in Table 5.


Events User functions Kernel functions
Load module insmod module_init()
Open device fopen file_operations: open
Read device
Write device
Close device
Remove module rmmod module_exit()

Table 5. Device driver events and their associated interfacing functions between
kernel space and user
space.

The “memory” driver: closing the device as a


file
The corresponding function for closing a file in user space (fclose) is the release: member of the
file_operations structure in the call to register_chrdev. In this particular case, it is the function
memory_release, which has as arguments an inode structure and a file structure, just like
before.

When a file is closed, it’s usually necessary to free the used memory and any variables related
to the opening of the device. But, once again, due to the simplicity of this example, none of
these operations are performed.

The memory_release function is shown below:

<memory release> =
int memory_release(struct inode *inode, struct file *filp) {
/* Success */
return 0;
}

This new function is shown in Table 6.

Events User functions Kernel functions


Load module insmod module_init()
Open device fopen file_operations: open
Read device
Write device
Close device fclose file_operations: release
Remove module rmmod module_exit()

Table 6. Device driver events and their associated interfacing functions between
kernel space and user
space.

The “memory” driver: reading the device


To read a device with the user function fread or similar, the member read: of the
file_operations
structure is used in the call to register_chrdev. This time, it is the function memory_read. Its
arguments are: a type file structure; a buffer (buf), from which the user space function (fread)
will read; a counter with the number of bytes to transfer (count), which has the same value as
the usual counter in the user space function (fread); and finally, the position of where to start
reading the file (f_pos).

In this simple case, the memory_read function transfers a single byte from the driver buffer
(memory_buffer) to user space with the function copy_to_user:

<memory read> =
ssize_t memory_read(struct file *filp, char *buf,
size_t count, loff_t *f_pos) {
/* Transfering data to user space */
copy_to_user(buf,memory_buffer,1);

/* Changing reading position as best suits */

if (*f_pos == 0) {
*f_pos+=1;
return 1;
} else {
return 0;
}
}

The reading position in the file (f_pos) is also changed. If the position is at the beginning of the
file, it isincreased by one and the number of bytes that have been properly read is given as a
return value, 1. If not at the beginning of the file, an end of file (0) is returned since the file
only stores one byte.

In Table 7 this new function has been added.

Events User functions Kernel functions Load module insmod module_init()

Open device fopen file_operations: open


Read device fread file_operations: read
Write device
Close device fclose file_operations: release
Remove modules rmmod module_exit()

Table 7. Device driver events and their associated interfacing functions between
kernel space and user
space.

The “memory” driver: writing to a device


To write to a device with the user function fwrite or similar, the member write: of the
file_operations structure is used in the call to register_chrdev. It is the function
memory_write, in this particular example, which has the following as arguments: a type file
structure;
buf, a buffer in which the user space function (fwrite) will write; count, a counter with the
number of bytes to transfer, which has the same values as the usual counter in the user space
function (fwrite); and finally, f_pos, the position of where to start writing in the file.

<memory write> =
ssize_t memory_write( struct file *filp, char *buf,
size_t count, loff_t *f_pos) {
char *tmp;
tmp=buf+count-1;
copy_from_user(memory_buffer,tmp,1);
return 1;
}
In this case, the function copy_from_user transfers the data from user space to kernel space.
PROJECT ON VIRTUAL CHARACTER DEVICE DRIVER
INTRODUCTION

The module we are going to develop in this chapter is a rough translation of the C code found in Rubini &
Corbet’s Linux Driver Drivers (Second Edition) text. However, unlike the skull driver of the previous
chapter (which was almost a line for line translation of the C code), the scullc driver I’m going to present
in this chapter is quite a bit less capable than the C version of LDD2.

This has nothing to do with the language choice; rather, it’s an issue of pedagogy. The scullc driver that
R&C present is far more sophisticated than it needs to be; indeed, it presents a good example of software
engineering and design.

The Design

In LDD2, R&C have chosen to develop a single device driver that implements several different views of
the device to real-world users. In a real-world device driver, this is exactly what you want to do and I
heartily recommend that you read Chapter Three in LDD2 to see how to create "polymorphic" device
drivers that present several different types of devices to Linux through the use of minor numbers. For
simplicity, however,I will implement each of these device drivers as separate drivers within this text.

This allows us to focus on the examples at hand (while presenting the source code for the full example)
without having to deal with extra code intended for use by other forms of the scullc device. Although
this chapter does not ultimately merge the device drivers together (leaving that as an exercise for the
user),doing so is not difficult at all.

Major and Minor Numbers

An application accesses a character device driver using ’filenames’ in the file system. These are the
filenamesof special device files and you normally find them in the /dev subdirectory. The following is a
(very)short list of some of the files you will typically find in the /dev subdirectory:

crw--w---- 1 root root 4, 0 Apr 18 14:16 /dev/tty0


crw------- 1 root root 4, 1 Apr 18 14:16 /dev/tty1
crw------- 1 root root 4, 2 Apr 18 14:16 /dev/tty2

The ’c’ appearing in this first column of the ’ls -l /dev/tty[0-2]" display indicates that tty0, tty1, and tty2
are all character devices. Other device types would place a different character here (e.g., block devices
place a ’b’ in column one).

Note the two columns of numbers between the columns containing ’root’ and ’Apr’. These numbers are
the major device number and the minor device number, respectively. In the listing above, the major
number is 4 (all three devices) and the minor numbers are 0, 1, and 2. The major number identifies the
device driver associated with a device. Therefore, tty0, tty1, and tty2 all three share the same device
driver, since they have the same major number. The kernel uses the major number, when you open a
file, to determine which device driver should receive the open call.
The kernel simply passes the minor number along to the device driver. The kernel, in no way,
manipulates or notes the value of the minor number. This value is strictly for use by the device driver.
Typically, a device driver will use the minor number to differentiate between several instances of the
same device type.

For example, if your computer has two peripheral adapters with the same hardware, the system will only
need one device driver (and one major number) to control the devices. The device driver can use the
minor number to differentiate between the two actual devices.

Both the major and minor numbers are eight-bit values at this time. This is a severe limitation that
Linux kernel developers are trying to remedy by the v2.6 kernel release, but so many applications and
devicedrivers out there are ’aware’ of the internal representation of device numbers under Linux that this
is going to be a difficult task to achieve. The problem is that there are far more than 256 devices
available for PCs today. Unfortunately, Linux inherited this problem from older UNIX variations and, as
you may recall, UNIX originated on old DEC equipment way back in the 1970’s. Back then, there weren’t
many devices available for machines like PDP-8’s and PDP-11’s (the original machines on which UNIX
ran). Therefore, 256 seemed like infinity at the time (also, it was the case that holding both the major
and minor number in a single word of memory was a big deal because systems didn’t have much memory
at the time (4K words was a typical system). Today, of course, memory and devices are plentiful, but
we’re still suffering from the limitations inherited from the original UNIX systems.

To add a new device to the system you need to assign a major number to that device. You should do this
within the init_module procedure by calling the following function2:

procedure linux.

register_chrdev
(
major :dword;
_name :string;
var fops :linux.file_operations
)

Dynamic Allocation of Major Numbers

The "biggie" device drivers (disks, consoles, serial ports, parallel ports, etc.), all have fixed major device
numbers assigned to them. You can find a list of the statically allocated devices in the Documentation/
devices.txt file of your Linux source distribution. As you can see by looking at this file, a large percentage
of the device numbers are already assigned (and re-assigned!), so finding a major device number that
doesn’t conflict with some other device is difficult. Unless you’re planning to use your device driver only
on your personal system, simply choosing a number of a device that isn’t present in your system will not
suffice.

Someone else who wants to use your device driver might very well have that equipment installed and a
conflict could occur. Fortunately, there is a way out: dynamic allocation of major numbers. If you pass
zero as the major number to register_chrdev, this function will not use zero as the majornumber; instead,
it uses the value zero as a flag to indicate that the caller is requesting that register_chrdevpick a handy,
currently unused, major device number.
Character devices:

1 mem
2 pty
3 ttyp
4 ttyS
5 cua
7 vcs
10 misc
14 sound
29 fb
36 netlink
128 ptm
136 pts
162 raw
180 usb
253 scullc
254 iscsictl

Removing a Driver From the System

Just before a module unloads itself from the system, it must relinquish its major number. You’ll generally
do this in the cleanup_module procedure by calling the following function:
procedure linux.unregister_chrdev( major:dword; _name:string );

The parameters are the major number and device name that you passed to the linux.register_chrdev
function in the module_init code. This function returns a negative error code in EAX if either parameter
does not agree with the original values.

File Operations

As you may recall from the discussion of linux.register_chrdev earlier, your driver communicates the
facilities it provides to the kernel (beyond init_module and cleanup_module) using the fops argument to
linux.register_chrdev. In this section we’ll discuss the fops table (type file_operations).

The file_operations data type is a table of function pointers. Each entry in this table contains either
NULL (zero) or the address of some procedure that provides some sort of functionality for the device
driver. Linux checks the entries in this table when invoking a particular device function. If the entry of
interest contains zero, the Linux will perform some default action (like returning an error code); if the
entry is non-zero,then Linux will call the procedure at the address specified by this table entry. As a
device driver writer, it is your responsibility to decide which functionality you’re going to provide, write
the procedures to provide this functionality, fill in a file_operations table with the addresses of the
functions you’ve written, and then pass the address of this table to the linux.register_chrdev during
driver initialization.
file_operations record for drivers:
file_operations:record
owner :dword; // Pointer to module_t;
llseek :procedure
(
file :dword; // Pointer to file
offset :qword; // 64-bit offset
whence :dword // Type of seek.
); @cdecl;
read :procedure
(
file :dword; // Pointer to file
buf :dword; // Buffer address.
size :dword; // Size of transfer
var offset :qword // Store new ofs here.
); @cdecl;
write :procedure
(
file :dword; // Pointer to file
buf :dword; // Buffer address.
size :dword; // Size of transfer
var offset :qword // Store new ofs here.
); @cdecl;
readdir :procedure
(
file :dword; // Pointer to file.
buf :dword; // data buffer.
count :dword // ignored (?)
); @cdecl;
poll :procedure
file:dword;
poll_table_struct:dword
); @cdecl;
_ioctl :procedure( inode:dword; file:dword ); @cdecl;
mmap :procedure
(
file:dword; // pointer to file
vmas:dword // pointer to vm_area_struct
); @cdecl;
open :procedure
(
inod :dword; //pointer to inode
file :dword //pointer to file
); @cdecl;
flush :procedure
(
file :dword //pointer to file
); @cdecl;
release :procedure
(
inod :dword; //pointer to inode
file :dword //pointer to file
);
fsync :procedure
(
inod :dword; //pointer to inode
de :dword; //pointer to dentry
datasync:dword
); @cdecl;
fasync :procedure
(
fd :dword; //file descriptor
file :dword; //pointer to file
on :dword
); @cdecl;
lock :procedure
(
file :dword; //file pointer
typ :dword;
filock :dword //pointer to file_lock
); @cdecl;
readv :procedure
(
file :dword; //pointer to file
iov :dword; //pointer to iovec
count :dword;
offs :dword
); @cdecl;
writev :procedure
(

file :dword; //pointer to file


iov :dword; //pointer to iovec
count :dword;
offs :dword
); @cdecl;
sendpage :procedure
(
file :dword; // Pointer to file.
thePage :dword; // Pointer to page struct.
pgNum :dword; // ???
size :dword;
var offset :qword
);
get_unmapped_area:
procedure
(
file :dword;
u1 :dword;
u2 :dword;
u3 :dword;
u4 :dword
);
endrecord;

CODE OF VIRTUAL CHARACTRE DEVICE DRIVER

/*
* plp_kmem.c - Minimal example kernel module.
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>

#include <linux/fs.h>
#include <linux/major.h>
#include <linux/blkdev.h>
#include <linux/cdev.h>

#include <asm/uaccess.h>

#define PLP_KMEM_BUFSIZE (1024*1024) /* 1MB internal buffer */

/* global variables */

static char *plp_kmem_buffer;

static struct class *plp_kmem_class;    /* pretend /sys/class */


//static dev_t plp_kmem_dev;        /* dynamically assigned char device */
static struct cdev *plp_kmem_cdev;    /* dynamically allocated at runtime. */

static int howmany = 1;


module_param(howmany, int, S_IRUGO);

/* function prototypes */

static int __init plp_kmem_init(void);


static void __exit plp_kmem_exit(void);

static int plp_kmem_open(struct inode *inode, struct file *file);


static int plp_kmem_release(struct inode *inode, struct file *file);
static ssize_t plp_kmem_read(struct file *file, char __user *buf,
                size_t count, loff_t *ppos);
static ssize_t plp_kmem_write(struct file *file, const char __user *buf,
                size_t count, loff_t *ppos);

typedef struct PSEUDO_DEV


{
    struct list_head list;
    static void *mem;
    struct cdev cdev;
    dev_t device_id;
}PSEUDO_DEV;

static LIST_HEAD(myDevList);

/* file_operations */

static struct file_operations plp_kmem_fops = {


    .read        = plp_kmem_read,
    .write        = plp_kmem_write,
    .open        = plp_kmem_open,
    .release    = plp_kmem_release,
    .owner        = THIS_MODULE,//this is a must for the system
                                      //ptr to this module's object/structure
};

/*
* plp_kmem_open: Open the kmem device
*/

static int plp_kmem_open(struct inode *inode, struct file *file)


{

  PSEUDO_DEV *obj;
  obj = container_of(inode->i_cdev, PSEUDO_DEV, cdev);

  file->private_data = obj;

//here the open is dummy - not always


#ifdef PLP_DEBUG
    printk(KERN_DEBUG "plp_kmem: opened device.\n");
#endif

    return 0;
}

/*
* plp_kmem_release: Close the kmem device.
*/

static int plp_kmem_release(struct inode *inode, struct file *file)


{

//dummy here - not always 

#ifdef PLP_DEBUG
    printk(KERN_DEBUG "plp_kmem: device closed.\n");
#endif

    return 0;
}

/*
* plp_kmem_read: Read from the device.
*/

static ssize_t plp_kmem_read(struct file *file, char __user *buf,


                size_t count, loff_t *ppos)
{
    size_t bytes = count;
    loff_t fpos = *ppos;
    char *data;

      //fetching the current device object/context

        struct pseudo_dev_obj *obj = file->private_data ; 

    if (fpos >= PLP_KMEM_BUFSIZE)


        return 0;
    
    if (fpos+bytes >= PLP_KMEM_BUFSIZE)
        bytes = PLP_KMEM_BUFSIZE-fpos;

    if (0 == (data = kmalloc(bytes, GFP_KERNEL)))


        return -ENOMEM;

#ifdef PLP_DEBUG
    printk(KERN_DEBUG "plp_kmem: read %d bytes from device, offset %d.\n",
        bytes,(int)fpos);
#endif

    memcpy(data,plp_kmem_buffer+fpos,bytes);
    
    if (copy_to_user((void __user *)buf, data, bytes)) {
        printk(KERN_ERR "plp_kmem: cannot write data.\n");
        kfree(data);
        return -EFAULT;
    }
    
    *ppos = fpos+bytes;

    kfree(data);
    return bytes;
}

/*
* plp_kmem_write: Write to the device.
*/

static ssize_t plp_kmem_write(struct file *file, const char __user *buf,


                size_t count, loff_t *ppos)
{
        //fetching the current device object/context
        struct pseudo_dev_obj *obj = file->private_data ;  

        //write to the kfifo, as per the standard rules


        //and return appropriately
        //the special case of blocking

    size_t bytes = count;


    loff_t fpos = *ppos;
    char *data;

    if (fpos >= PLP_KMEM_BUFSIZE)


        return -ENOSPC;

    if (fpos+bytes >= PLP_KMEM_BUFSIZE)


        bytes = PLP_KMEM_BUFSIZE-fpos;

    if (0 == (data = kmalloc(bytes, GFP_KERNEL)))


        return -ENOMEM;
       //this will copy data from user-space to system-space
       //this will also verify  that the user-space buffer is
       //really user-space buffer 
    if (copy_from_user((void *)data, (const void __user *)buf, bytes)) {
        printk(KERN_ERR "plp_kmem: cannot read data.\n");
        kfree(data);
        return -EFAULT;
    }

#ifdef PLP_DEBUG
    printk(KERN_DEBUG "plp_kmem: write %d bytes to device, offset %d.\n",
        bytes,(int)fpos);
#endif
        //user-space data was earlier copies into a system-space 
        //buffer
        //from system-space buffer data is copied to the 
        //pseudo device built using vmalloc() - meaning, a system
        //space buffer is treated as device and managed by this driver
    memcpy(plp_kmem_buffer+fpos,data,bytes);

    *ppos = fpos+bytes;

    kfree(data);
    return bytes;
}

/*
* plp_kmem_init: Load the kernel module into memory
*/

static int __init plp_kmem_init(void)


{
    PSEUDO_DEV *pDev;
    int count;
    char devName[32];
    
    for(count = 0; count < howmany; count++)
    {
                printk(KERN_INFO "plp_kmem: Allocating memory for Private Device Objects. (Private
DeviceNo:%d)\n", count);

                if (NULL == (pDev = kmalloc(sizeof(PSEUDO_DEV), GFP_KERNEL))) {


                        printk(KERN_ERR "plp_kmem: cannot allocate memory!\n");
                        goto error;
                }

        printk(KERN_INFO "plp_kmem: Allocating %d bytes of internal buffer. (DeviceNo:%d)\n",


                PLP_KMEM_BUFSIZE, count);

        if (NULL == (pDev->mem = vmalloc(PLP_KMEM_BUFSIZE))) 


        {
            printk(KERN_ERR "plp_kmem: cannot allocate memory!\n");
            goto error;
        }

        //TODO:


        //memset((void *)pDev->mem, 0, PLP_KMEM_BUFSIZE);

           //the first param. is the storage for the first device no
           //allocated dynamically
           //second param. is the minor no. associated with the first
           //device no. allocated dynamically
           //third param. is the no. of dynamic device ids. requested
           //last param. is a logical name associated with the 
           //device nos. 

        memset(devName, 0, 32);


        sprintf(devName, "kmem_pdev%d",count); 
                
        if (alloc_chrdev_region(&pDev->device_id, count, 1, devName))
            goto error;

        //printk("Major: %d, Minor:%d\n", MAJOR(plp_kmem_dev), MINOR(plp_kmem_dev));    

           //we are requesting for a system defined structure 


           //from a slab allocator in the KMA dedicated for
           //struct cdev 
           //you may ask the system to allocate or you may provide
           //the structure as a global data

        //todo:


        //if (0 == (plp_kmem_cdev = cdev_alloc()))
        //    goto error;

        kobject_set_name(pDev->cdev.kobj,"plp_kmem_cdev%d",count);


        //kobject_set_name(&plp_kmem_cdev->kobj,"plp_kmem_cdev%d",i);
           //we are passing the file-operations supported by
          //our driver to the system - this is known as 
            //passing hooks - registering our driver's characteristics
            //with the system
            //TODO: cdev_init()

        cdev_init(&pDev->cdev.ops, &plp_kmem_fops);


        //pDev->cdev.ops = &plp_kmem_fops; /* file up fops */
        if (cdev_add(&pDev->cdev, pDev->device_id, 1)) {
            kobject_put(&pDev->cdev->kobj);
            unregister_chrdev_region(pDev->device_id, 1);
            goto error;
        }

    /*    plp_kmem_class = class_create(THIS_MODULE, "plp_kmem");


        if (IS_ERR(plp_kmem_class)) {
        printk(KERN_ERR "plp_kmem: Error creating class.\n");
        cdev_del(plp_kmem_cdev);
        unregister_chrdev_region(plp_kmem_dev, 1);
        goto error;
        }
        class_device_create(plp_kmem_class, NULL, plp_kmem_dev, NULL, "plp_kmem");
    */

        //Add the private object to the list..


        list_add_tail(&pDev->list, &myDevList);
    }

    printk(KERN_INFO "plp_kmem: loaded.\n");


    printk(KERN_INFO "plp_kmem: howmany:%d.\n", howmany);
    return 0;

    error:
        printk(KERN_ERR "plp_kmem: cannot register device.\n");
           //return appropriate negative error code
        return 1;
}

/*
* plp_kmem_exit: Unload the kernel module from memory
*/

static void __exit plp_kmem_exit(void)


{
    int count = 0;
    //class_device_destroy(plp_kmem_class, plp_kmem_dev);
    //class_destroy(plp_kmem_class);

    PSEUDO_DEV *pDev, *n;


    list_for_each_entry_safe(pDev,n, &myDevList, list)
    {
           //removing the registration of my driver
        cdev_del(pDev->cdev);//remove the registration of the driver/device    
            //freeing the logical resources - device nos.
        unregister_chrdev_region(pDev->device_id,1);

           //freeing the system-space buffer


        vfree(pDev->mem);
    }
    printk(KERN_INFO "plp_kmem: unloading.\n");
}

/* declare init/exit functions here */

module_init(plp_kmem_init);
module_exit(plp_kmem_exit);

/* define module meta data */

MODULE_DESCRIPTION("Demonstrate kernel memory allocation");

MODULE_ALIAS("memory_allocation");
MODULE_LICENSE("GPL");
MODULE_VERSION("0:1.0");

Debugging Techniques

One of the greatest problems facing device driver (and kernel) programmers is the fact that it’s very
difficult to debug kernel code. It’s difficult to run a kernel or device driver under a debugger nor is it easy
to trace kernel code. Kernel errors are difficult to reproduce and track down because they often crash the
whole system requiring a reboot (which tends to destroy the data you could use to track down the
problem). Even if you’re fortunately enough to have a hardware debugging device like an in-circuit
emulator (ICE), it’s difficult to trace through the kernel because it’s just a set of "library functions" that
application programs execute. You can’t start and stop a "kernel process" like you can a normal
application. These problems create lots of challenges for kernel developers.

This section attempts to introduce some techniques you can use to help trace through kernel code and
track down problems that develop in your code.

Code Reviews

Without question, one of the most poweful tools you can use to track down problems in your device
driver code is analysis. Software engineering research indicates that software engineers tend to find
fewer than 60% of the defects in a program during normal testing and debugging. Since kernel testing
and debugging can hardly be considered ’normal’ one would expect to find even fewer defects in one’s
kernel code when relying specifically on testing and debugging techniques.
Character Device Drivers for Linux

The movement into open source communication for drivers has created a different set
of tools for PCs.  While Linux is focused on a kernel based framework, newer
applications are now being added to enhance performance.  This includes block device
drivers and character device drivers.  Both are used to provide higher communication
levels through the Linux system while allowing the framework to work more effectively
with open source.

The character device drivers are known to provide communication through the I/O, or
input / output stream.  Tape drives, serial ports and other extra applications are
typically handled through this type of driver.  The combination of functions in the
character device driver include basic communication from the main driver to a
component, such as a USB or additional hardware.  These combinations are combined
with memory commands to provide faster communication from the driver to the
interface.

The character device drivers are then divided into three main categories.  The first is
the byte stream I/O, which is responsible for interfacing with the devices that are added
into the computer.  For instance, if you have added a USB to your computer, the byte
stream will locate the device and will begin to connect it to the operating system.  The
second type of character device is the memory mapped devices.  This works by
providing memory from the device to the driver, which provides compatibility for
mapping and communicating specific applications.

The third type of character device driver is known as STREAMS.  This differs from the
other two character device models because it has a specific programming and coding
method used.  Typically, these will be added into networks and terminals, instead of the
devices that are added into the computer.  The STREAMS are responsible for uploading
of various data as well as configuration of support that is needed for the operating
system.

With these three types of character device drivers for Linux, is the ability to have driver
operations that are more compatible with high performance and better functioning. 
Communicating from the computer operating system and to remote devices as well as
networks, gives Linux higher performance by using character device drivers.

Potrebbero piacerti anche