Sunday, November 11, 2012

Static and Dynamic Libraries in C in Linux

Hi All...!!!

I am writing this post as a explanation for the doubts raised by few of the readers of my blog. I will be discussing about Static and Dynamic libraries in C in Linux with a sample code.

Libraries are also known as "shared components" or "archive libraries" which groups together multiple compiled object code files into a single file known as a library. 

Typically C functions which can be shared by more than one application are broken out of the application's source code, compiled and bundled into a library. The C standard libraries are examples of shared components which can be linked with your code.

The benefit is that each and every object file need not be stated when linking because the developer can reference the individual library. This simplifies the multiple use and sharing of software components between applications. It also allows application vendors a way to simply release an API to interface with an application. 

Components which are large can be created for dynamic use, thus the library remain separate from the executable reducing it's size and thus disk space used. The library components are then called by various applications for use when needed.

There are two Linux C library types which can be created:

Static libraries: Library of object code which is linked with, and becomes part of the application.


Dynamically linked shared object libraries: There is only one form of this library but it can be used in two ways.

  • Dynamically linked at run time but statically aware. The libraries must be available during compile/link phase. The shared objects are not included into the executable component but are tied to the execution.
  • Dynamically loaded/unloaded and linked during execution (i.e. browser plug-in) using the dynamic linking loader system functions.

Library naming conventions:
  • Libraries are typically names with the prefix "lib", this is true for all C standard libraies
  • Static libraries are with extension (.a)
  • Dynamic or Shared libraries are with extension (.so)
Static libraries are loaded when the program is compiled and Dynamically-linked libraries are loaded at run time. Dynamic libraries save the RAM space as against the Static library because linking to static libraries includes the actual code for the library functions with the executable. DLL code is kept at one location and is shared among all the processes/application that uses the DLL.


Dynamic loading and un-loading of shared libraries in C in Linux

This is the other way of using Dynamic libraries by loading/unloading at runtime.

Please refer to my earlier posts for more explanation/understanding

Step1: Implement the library source. Let us assume there are two files namely one.c and two.c each implementing one function as follows
$ vim one.c

#include <stdio.h>
int myadd(int a, int b)
{
        return a+b;
}

$ vim two.c

#include <stdio.h>
int mymult(int a, int b)
{
       return a*b;
}

Step2: Compile the sources to generate relocatable.

Note: ‘.so’ is the extension for dynamic libraries.

$ gcc -c -fpic one.c 
        ( to create a relocatable one.o )
$ gcc -c -fpic two.c 
       ( to create a relocatable two.o ) 
$ gcc -shared -o libmyown.so one.o two.o
        ( libmyown.so is the name of the dynamic library to be created )
         - shared flag used to create a shared library 

Step3: Create the source file to dynamically load our share-library.

here funAddPtr and funMultPtr are the function pointer for myadd and mymult

Please refer to man-pages for dlopen, dlsym, dlclose 

$ vim test.c


#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main()
{
void *module;
int (*funAddPtr)(int,int);
int (*funMultPtr)(int,int);

char *error;

printf("Example : Dynamic loading/un-loading of shared library\n");


/* Load dynamically loaded library */
module = dlopen("./libmyown.so", RTLD_LAZY);
if (!module) {
fprintf(stderr, "Couldn't open libmine.so: %s\n", dlerror());
exit(1);
}

    funAddPtr = dlsym(module, "myadd");
    if ((error = dlerror()) != NULL)  {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }

printf("Sum of 3,4 = %d\n", funAddPtr(3,4));


funMultPtr = dlsym(module, "mymult");
if ((error = dlerror()) != NULL)  {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }
    
    printf("Product of 3,4 = %d\n", funMultPtr(3,4));

dlclose(module);

return 0;
}

Step4: Compile the source and run

$ gcc -rdynamic -o test test.c -ldl

$./test

Example : Dynamic loading/un-loading of shared library
Sum of 3,4 = 7
Product of 3,4 = 12





Thursday, August 30, 2012

Linux PCI Driver Model


PCI Understanding:

  • The Peripheral Component Interconnect Bus (PCI) today is present in a wide variety of microcomputers ranging from Intel-based PC architectures to DEC-Alpha-based work-stations.
  • The CPU communicates with the PCI subsystem using a special chipset known as PCI-Bridge.
  • PCI-Bridge is an intelligent controller, that handles all necessary tasks to transfer data from or to the CPU or the Memory Subsystem.
  • On PCI the addresses and data are transferred as separate chunks over the bus because all bus lines can be used either as address or as data-lines.
  • Each bus device has its own 265byte space of Memory for configuration  purposes that can be accessed through the CONFIG_ADDRESS and the CONFIG_DATA registers.
  • All devices that are known to Linux you will see at /proc/pci.
  • Device resources (I/O addresses, IRQ lines) automatically assigned at boot time, either by the BIOS or by Linux itself (if configured). 
  • To identify a certain device while driver writing you will at least have to know the vendor-id and the device-id that is statically stored in the device configuration block.
  • Driver writers normally need to know only the base address of the device and the IRQ line that the device is using.
  • PCI device configuration information is Little-Endian. Remember that in your drivers. 
  • lspci enumerate all the devices.
          00:16.0 Communication controller: Intel Corporation 6 Series/C200 Series Chipset Family MEI Controller #1 (rev 04)
          00:19.0 Ethernet controller: Intel Corporation 82579V Gigabit Network Connection (rev 05)
          |     |   |
          |     |   |_Function Number
          |     |_PCI Device Number
          |_PCI Bus Number

  • lspci -tv
          -[0000:00]-+-00.0  Intel Corporation 2nd Generation Core Processor Family DRAM Controller
                          +-01.0-[01]--+-00.0  nVidia Corporation GF108 [GeForce GT 430]
                          |            \-00.1  nVidia Corporation GF108 High Definition Audio Controller
             |        |       |        |   
             |        |       |        |_ PCI Bus-1
             |        |       |_ PCI Bridge
             |        |_ PCI Bus-0
             |_ PCI Domain

  • Device configuration can be displayed with lspci -x
  • Standard information found in PCI configurations:
    • Offset 0: Vendor Id
    • Offset 2: Device Id
    • Offset 10: Class Id (network, display, bridge...)
    • Offsets 16 to 39: Base Address Registers (BAR) 0 to 5
    • Offset 44: Subvendor Id
    • Offset 46: Subdevice Id
    • Offsets 64 and up: up to the device manufacturer
    • Kernel sources: these offsets are defined in include/linux/pci_regs.h

  • PCI Device Initialization steps:
    • Enable the device.
    • Request I/O port and I/O memory resources.
    • Set the DMA Mask for both coherent and streaming DMA.
    • Allocate and Initialize shared coherent data.
    • Initialize device Registers.
    • Register IRQ handler.
    • Register to other subsystems (network, video ..)
    • Enable DMA or Processing Engines.
    • Before touching any device registers, the driver should first execute pci_enable_device(). This will:
      • Wake up the device if it was in suspended state.
      • Allocate I/O and memory regions of the device, If not already done by the BIOS.
      • Assign IRQ to the device, If not already done by the BIOS 
    • Enable DMA by calling pci_set_master(). This will:
      • Enable DMA by setting the bus master bit in the PCI_COMMAND register. The device will then be able to act as a master on the address bus.
      • Fix the latency timer value if it's set to something bogus by the BIOS.
      • This enables the PCI_COMMAND bit for Memory ­ Write Invalidate.
      • This also ensures that the cache line size register is set correctly.
    • Accessing configuration registers:
      • Reading:
        • int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
        • int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
        • int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
      • Writing:
        • int pci_write_config_byte(struct pci_dev *dev,  int where, u8 val);
        • int pci_write_config_word(struct pci_dev *dev,  int where, u16 val);
        • int pci_write_config_dword(struct pci_dev *dev,  int where, u32 val);
    • Accessing I/O registers and memory:
      • Each PCI device can have up to 6 I/O or memory regions, described in BAR0 to BAR5.
      • Access the base address of the I/O region:
        • long iobase = pci_resource_start (pdev, bar);
      • Access the I/O region size:
        • long iosize = pci_resource_len (pdev, bar);
      • Reserve the I/O region:
        • pci_request_region(pdev, bar, “my driver”);
    • Use pci_dma_set_mask() to declare any device with more (or less) than 32­bit bus master capability
    • In particular, must be done by drivers for PCI­X and PCIe compliant devices, which use 64 bit DMA.
    • If the device can directly address "consistent memory" in System RAM above 4G physical address, register this by calling pci_set_consistent_dma_mask().
    • You can allocate your cache consistent buffers if you plan to use such buffers. 
    • If needed by the device Set some “capability” fields and do some vendor specific initialization or reset Example: clear pending interrupts.
    • Register interrupt handlers:
      • Need to call request_irq() with the IRQF_SHARED flag, because all PCI IRQ lines can be shared.
      • Registration also enables interrupts, so at this point
        • Make sure that the device is fully initialized and ready to service interrupts.
        • Make sure that the device doesn't have any pending interrupt before calling request_irq(). 
      • Where you actually call request_irq() can actually depend on the type of device and the subsystem it could be part of (network, video, storage...). 
      • Your driver will then have to register to this subsystem.

  • PCI Device Shutdown: 
    • Disable the generation of new interrupts. If you don't, the system will get spurious interrupts, and will eventually disable the IRQ line. Bad for other devices on this line!
    • Release the IRQ.
    • Stop all DMA activity. Needed to be done after IRQs are disabled (could start new DMAs)
    • Release DMA buffers: streaming first and then consistent ones.
    • Unregister from other subsystems
    • Unmap I/O memory and ports with io_unmap().
    • Disable the device with pci_disable_device().
    • Unregister I/O memory and ports. If you don't, you won't be able to reload the driver. 

Tuesday, June 19, 2012

Your version is: java version "1.6.0_20". The correct version is: Java SE 1.6.

I cam across this issue with java while building Android source.

$ java -version < the output would be as below >
java version "1.6.0_20"
OpenJDK Runtime Environment (IcedTea6 1.9.10) (6b20-1.9.10-0ubuntu1~10.10.3)
OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)

To install new version of java, Follow the below steps which worked for me.

Step1:

     Append the below lines to sources.list at /etc/apt/ as a sudo user.
deb http://ppa.launchpad.net/sun-java-community-team/sun-java6/ubuntu maverick main 
deb-src http://ppa.launchpad.net/sun-java-community-team/sun-java6/ubuntu maverick main
$ sudo gedit /etc/apt/sources.list

Step2:

     Do the following installations and updates.
$ sudo apt-get install python-software-properties
$ sudo add-apt-repository ppa:sun-java-community-team/sun-java6
$ sudo apt-get update
$ sudo apt-get install sun-java6-jdk

Step3:

     After the installations are done, you need to update your java version with the following command.
$ sudo update-alternatives --config java
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                      Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      auto mode
  1            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      manual mode
  2            /usr/lib/jvm/java-6-sun/jre/bin/java       63        manual mode

Press enter to keep the current choice[*], or type selection number: 

Selection option 2 for selecting the right version of Java. 

Step4:

Now check your latest config and this should be as below.
$ java -version < the output would be as below >
java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
Java HotSpot(TM) 64-Bit Server VM (build 17.0-b16, mixed mode)


Monday, February 6, 2012

What is the difference between big endian and little endian ?

        endian or endianness refers to the ordering of individually addressable sub-components within the representation of a larger data item as stored in external memory. The most common cases refer to how bytes are ordered within a single 16-, 32-, or 64-bit word, and endianness is then the same as byte order. The usual contrast is whether the most significant or least significant byte is ordered first.

Consider a memory location occupied by an Integer variable of 4-bytes:


Little Endian : In this the lower-order byte of the number is stored in memory at the lowest address, and the high-order byte at the highest address. For example, a 4 byte Integer

will be arranged in memory as follows:

Base Address+0 -> Byte0
Base Address+1 -> Byte1
Base Address+2 -> Byte2
Base Address+3 -> Byte3

Example: Intel processors (those used in PC's) use "Little Endian" byte order.

Big Endian: In this the high-order byte of the number is stored in memory at the lowest address, and the low-order byte at the highest address. The same 4 byte integer would be stored as:

Base Address+0 -> Byte3
Base Address+1 -> Byte2
Base Address+2 -> Byte1
Base Address+3 -> Byte0

Example: Motorola processors (those used in Mac's) use "Big Endian" byte order.