Monday, December 30, 2013

Simple Ring Buffer


 

A simple ring buffer
As an example for which classes are handy, let's consider the implementation of a circular queue or ring buffer. A ring buffer can be a useful structure for transmitting data between asynchronous processes. For instance, you might use a ring buffer to buffer character data coming from or going to a peripheral device such as a serial port.

A ring buffer is a first-in-first-out data structure. You insert data at the buffer's back end and remove it from the front end. A typical implementation for a character ring buffer uses three variables:
char array[N];
int head, tail;

where:

  • array is a fixed-size array of characters
  • head is the index of the front element in the buffer
  • tail is the index of the array element just beyond the back element, or equivalently, the index in the array at which the next element will be inserted
To insert a new element at the back of the buffer, store the new value at array[tail]; and increment tail; if the new value for tail is N (the number of array elements), then reset tail to zero.
To remove an element from the front of the buffer, simply increment head, resetting head to zero when it reaches N.









Figure 1: A ring buffer with a maximum capacity of N elements


In effect, head and tail chase each other around the array, as shown in Figure 1. Initially, the head and tail have the same value, typically zero, indicating that the ring buffer is empty. As the tail pulls away from the head, the buffer fills up. If and when the tail gets so far ahead that it wraps around and catches up to the head, the buffer will be full. As the head catches up to the tail, the buffer empties. When the head completely overtakes the tail, the buffer will be empty once again.
Implementing a ring buffer that you can safely share between asynchronous processes requires attention to details that I don't want to get into just yet. For now, I'm just interested in using the buffer as an example to illustrate the need for class types. I'll defer further discussion of concurrency problems for a later column and focus on building a ring buffer suitable for use within a single process.
A ring buffer in C
Many C programmers would implement a ring buffer as a loose collection of variables. They might name the variables with a common prefix to show that they're related, but do little else to package the ring buffer as a single entity. For example, the declarations for a ring buffer used to transmit data to a UART (a serial port) might look like:
#define TXBUFSIZ 32
char txbuf[TXBUFSIZ];
int txhead, txtail;

Using this style, operations on the ring buffers are typically scattered throughout the code. For example, a handler that responds to an interrupt when the UART transmitter is ready for more data might look like:
if (/* UART transmit interrupt pending */)
    {
    if (txhead != txtail)
        {
        put_to_UART(txbuf[txhead]);
        if (++txhead >= TXBUFSIZ)
            txhead = 0;
        }
    /* clear interrupt */;
    }

Once you know how the ring buffer works, this code isn't particularly hard to read. For instance, the condition txhead != txtail tests that the buffer isn't empty. The expression txbuf[txhead] refers to the front element in the buffer, so that:
put_to_UART(txbuf[txhead]);
sends the front character of the ring buffer to the UART. Finally:
if (++txhead >= sizeof(txbuf))
    txhead = 0;

removes the front character from the buffer.
The problem with this style of programming is that it's harder to read and maintain than it needs to be. For example, suppose you discover that implementing head and tail as pointers, rather than as integers, yields better (smaller or faster) code. To implement this change, you must change the declarations for the transmit buffer to:
#define TXBUFSIZ 32
char txbuf[TXBUFSIZ];
char *txhead, *txtail;

Then you have to scrutinize all uses of head and tail throughout the code, and rewrite them if necessary. For example, you have to rewrite the code for the handler as:
if (/* UART transmit interrupt pending */)
    {
    if (txhead != txtail)
        {
        put_to_UART(*txhead);
        if (++txhead >= txbuf + TXBUFSIZ)
            txhead = txbuf;
        }
    /* clear interrupt */;
    }

Notice that the condition txhead != txtail remains the same, but the code to copy and remove the front element changes slightly.
Using abstract types in C
Skilled C programmers anticipate such changes and package data structures such as ring buffers as abstract types. An abstract type is one that's packaged to separate its functional behavior from its implementation. This lets you use an abstract type with little or no regard for its underlying implementation. Ideally, changes in the type's implementation should not change the way you use the type.
For example, you can implement the ring buffer as a structure type, such as:
enum { rb_bufsiz = 32 };
struct ring_buffer
    {
    char array[rb_bufsiz];
    int head, tail;
    };
typedef struct ring_buffer ring_buffer;

This version of the ring buffer uses an enumeration rather than a macro to define the constant for the array size. Enumeration constants are often preferable to macros for defining symbolic constants. (See "Enumeration Constants vs. Constant Objects," December 2001, p13.) The typedef immediately after the structure definition elevates the name ring_buffer from a tag to a full-fledged type name. (See "Tag vs. Type Names," October 2002, p7.)
Using the ring_buffer structure, you can declare the ring buffer for the UART transmitter as simply:
ring_buffer tx;
The ring_buffer structure should come with a comprehensive set of functions to implement the fundamental ring buffer operations. One such function is:
bool rb_empty(ring_buffer const *b)
    {
    return b->head == b->tail;
    }

Calling rb_empty(&tx) returns true if and only if tx is empty. In C99, you'd probably declare this function with the keyword inline to eliminate the function call overhead. In earlier C dialects, you might implement it as a macro:
#define rb_empty(b) ((b)->head == (b)->tail)
Then you can rewrite:
if (txhead != txtail)
    ...

more abstractly as:
if (!rb_empty(&tx))
    ...

Listing 1: C99 header file
// ring_buffer.h
#ifndef RING_BUFFER_INCLUDED
#define RING_BUFFER_INCLUDED

enum { rb_size = 32 };
struct ring_buffer
        {
        char buffer[rb_size];
        int head, tail;
        };

inline
bool rb_empty(ring_buffer const *b)
        {
        return b->head == b->tail;
        }

inline
char rb_front(ring_buffer const *b)
        {
        return b->buffer[b->head];
        }

inline
void rb_pop_front(ring_buffer *b)
        {
        if (++b->head >= rb_size)
                b->head = 0;
        }

#endif
Listing 1 contains a C99 header file, ring_buffer.h, which defines ring_buffer as an abstract type with supporting functions. Using these functions, the code for the UART handler looks like:
if (/* UART transmit interrupt pending */)
    {
    if (!rb_empty(&tx))
        {
        put_to_UART(rb_front(&tx));
        rb_pop_front(&tx);
        }
    /* clear interrupt */;
    }

I borrowed the function names front and pop_front from functions that support similar operations in the Standard C++ Library. Calling rb_front(&tx) returns the front element of tx, and calling rb_pop_front(&tx) removes the element from the front of tx.
What's missing?
Although you can implement abstract types in C, it's much harder to do it in C than in C++. C compilers don't give you any help with catching mistakes that undermine the abstraction.
For example, ring_buffer.h in Listing 1 doesn't provide a function to initialize a ring_buffer. Before you insert elements into a ring_buffer, you should initialize the head and tail to the same value, typically zero. However, you can get away without initializing the head and tail if you declare the ring_buffer as a statically allocated object. Objects with static storage are initialized to zero by default.
Now, suppose you change the ring_buffer implementation so that head and tail are declared as pointers instead of integer-valued indices. In that case, head and tail should be initialized to point to the initial element of the ring_buffer's array. However, if your program contains a ring_buffer initialized to zero by default, its head and tail pointers will be initialized to null pointers.
Of course, you can fix the bug by adding code in your application to initialize the ring_buffer properly, as in:
ring_buffer tx;
...
tx.head = tx.tail = tx.array;

But this undermines the whole purpose of using abstract types. Again, an abstract type should provide a sufficiently complete set of basic operations so that you can use that type without knowing its implementation. Ideally, the compiler shouldn't allow the user of an abstract ring_buffer type to write statements such as:
tx.head = tx.tail = tx.array;
which depend on knowing how the type is implemented.
A language that truly supports abstract type can detect and complain about statements that undermine abstractions. C compilers can't do this. C++ compilers can. Next month, I'll show you how.

Tuesday, November 19, 2013

Glimpses of scripting languages

What is a scripting language?
Scripting languages are programming languages that are typically written using high-level programming constructs, which makes them easy to learn. While there is no fixed definition of what
constitutes a scripting language, some of the common distinguishing traits of these languages include:
Interpreted: Scripting languages are typically converted into machine level code during runtime by an interpreter, rather than being compiled into an executable before running.
While this leads to a performance hit as each line has to be interpreted on the fly, it makes for easier portability between systems.
Typeless: Variables can be used to hold any type of data without having to explicitly declare their type. While this can make it easy to run into typecasting errors, it makes the language easier to
learn and can improve readability of the script.
Native Complex Types: Most scripting languages also natively provide certain complex data types like strings, arrays, lists and hashes.
Garbage Collection: Most scripting languages automate garbage collection (freeing of memory used by data). This can reduce the likelihood of memory leaks occurring.

What do scripting language names mean?
Perl: Stands for Practical Extraction and Report Language
Python: Named after the BBC show “Monty Python’s Flying Circus”
Tcl: Stands for Tool Command Language

Basic Syntax:
The following section walks you through the basic syntax used in Perl, Python and Tcl.
 Declaring and Using Scalar Variables
Perl
#Create a numeric variable
my $myNumber = 5;
#Create a string
my $myString = "Hello World";
#Create a new numeric and assign the original to it
my $myNewNumber = $myNumber;

Python
#Create a numeric variable
myNumber = 5;
#Create a string
myString = "Hello World";
#Create a new numeric and assign the original to it

myNewNumber = myNumber;
Tcl
#Create a numeric variable
set myNumber 5;
#Create a string
set myString "Hello World";
#Create a new numeric and assign the original to it
set myNewNumber $myNumber;


Displaying output to the Standard Output (STDOUT)
Perl
#Print a string to the screen
print "Hello World\n";
#Print a string and a number
my $sum = 5;
print "The sum is: ", $sum, "\n\n";
Python
#Print a string to the screen
print "Hello World";
#Print a string and a number
sum = 5;
print "The sum is: ", sum, "\n";
Tcl
#Print a string to the screen
puts "Hello World";
#Print a string and a number
set sum 5;
puts "The sum is: $sum \n";
Declaring and Using Lists
Perl
#Declare a list with three elements
my @myList = (5, "foo", 3.14);
#The index of the last element is accessed by $#<listname>
print "Size of myList: ", $#myList + 1, "\n";
#Access a single element in the list
print "The second element in the list is: ", $myList[1], "\n\n";
Python
#Declare a list with three elements
myList = [5, "foo", 3.14];
#The length of the list is accessed by len(<listname>)
print "Size of myList: ", len(myList);
#Access a single element in the list
print "The second element in the list is: ", myList[1], "\n";
Tcl
#Declare a list with three elements
set myList {5 "foo" 3.14};
#The index of the last element is accessed by llength
puts "Size of myList: [llength $myList]";
#Access a single element in the list
puts "The second element in the list is: [lindex $myList 1] \n";



Reading User Input from the Command Line (Command Line Arguments)
Perl
#Command line arguments are stored in list ARGV
#Get number of arguments
print "Number of Command Line Arguments: ", $#ARGV + 1, "\n";
#Access individual argument
print "The first Command Line Argument is: ", $ARGV[0], "\n\n";
Python
import sys;
#Command line arguments are stored in list sys.argv
4/5 www.ni.com
#Command line arguments are stored in list sys.argv
#Get number of arguments
print "Number of Command Line Arguments: ", len(sys.argv) - 1;
#Access individual argument
print "The first Command Line Argument is: ", sys.argv[1], "\n";
Tcl
#Command line arguments are stored in list $argv and $argc
#Get number of arguments
puts "Number of Command Line Arguments: $argc";
#Access individual argument
puts "The first Command Line Argument is: [lindex $argv 0] \n";


Reading User Input from the Standard Input (STDIN)


Perl
#Read a user input from the keyboard (terminates on return key)
print "Enter value, and then press Enter: ";
my $myUserInput = <STDIN>;
Python
#Read a user input from the keyboard (terminates on return key)
myUserInput = raw_input("Enter value, and then press Enter: ");
Tcl
#Read a user input from the keyboard (terminates on return key)
puts "Enter value, and then press Enter: ";
gets stdin myUserInput;


Syntax of Common Conditional Statements

 
Perl
#if, elseif, else
if ($myNumber == 5)
{
print "My Number is Five \n";
}
elsif ($myNumber == 3.14)
{
print "My Number is Pi \n";
}
else
{
print "My Number is neither Five nor Pi \n\n"
}
#while loop
while ($myNumber != 0) #could do: until ($myNumber == 0)
{
print "My Number is: ", $myNumber, "\n";
$myNumber -= 1;
}
print "\n";
#for loop
for ($myNumber = 0; $myNumber < 5; $myNumber++)
{
print "My Number is: ", $myNumber, "\n";
}
print "\n";
#foreach loop
foreach my $currentElement (@myList)
{
print "The current element is: ", $currentElement, "\n";
}
Python
#if, elseif, else
if (myNumber == 5):
print "My Number is Five";
elseif (myNumber == 3.14):
print "My Number is Pi";
print "My Number is Pi";
else:
print "My Number is neither Five nor Pi \n";
#while loop
while (myNumber != 0):
print "My Number is: ", myNumber;
myNumber -= 1;
print "\n";
#for loop
for myNumber in range (0, 5):
print "My Number is: ", myNumber;
print "\n";
#foreach loop
for currentElement in myList:
print "The current element is: ", currentElement;
Tcl
#if, elseif, else
if {$myNumber == 5} {
puts "My Number is Five \n";
} elseif {$myNumber == 3.14} {
puts "My Number is Pi \n";
} else {
puts "My Number is neither Five nor Pi \n\n";
}
#while loop
while {$myNumber != 0} {
puts "My Number is: $myNumber";
incr myNumber -1;
}
puts "\n";
#for loop
for {set myNumber 0} {$myNumber < 5} {incr myNumber} {
puts "My Number is: $myNumber";
}
puts "\n";
#foreach loop
foreach currentElement $myList {
puts "The current element is: $currentElement";
}



With Courtesy,
http://www.ni.com/white-paper/8910/en/


Tuesday, August 20, 2013

Glimpses of Device drivers

 Linux Device Driver

       Linux device driver to control a hardware card. We detail what functions need to be written, outline the supporting kernel functions that are available, explain how to initialize the driver and how memory is requested and allocated in an efficient manner, and provide a ``real'' driver example.

 Introduction to Linux:

        Linux is a 32-bit multitasking, multimedia operating system with complete source code, developed b y the free software community on the Internet. Linux is a clone of the Unix operating system that runs on Intel 80386/80486/Pentium computers. It supports a wide range of software, from TEX to the X Window System, to the GNU C/C++ compiler, to TCP/IP. The Linux system is mostly compatible at the source level with a number of Unix standards including IEEE POSIX.1, System V, and BSD. Linux also provides a complete Unix programming environment, including standard libraries, programming tools, compilers, and debuggers.

   A device driver consists of a set of routines that control a peripheral device attached to a workstation. The operating system normally provides a uniform interface to all peripheral devices. Linux and Unix present peripheral devices at a sufficiently high level of abstraction by observing that a large proportion of I/O devices can be represented as a sequence of bytes. Linux and Unix use the file--which is a well understood data structure for handling byte sequences--to represent I/O devices.

Linux I/O Subsystem:

 http://i.cmpnet.com/nc/unixworld/graphics/010.fig1.jpg
  
The above fig. shows  Linux architecture in the most general terms. Here, the kernel is shown wrapped around the hardware to depict that it is the software component that has direct access to--and control over--the system hardware, including the processor, primary memory, and I/O devices.

The below diagram shows the user-level programs communicate with the kernel using system calls, for instance, open(), read(), write(), ioctl(), close() , and the like.


http://i.cmpnet.com/nc/unixworld/graphics/010.fig2.jpg

Linux System Calls

The kernel is not a separate task under Linux. It is as if each process has a copy of the kernel. When a user process executes a system call, it does not transfer control to another process, but changes its execution mode from user to kernel mode. In kernel mode, while executing the system call, the process has access to the kernel address space, and through supporting functions , it has access to the address space of the user executing the call.

http://i.cmpnet.com/nc/unixworld/graphics/010.fig3.jpg




 From the above diagram, it depicts the I/O Subsystem. The Linux kernel implements a device-independent I/O system that serves all devices. A device driver provides the I/O system with a standard interface to the hardware, hiding the unique characteristics of the hardware device from the user to the greatest extent possible.

A user program that employs some basic system calls to read characters from a device into a buffer. When a system call is requested, the kernel transfers control to the appropriate device driver routine that executes on behalf of the calling user process (as shown in the above Figure).

All devices look like files on a Linux system. In fact, the user-level interface to a device is called a ‘special file These special files (often called device nodes) reside in the /dev directory. For ex ample, invoking the command ls -l /dev/lp* can be used to yield the following status information:
crw-rw-rw 1 root root 6, 0 April 23 1994 /dev/lp0

This example indicates that: ‘lp0’ is a character type device (the first letter of the file mode field is ‘c’), the major number is 6, and minor device number 0 is assigned to the device.

Major device numbers
are used by the Linux system to map I/O requests to the driver code, thereby deciding which device driver to execute, when a user reads from or writes to the special file. The minor numbers are entirely under the control of the driver writer, and usually refer to ‘sub-devices’ of the device. These sub-devices may be separate units attached to a controller. Thus, a disk device driver may, for example, communicate with a hardware controller (the device) which has several disk drives (sub-devices) attached. 

    outlines the flow of execution of a system call within the Linux operating system.

 http://i.cmpnet.com/nc/unixworld/graphics/010.fig4.jpg

  Device Drivers

A device driver is a collection of subroutines and data within the kernel that constitutes the software interface to an I/O device. When the kernel recognizes that a particular action is required from the device, it calls the appropriate driver routine, which passes control from the user process to the driver routine. Control is returned to the user process when the driver routine has completed. A device driver may be shared simultaneously by user applications and must be protected to ensure its own integrity.
    A device driver provides the following features:
  •        A set of routines that communicate with a hardware device and provide a uniform interface to the operating system kernel.
  •     A self-contained component that can be added to, or removed from, the operating system dynamically.
  •     Management of data flow and c ontrol between user programs and a peripheral device.
  •     A user-defined section of the kernel that allows a program or a peripheral device to appear as a `` /dev '' device to the rest of the system's software.
http://i.cmpnet.com/nc/unixworld/graphics/010.fig5.jpg
Figure: The relationship between device driver and the Linux system.


Character,  Block Device Drivers & Network Drivers:

Character and block device drivers are the two main types of peripheral drivers. A disk drive is an example of a block device, whereas, terminals and line printers are examples of character devices.
      A block device driver is accessed by user programs through a system buffer that acts as a data cache. Specific allocation and memory management routines are not necessary as the system transfers the data to/from the device. Character device drivers communicate directly with the user program, as there is no buffering performed. Linux transfers control to the appropriate device driver when a user program requests a data transfer between a section of its memory and a device. The device driver is responsible for transferring the data. Within Linux, the source for character drivers is kept in the /usr/src/linux/drivers/char directory. 

 Network device drivers:

      The network device are different from the block devices but it appears to be the mounted disk devices.

There are a few important differences between mounted disks and packet-delivery interfaces. To begin with, a disk exists as a special file in the /dev directory, whereas a network interface has no such entry point. The normal file operations (read, write, and so on) do not make sense when applied to network interfaces, so it is not possible to apply the Unix "everything is a file" approach to them. Thus, network interfaces exist in their own namespace and export a different set of operations.
Although you may object that applications use the read and write system calls when using sockets, those calls act on a software object that is distinct from the interface. Several hundred sockets can be multiplexed on the same physical interface.
But the most important difference between the two is that block drivers operate only in response to requests from the kernel, whereas network drivers receive packets asynchronously from the outside. Thus, while a block driver is asked to send a buffer toward the kernel, the network device asksto push incoming packets toward the kernel. The kernel interface for network drivers is designed for this different mode of operation.

Network drivers also have to be prepared to support a number of administrative tasks, such as setting addresses, modifying transmission parameters, and maintaining traffic and error statistics. The API for network drivers reflects this need, and thus looks somewhat different from the interfaces we have seen so far.

The network subsystem of the Linux kernel is designed to be completely protocol independent. This applies to both networking protocols (IP versus IPX or other protocols) and hardware protocols (Ethernet versus token ring, etc.). Interaction between a network driver and the kernel proper deals with one network packet at a time; this allows protocol issues to be hidden neatly from the driver and the physical transmission to be hidden from the protocol.

A Linux user process executes in a space isolated from critical system data and other user processes. This protected environment provides security to protect the process from mistakes in other processes. By contrast, a device driver executes in kernel mode, which places few limits on its freedom of action. The driver is assumed to be correct and responsible. A driver has to be part of the kernel in order to service interrupts and access device hardware. A driver should process interrupts efficiently to preserve the scheduler’s ability to balance the demands on the system. It should also use system buffers responsibly to avoid degrading system performance.

A device driver contains both interrupt and synchronous sections. The interrupt section deals with re al-time events and is driven by interrupts from devices. The synchronous section, which comprises the remainder of the driver, only executes when the process which it serves is also active. When a device requests some software service, it generates an ``interrupt.'' The interrupt handler must determine the cause of the interrupt and take appropriate action.

Kernel Programming Environment

A Linux process might have to wait for an event to occur before it can proceed. For example, a process might wait for requested information to be written to a hardware device before continuing. One way that processes can coordinate their actions with events is through sleep() and wakeup() system calls. When a process goes to sleep, it specifies an event that must occur, that is, wakeup, before it can continue its task. For example: interruptible_sleep_on(&dev_wait_queue) causes the process to sleep and adds the process number to the list of processes sleeping on dev_wait_queue . When the devi ce is ready, it posts an interrupt, causing the interrupt service routine in the driver to be activated. The routine services the device and issue a corresponding wakeup call, for example, wake_up_interruptible(&dev_wait_queue) , which wakes up the process sleeping on dev_wait_queue.

    Special care must be taken if two or more processes, such as the synchronous and interrupt portions of a device driver, share common data. The shared data area must be treated as a critical section. The critical section is protected by ensuring that processes only have mutually exclusive access to the shared data. Mutually exclusive access to a critical section can be implemented by using the Linux kernel routines cli() and sti() . Interrupts are disabled by cli() while the process is operating in the critical section and re-enabled by sti() upon exit from the critical section, as in:
cli()
 Critical Section Operations
sti().

Virtual File system Switch (VFS)

The principal interface between a device driver and the rest of the Linux kernel comprises a set of standard entry points and driver-specific data structures.http://i.cmpnet.com/nc/unixworld/graphics/010.fig6.jpg


 
 Illustrates how the entry points are registered with the Virtual File system Switch using the file_operations structure. This structure, which is defined in /usr/include/linux/fs.h , constitutes a list of the functions written for the driver. The initialization routine, xxx_init() registers the file_operations structure with the VFS and allocates a major number for the device.
Device Driver Development Supporting Functions

The table below contains most of the common supporting functions available for writing device drivers.

add_timer()
    Causes a function to be executed when a given amount of time has passed
cli()
    Prevents interrupts from being acknowledged
end_request()
    Called when a request has been satisfied or aborted
free_irq()
    Frees an IRQ previously acquired with request_irq() or irqaction()
get_fs*()
    Allows a driver to access data in user space, a memory area distinct from the kernel
inb(), inb_p()
    Reads a byte from a port. Here, inb() goes as fast as it can, while inb_p() pauses before returning.
irqaction()
    Registers an interrupt like a signal.
IS_*(inode)
    Tests if inode is on a file system mounted with the corresponding flag.
kfree*()
    Frees memory previously allocated with kmalloc()
kmalloc()
    Allocates a chu nk of memory no larger than 4096 bytes.
MAJOR()
    Reports the major device number for a device.
MINOR()
    Reports the minor device number for a device.
memcpy_*fs()
    Copies chunks of memory between user space and kernel space
outb(), outb_p()
    Writes a byte to a port. Here, outb() goes as fast as it can, while outb_p() pauses before returning.
printk()
    A version of printf() for the kernel.
put_fs*()
    Allows a driver to write data in user space.
register_*dev()
    Registers a device with the kernel.
request_irq()
    Requests an IRQ from the kernel, and, if successful, installs an IRQ interrupt handler.
select_wait()
    Adds a process to the proper select_wait queue.
*sleep_on()
    Sleeps on an event, puts a wait_queue entry in the list so that the process can be awakened on that event.
sti()
    Allows interrupts to be acknowledged.
sys_get*()
    System calls used to get information regarding the process, user, or group.
wake_up*()
    Wakes up a process that has been put to sleep by the matching *sleep_on() function.


  Name space

The name of the driver should be a short string. Throughout this article we have used "xxx" as our device name. For instance, the parallel (printer) device is the ``lp'' device, the floppies are the ``fd'' devices, and the SCSI disks are the ``sd'' devices. To avoid name space confusion, the entry point names are formed by concatenating this unique driver prefix with a generic name that describes the routine. For instance, xxx_open() is the ``open'' routine for the ``xxx'' driver.

Accessing Hardware Memory


A Linux user process can not access physical memory directly. The memory management sc heme--which is a demand paged virtual memory system--means that each process has its own address space (user virtual address space) that begins at virtual location zero. The kernel has its own distinct address space known as the system virtual address space.

The device driver copies data between the kernel'’s address space and the user program'’s address space whenever the user makes a read() or write() system call. Several Linux routines--such as, memcpy_*fs() and put_fs*() --enable device drivers to transfer data across the user-system boundary. Data may be transferred in bytes, words, or in buffers of arbitrary sizes. For example, memcpy_fromfs() transfers an arbitrary number of bytes of data from user space to the device, while get_fs_byte() transfers a byte of data from user space. Similarly, memcpy_tofs() and put_fs_byte() write data to user space memory.

The transfer of data betwee n the memory accessible to the kernel and the device itself is machine-dependent. Some machines require that the CPU execute special I/O instructions to move data between a device register and addressable memory--often called direct memory access (DMA). Another scheme, known as memory mapped I/O, implements the device interface as one or more locations in the memory address space. The most common method uses I/O instructions, provided by the system to allow drivers access the data in a general way. Linux provides inb() to read a single byte from an I/O address (port) and outb() to write a single byte to an I/O address. The calling syntax is shown here:

unsigned char inb(int port)
outb(char data, int port)

Writing a Character Device Driver


 Device Driver Initialization

In order that the device driver is correctly initialized when the operating system is booted, the xxx_init() routine must be executed. To ensure this happens, add the following line to the end of the chr_drv_init() function in the /usr/src/linux/driver/char/mem.c file:

mem_start = xxx_init(mem_start);

and resave the file back to disk.

Installing the Driver in the Kernel

A character device driver has to be archived into the /usr/src/linux/drivers/char/char.a library. The following steps are required to link the driver to the kernel:

  1.     Put a copy of the source file (say xxx_drv.c ) in the /usr/src/linux/drivers/char directory.
  2. Edit Makefile in the same directory so it will compile the source for the driver--add xxx_drv.o to the OBJS list, which causes the make utility to automatically compi le xxx_drv.c and add the object code to the char.a library archive.
  3. The last step step is the recompilation of the kernel.

The following steps are required to recompile the Linux kernel:
  •   Log in as root
  •   Change to the /root/linux directory
  •  Carry out the following series of commands
  •   make clean ; make config to configures the basic kernel
  •   make dep to set-up the dependencies correctly
  •  make to create the new kernel
  •  Wait for the kernel to compile and go to the /usr/src/linux directory.
    In order to boot the new kernel, copy the new kernel image ( /usr/src/linux/zImage ) into the place where the regular bootable kernel is found.

Device File Creation


In order to access the device using system calls, a special file is created. The driver files are normally stored in the /dev directory of the system. The following commands create the special device file:

mknod /dev/xxx c 22 0
    Creates a special character file named xxx and gives it major number 22 and minor number 0.
chmod 0666 /dev/xxx
    Ensures that every user in the system has read/write access to the device.

Conclusions

In this article, we have detailed how to write a hardware character device driver for the Linux operating system. We have outlined how to access hardware memory. We have also presented the kernel programming environment, as well as the supporting functions available to write a device driver. A number of worked examples were also presented to aid the programmer in developing his/her own device driver(s).


With Courtsey:
http://www.networkcomputing.com

Saturday, August 10, 2013

Unix v/s Linux

Introduction

The history of UNIX® dates back to 1969. Through the years, it has developed and evolved through a number of different versions and environments. Most modern UNIX variants known today are licensed versions of one of the original UNIX editions. Sun's Solaris, Hewlett-Packard's HP-UX, and IBM's AIX® are all flavors of UNIX that have their own unique elements and foundations. For example, Sun's Solaris is UNIX, but incorporates many tools and extensions designed to get the best out of Sun's own workstation and server hardware.
Linux® was born out of the desire to create a free software alternative to the commercial UNIX environments. Its history dates back to 1991, or further back to 1983, when the GNU project, whose original aims where to provide a free alternative to UNIX, was introduced. Linux runs on a much wider range of platforms than most UNIX environments, such as the Intel®/AMD led x86 platform. Most UNIX variants run on just one architecture.
Because of this history and the heritage of the two products, Linux and UNIX have a common foundation, but are also very different. Many of the tools, utilities, and free software products that are standard under Linux were originally developed as free alternatives to the versions available on UNIX. Linux often provides support for many different options and applications, picking the best (or most popular) functionality from the UNIX and free software environment.
An administrator or developer who supports Linux systems might find it uncomfortable to move to a commercial UNIX system. On the whole, the foundations of any UNIX-like operating system (tools, filesystem layout, programming APIs) are fairly standardized. However, some details of the systems show significant differences. The remainder of this article covers the details of these differences.

 Technical differences

The developers of commercial editions of UNIX have a specific target audience and platform for their operating system. They also have a pretty good idea of what applications they want to support and optimize. Commercial UNIX vendors do everything they can to maintain consistency between different versions. They have published standards that they follow for their customers.
The development of GNU/Linux, on the other hand, is more diverse. Developers come from many different backgrounds, and therefore have different experiences and opinions. There has not been as strict of a standard set of tools, environments, and functionality within the Linux community. The Linux Standards Base (LSB) project was formed in an attempt to alleviate this problem, but it has not provided as much help as hoped.
This lack of standards results in noticeable inconsistencies within Linux. It might seem a benefit to some for developers to have the freedom to emulate the best parts of other operating systems, but it can be very confusing when certain elements of Linux emulate different UNIX variants. For example, device names within Linux might emulate AIX, while the filesystem tools seem more like the tools supplied with HP-UX. These sorts of inconsistencies even exist between different Linux distributions. For example, Gentoo and RedHat have different methods for keeping their systems current with the latest patches and software releases.
By comparison, each new release of an operating system comes with a well-documented range of new features and changes within the UNIX space. Commands, tools, and other elements are rarely changed, and often the same command line arguments and interfaces remain over many editions of the software. Where there are significant changes, a commercial UNIX vendor often provides a compatibility layer, or the ability to run the older version of the tool.
This consistency means that tools and applications can be used on new editions of the operating system without a large body of testing. It is much easier for a UNIX user or administrator to update their skills on what is otherwise an unchanged UNIX operating system than the migration or adaptation of skills that might be required between Linux distributions.
Hardware architecture
Most commercial versions of UNIX are coded for a single, or possibly a small handful, of hardware architectures. HP-UX is available on PA-RISC and Itanium machines. Solaris is available on SPARC and x86. AIX is only for power processors, and so forth.
Because of these limitations, the UNIX vendors can optimize their code for these architectures. They can take advantage of every feature. Since they know their supported devices, their drivers can be better optimized as well. They are also not restricted by the weak BIOS limitations of most PCs.
Linux, on the other hand, has historically been designed to be as compatible as possible. Not only is Linux available for dozens of architectures, but the number of I/O and other external devices that might be used are almost limitless. The developers cannot assume that a specific hardware feature will be installed and, so often, cannot optimize as well. One example is the memory management on Linux. Since it was originally developed on x86 hardware, it used the segmented memory model. It adapted to use paged mode memory over time, but still retains some segmented memory requirements. This has caused problems for architectures that do not support segmented memory. This is not an issue for UNIX vendors. They know exactly which hardware features they have available.

The kernel

The kernel is the core of any operating system. The source code is not freely available for any of the commercial versions of UNIX. Quite the opposite exists for Linux. As such, procedures for compiling and patching kernels and drivers are vastly different. With Linux and other open source operating systems, a patch can be released in source code form and end users can install it, or even verify and modify it if desired. These patches tend to be far less tested than patches from UNIX vendors. Since there is not a complete list of applications and environments that need to be tested on Linux, the Linux developers have to depend on the many eyes of end users and other developers to catch bugs.
Commercial UNIX vendors only release their kernels in binary form. Some release the kernel as a single monolithic package, while others are able to dismantle the kernel and upgrade just a single module. Either way, it is still in binary form. If an update is required, the administrator has to wait for the vendor to release the patch in binary form, but they can be more secure knowing that the vendor has performed sufficient regression testing.
All commercial versions of UNIX have evolved to support some sort of module-based kernel. Drivers and certain features are available as separate components and can be loaded or unloaded from the kernel as needed. None is quite as open and flexible as the module architecture in Linux. However, with the flexibility and adaptability of Linux comes constant change. The Linux code base is constantly changing and the API can change at the whim of a developer. When a module or driver is written for a commercial version of UNIX, that code works far longer than the same driver written for Linux.

Filesystem support

One of the reasons Linux has become such a powerful tool is its immense compatibility with other operating systems. One of the most obvious features is the plethora of filesystems that are available. Most commercial version of UNIX supports two, or possibly three, different local filesystem types. Linux, however, supports almost all of the filesystems that are currently available on any operating system.  shows which filesystems are supported under which version of UNIX. You can mount each of these filesystems under Linux, although not all of them allow full read-write support.


Table 1. Filesystems that come standard with UNIX versions
AIXjfs, gpfs
HP-UXhfs, vxfs
Solarisufs, zfs
Irixxfs
Most commercial UNIX versions have at least some sort of journaling filesystem available. For instance, HP-UX uses hfs as its standard filesystem, but it also supports the journaling vxfs filesystems. Solaris is similar with ufs and zfs. Journaling filesystems are a critical component of any enterprise server environment. Linux was relatively late to support journaling filesystems, but now there are several options ranging from ports of commercial filesystems (xfs, jfs) to native Linux-only filesystems (ext3, reiserfs).
Other filesystem features include quota support, file access control lists, mirroring, snapshots, and resizing. These are are supported in some form or another on some of the Linux filesystems. Most of these features are not standardized on Linux. They might work one way on one filesystem, but another method is required on another filesystem. Some of these features are just not available on some Linux filesystems, and some require additional tools to be installed, such as a certain version of LVM or software raid package. Linux historically had difficulty reaching consensus on programming interfaces and standard tools, since there are so many filesystems that present these features so differently.
Since commercial versions of UNIX have a limited number of filesystems to support, their tools and methods are more standardized. For instance, since there is only one main filesystem on Irix, there is only one method used to set access control lists. This makes it much simpler for the end user as well as for vendor support.

Application availability

Most of the core applications are the same between UNIX and Linux. For instance, cp, ls, vi, and cc are commands that are available in both UNIX and Linux, and are very similar, if not apparently identical. The Linux versions tend to be based on the GNU version of these tools, whereas the current UNIX versions are based on the original UNIX tools. The tools on UNIX have had a very stable history and rarely change anymore.
This is not to say that commercial versions of UNIX cannot use the GNU tools. In fact, many commercial UNIX vendors include many of the GNU tools in their installations, or as free options. They are just not the standard tools in the customary locations. Certain free programs, such as emacs or Perl, do not have non-free counterparts. Most vendors offer these as pre-compiled packages that are either automatically installed or available as an optional component.
Free and open source applications are almost always assumed to be available and function on all Linux distributions. There are huge amounts of free software available for Linux. Many of these applications have been ported and are available in some form on commercial versions of UNIX.
When it comes to non-free and/or closed-source applications (CAD, financial, graphics design), however, Linux comes up short. While some software vendors have released versions of their programs for Linux, the majority seem to be delaying their releases until Linux adoption reaches a critical mass.

System administration

Although some Linux distributions come with a standard system management tool, such as SUSE's YaST, there is not a Linux-wide standard on tools for system management. Text files and command-line tools are available, but these can be cumbersome and sometimes difficult to remember. Each commercial version of UNIX has its own separate management interface. From this interface, aspects of the entire system can be manipulated and altered. One example of this is the System Administration Manager (SAM) on HP-UX.
From within SAM, there are modules where:
  • Users or groups can be managed.
  • Kernel parameters can be modified.
  • Networking is configured.
  • Disks are configured and initialized.
  • X server configuration can be changed.
This tool is well-written and incorporates well with the back-end text files. There is no such tool for Linux. Even SUSE's YaST is not nearly as complete, or compatible.
One aspect of UNIX and Linux that appears to be different for almost every version of UNIX and Linux is the location of system initialization scripts. Luckily /sbin/init and /etc/inittab are in standard locations. But beyond that, all the startup scripts are in different locations. Table 2 lists the location of system initialization scripts for various UNIX and Linux distributions.

Table 2. Location of system initialization scripts on various UNIX versions
HP-UX/sbin/init.d
AIX/etc/rc.d/init.d
Irix/etc/init.d
Solaris/etc/init.d
Redhat/etc/rc.d/init.d
SUSE/etc/rc.d/init.d
Debian/etc/init.d
Slackware/etc/rc.d
Because of the many different distributions of Linux and the almost infinite number of application and version differences, package management on Linux has always been a little tricky. There are a range of different package management tools available. The correct tool depends on which Linux distribution you are using. Further confusion results from different distributions using the Redhat Package Manager (RPM) file format, but their packages remain incompatible. This fragmentation has led to a myriad of different options, and it is not always obvious which system is in use within a particular environment
On the other hand, UNIX vendors use standard package managers. Even though there are different applications and formats among the different commercial UNIX variants within a specific version, the application environment is consistent and stable. For instance, Solaris has used the same package management tools since its inception. It has been, and will most likely always be, the same tools to identify, add, or remove packages on Solaris.
Recalling that commercial UNIX vendors supply the hardware that accompanies their operating systems, they are able to introduce hardware features that are much harder for Linux to include. For instance, recent Linux versions have attempted to support hot-swap components in hardware (with varied success). Commercial UNIX versions have had these features for many years. There is also more advanced hardware monitoring on commercial UNIX versions. The vendors can write drivers and hooks into their operating system that can monitor hardware health, such as ECC memory failures or power supply parameters, or any other hardware component. This sort of support on Linux is very premature.
Commercial UNIX hardware also has far more advanced initial boot options. Before the operating system boots, there are many options to decide how to boot, check system health, or set hardware parameters. The BIOS that is standard in PCs has few, if any, of these features.

Support

One of the most obvious differences between Linux and UNIX is the cost perspective. Commercial UNIX vendors charge significant amounts of money to acquire and use their versions of UNIX on their optimized hardware. Linux distributions, on the other hand, charge relatively little, if anything, for their operating system.
If a commercial version of UNIX is purchased, the vendors usually provide technical support to make sure the system works as expected. Most Linux users do not have the luxury of a company to stand behind their systems. They depend on the support of email lists, forums, and various Linux users groups. These support tools are not only restricted to Linux. Many administrator and users of commercial versions of UNIX participate in these free support groups to find and provide help. Many people even claim the free support groups are more responsive than the commercial vendor's support systems.

Summary

Overall, the general environment between UNIX and Linux is very similar. Moving as a user or administrator from Linux to UNIX, or vice versa, brings some inconsistencies, but overall is fairly seamless. Even though the filesystems or kernels might differ and require specialized knowledge to optimize, the tools and APIs are consistent. In general, these differences are no more drastic than variations among different versions of UNIX. All branches of UNIX and Linux have evolved and will be slightly different, but because of the maturity of the UNIX concept, the foundation doesn't change very much.

With courtesy:
http://www.ibm.com/developerworks/aix/library/au-unix-difflinux.html