13 Linux Introduction

 

  <--Last Chapter Table of Contents Next Chapter-->  

 
This section is an introduction to Linux programming.

13.1 Introduction to Processes

Linux is an operating system. It controls the execution of programs and access to system resources.

Linux can run multiple programs at one. Each running program is referred to as a process. The operating system switches to a new process every 100 milliseconds by default--like everything else in Linux, this value can be customized by changing the kernel source code.

Even on a Linux computer with only one user, there are usually several programs running in the background. The one process that is always running is "init": this is the root process, the first process Linux starts when the system is started.

13.1.1 Parents, Children and Families

Processes are grouped together into families. Each time a program starts another process, the new process is called a child and the original process is called a parent. When the parent unexpectedly stops running, Linux knows enough to stop all the related processes as well.

A multitasking program is a program that starts other processes to run simultaneously and assist it with its work. When a child process starts, Linux makes a copy of the parent's resources for the child. For example, all the files that where open to the parent are also open to the child process.

A multithreading program refers to independent streams of execution within a single process. Linux refers to each stream as a thread. Ada tasks and protected types, for example, are threads--they do not create entirely new programs they way multitasking works. Instead, they are miniature programs that run inside the parent task, sharing that parent's resources instead of getting their own copy.

Don't confuse Ada tasks with multitasking--the term "task" was chosen before the term "multithreading" became popular.


PID's

The ps command shows a list of all processes that are running. Each process has its own identifying number, called the PID (for Process Identification). Here's a typical output from the ps command:

$ ps
  PID TTY          TIME CMD
  579 tty1     00:00:00 login
  589 tty1     00:00:00 bash
  617 tty1     00:00:00 ps

PPID's

Processes also have a PPID (Parent Process ID) for identifying its parent process. The ps command l option (for "long") shows additional information about a process, including its PPID:

$ ps l
  F S   UID   PID  PPID  C PRI  NI ADDR    SZ WCHAN  TTY          TIME CMD
100 S     0   579     1  0  60   0    -   549 wait4  tty1     00:00:00 login
100 S     0   589   579  0  69   0    -   457 wait4  tty1     00:00:00 bash
100 R     0   624   589  0  70   0    -   634 -      tty1     00:00:00 ps
In this case, there are three processes in one family. The ps process (PPID 589) has a bash shell as its parent (PID 589). Likewise, the parent of the bash shell is the login command.


Process Groups

For complex programs with many processes, Linux can organize processes into process groups.

The ps lfw options (long, full, wide) will show the PID, the PPID and a simulated graph of groups of related processes.

$ ps lfw
  F   UID   PID  PPID PRI  NI   VSZ  RSS WCHAN  STAT TTY        TIME COMMAND
100     0   579     1   0   0  2196 1148 wait4  S    tty1       0:00 login -- root    
100     0   589   579  15   0  1828 1060 wait4  S    tty1       0:00 -bash
100     0   689   589  17   0  2484  824 -      R    tty1       0:00  \_ ps lfw
Here, it shows that "ps lfw" is a related to the bash shell process, but the bash shell is not related to the login command. The PPID number shows you which process started another, but not which group it belongs to. Because the bash shell process and the ps command are both members of the same group, if you were to kill the bash process, Linux would automatically kill the ps command as well because they are in the same group. With this arrangment, if the bash shell crashed, Linux can return control to the login program and allow you to log in again.

In the same way, if you have many processes in your program, you can group your processes so if they crash unexpectedly, the main program can continue running and take emergency actions. Process groups also provides an easy way to stop several processes at once. For example, a web server could put all the child processes into one group to make it easy to stop them when the server is being shut down.


Stopping Processes

Normally, a process stops when it reaches the end of the instructions it needs to execute.

You can stop a runaway program (or process) with the kill command, giving the command the PID number returned by the ps command.

  kill 624  #killing ps
Below we'll discuss stopping processes from inside a program.

A process that runs continually, performing some kind of regular system functions, is called a daemon (pronounced "day-mon", a variation on the word "demon" ). It's referred to as a daemon as if a little evil creature was running around doing work on its own. If you have a web server running, for example, you could refer to it's process as the web daemon.

13.1.2 Ownership and Permissions

For the sake of security, all programs belong to an owner and one or more groups. In the same way that all users have a login name (the "owner") and a list of groups they belong to, every program acts as if it's running on behalf of a particular user and their groups. If you're login is "bob", for example, any program you run will be owned by the "bob" login and whatever groups the "bob" login belongs to--your program can only access files that the "bob" login can access. If a program is owned by the superuser login, it doesn't automatically run with the full authority of the superuser.

This can be circumnavigated by the setuid and setgid permissions. When a program is marked with the setuid or setgid active, the program is owned by whatever owner and group owns the file. This is used for system programs that have to schedule events between multiple people, like the printer daemon.

13.2 Using System and OsLib.Spawn

Often, the first question new Linux programmers ask is, "How to do you run a shell command like ls from a program?"

The easiest, though not necessarily most practical, way to do Linux programming is to use the standard C library's system call. System starts a new process, starts a shell running and gives the shell whatever command to execute that you specify. The command executes just as if you typed it in at the command prompt.

For example, to list the files in the current directory on the screen, you'd type:

  function system( cmd : string ) returns integer;
  pragma Import( C, system );
  ...
  Result := system( "ls" & ASCII.NUL );
The result is the exit status returned by the command, usually zero if it executed successfully or non-zero if there was an error.

To capture the output of the system command, you can redirect the results to a file using the shell's output redirect, ">". This creates a text file for you to open with Ada.Text_IO.Open.

  Result := system( "ls > /tmp/ls.out" & ASCII.NUL);
  Ada.Text_IO.Open( fd, in_file, "/tmp/ls.out" );
  ...
The following is a simple program to print to the printer with the Text_IO library. It creates a text file and then uses system to run lpr to print it.
  with Ada.Text_IO; use Ada.Text_IO;
  procedure printer is
  -- a program for simple printing

  function System( s : string) return integer;
  pragma import( C, System, "system");
  -- starts a shell and runs a Linux command

  procedure PrintFile( s : string) is
  -- run the lpr command
    Result : integer;
  begin
      Result := System( "lpr " & s & ASCII.NUL );
      Put_Line( "Queuing " & s & "..." );
      if Result /= 0 then
         Put_Line( "system() call for lpr failed" );
      else
         Put_Line( "Printing is queued" );
      end if;
  end PrintFile;

  procedure CreateFile( s : string) is
  -- run the touch command
    Result : integer;
  begin
     Put_Line( "Creatinig " & s & "..." );
     Result := System( "touch " & s & ASCII.NUL );
     if Result /= 0 then
        Put_Line( "system() call for touch failed" );
     else
        Put_Line( "Spool file initialized" );
     end if;
  end CreateFile;

  -- the text file to print

  SpoolPath : constant string := "/tmp/spool.txt";
  SpoolFile : File_Type;

begin

  -- To open an out_file in text_io, it must exist.
  -- Create file will create a new spool file.

  -- Set_Output will redirect all output to the
  -- spool file.

  CreateFile( SpoolPath );
  Open( SpoolFile, out_file, SpoolPath);

  Set_Output( SpoolFile );
  -- write the report to printer

  -- Linux normally will not eject a page when
  -- printing is done, so we'll use New_Page.

  Put_Line( "Sales Report" );
  Put_Line( "------------" );
  New_Line;

  Put_Line( "Sales were good" );
  New_Page;
  -- Now, restore output to the screen, close
  -- the file and queue the file for printing
  -- using lpr.

  Set_Output( Standard_Output );
  Close( SpoolFile );

  PrintFile( SpoolPath );

  Put_Line( "Program done...check the printer" );

end printer;
Although this program will work for simple applications, another improved program to print using pipes is discussed below.

The system function is convenient but it has a couple of important drawback:

GNAT's OsLib provides a subprogram like system called spawn. It executes a Linux program without starting a shell first. Spawn requires a bit of setup to use. You have to define an array of access type arguments for the command. Once you invoke spawn, it returns a boolean value indicating whether the spawn succeeded or failed. The following excerpt is from an example program in the OSLib section covered below.
  Arguments : Argument_List( 1..1 );
  -- an argument list for 1 argument

  Ls : constant string := "/bin/ls";
  -- the program we want to run

  WasSpawned: boolean;
  RootDir : aliased string := "/";

begin

  Arguments(1) := RootDir'unchecked_access;
  -- unchecked to avoid useless (in this case) accessibility warning

  Spawn( Ls, Arguments, WasSpawned );

  if WasSpawned then
    New_Line;
    Put_Line( "End of ls output -- Spawned worked" );
  else
     Put_Line( "Spawn failed");
  end if;
This fragment runs the ls command, prints the results on the screen, and then displays the success of the command on the screen. Notice there are differences between spawn and system: If spawn is too limited, many UNIX and Linux programming books tell you how to create your own spawn style subprograms using fork, wait and the exec family of standard C commands. Here is an example of a short C function that executes a program and puts the results (standard output and standard error) to a text file of your choosing, allowing up to three parameters for the command.
  int CRunIt( char * path, char * outfile,
    char * param1, char * param2, char * param3 ) {

  pid_t child;
  int fd0, fd1, fd2;
  int status;
  int i;
  if ( !(child = fork()) ) {
    /* Redirect stdin, out, err */
    for (i=0; i< FOPEN_MAX; ++i ) close( i );
    fd0 = open( "/dev/null", O_RDONLY );
    if (fd0 < 0) exit( 110 );
    fd1 = open( outfile, O_WRONLY | O_CREAT | O_TRUNC );
        if (fd1 < 0) exit( 111 );
    fd2 = dup( 1 );
    if (param1[0]=='\0') {
      execlp( path, path, NULL );
    } else if (param2[0]=='\0') {
      execlp( path, path, param1, NULL );
    } else if (param3[0]=='\0') {
      execlp( path, path, param1, param2, NULL );
    } else {
      execlp( path, path, param1, param2, param3, NULL );
    }

    /* if we got here, file probably wasn't found */

    exit( errno );
  }
  wait( &status );
  if ( WIFEXITED( status ) != 0 )
    status = WEXITSTATUS( status );
  return status;
}
NoteIt is possible to rewrite this subprogram into Ada, but it's easier in C because of the constants, macros and execlp takes a variable number of parameters.
This function returns some special exit status values: 110 if /dev/null couldn't be opened for standard input, and 111 if the output file you specified couldn't be opened.
  function CRunIt( cmd, outfile, parm1, parm2, parm3 : string ) return integer;
  pragma Import( C, CrunIt, "CRunIt" );
  ..
  Result := CrunIt( "/bin/ls" & ASCII.NUL, -- executable to run
    "/tmp/ls.out" & ASCII.NUL, -- where output should go
    "" & ASCII.NUL, -- parameter 1 (none)
    "" & ASCII.NUL, -- parameter 2 (none)
    "" & ASCII.NUL ); -- parameter 3 (none)
An important part of running commands like this is deciding on temp file names that won't be used if two copies of the program are run at the same time. There's two ways to do this: If you are interested in accessing Linux more directly, read the next section.

13.3 The Linux Environment

Because Linux is based on decades old UNIX, the Linux development environment is like an onion. Originally, all UNIX programs accessed the operating system through kernel calls. As time went on, new ways of accessing the kernel were added, and new libraries were made to encapsulate common problems. Finally, Ada itself comes with many standard packages and features to work with the operating system.

For the Ada programmer, it's not difficult to work with the operating system. Gnat comes with many standard libraries. These are built using the standard C libraries. The C libraries, in turn, work by accessing the kernel. The problem is to decide which of the many ways to access Linux is the best suited for your program.

For simple tasks, using the standard Ada packages is the most straightforward way of working with Linux. However, the standard Ada packages were designed for portability: they only allow access to the most basic Linux features, and they aren't particularly fast when doing it. Working with the standard C libraries is a compromise between speed and convenience: the C libraries give you more features, but require you to import C function calls and convert between Ada and C data types. For maximum speed and flexibility, you can only work with kernel, but then you risk extra work by rewriting subprograms that already exist in the standard libraries.

To understand the differences between these layers, consider the problem of allocating dynamic memory. Usually you allocate memory with the Ada new statement. Where does new get its memory? It uses the standard C library's malloc function. But where does malloc get its memory? It gets it from the Linux kernel call mmap (memory map). The most direct way to get memory, and the method that gives you the most control, is mmap. On the other hand, the easiest way would be to use new and to let Ada allocate the memory and manage the details for you.

It's the same with multithreading and sequential files. Multitasking is based on LinuxThreads, a part of the standard C library, which in turn is based on the kernel's clone function. Sequential files are based on the standard C library's stream functions, which in turn are implemented using the kernel's file handling functions.

Figure: Ways of Doing the Same Thing
 
 
Memory Allocation Multithreading Sequential Files
Standard Ada:
new
task / protected
Ada.Sequential_IO package
Standard C libraries:
malloc
LinuxThreads functions
C stream functions
Linux Kernel:
mmap
clone function
kernel file functions
As you move down the list from the standard Ada libraries to the Linux kernel, your program becomes more platform specific. A program that uses new will run on any operating system that can run Ada 95. If you use malloc, your program will run on any operating system that uses has the standard UNIX C libraries available. If you use mmap, your program will run on any Linux computer—not just Intel-based Linux, but any flavour of Linux, include Sun UltraLinux or Apple Mklinux. Remember that Linux is a portable operating system: all versions of Linux will have mmap available.

13.4 Standard C Libraries

We've already covered many of the standard Ada libraries and language features in the above sections.

The standard C libraries define standardized subprograms that exist across most version of UNIX. They are "wrappers": that is, they do not work directly with the kernel. For example, the standard C libraries are not a part of the kernel, but they use the kernel to implement standard C functions that you'd find across many UNIX'sThe main C library, called libc, is automatically loaded by Gnat and you can import subprograms from it directly. Other standard C libraries, such as C's math library, libm, or the password encryption library, libcrypt, need to be linked in at the linking stage. The standard C library calls are defined in the online manual pages.

13.5 The Linux Kernel

There are three basic ways to work with the Linux kernel: kernel calls, devices and the proc file system. Because Linux is organized around files, the last two ways let you operate devices and get system information simply by opening and working with files using standard Linux file operations. This is makes it easier to work with Linux, but it also can make some tasks hard to visualize because you have to do them through a sequence of abstract file operations.

In a few cases, standard libraries and kernel calls have names that overlap, which can be confusing.

In addition, Linux sometimes provides alternative versions of system calls based on different flavours of UNIX. For example, there are two different system calls to assign an environment variable, one based on BSD UNIX (setenv) and another based on the POSIX standard (putenv). Both do exactly the same thing, but their parameters are slightly different. Linux provides both to make it easier to move programs written for other versions of UNIX to Linux. But for the Ada programmer, you have to choose the one that's easiest to use in your program.

13.5.1 Kernel Calls

Kernel calls (sometimes called system calls or syscalls) are basic operations that are implemented directly in the kernel. There are C libraries which supply some thin wrappers on the calls which do some of the setup and cleanup for your. Most kernel calls are in described in the C header file unistd.h. The appendices contain a list of the Linux kernel calls.

The kernel calls are documented in the online manual pages, but these are sometimes out of date due to the ever-changing nature of Linux.

13.5.2 Devices

The /dev directory defines device files. These files let you work with devices connected to your computer by using standard file operations. Devices can include hard drives, sound cards and the system console (the text display).

Devices are recognized by their names:

In addition, most distributions define the following links:
There are more device files than there are devices on a computer. For example, there may be 32 serial port device files defined, but that doesn't mean that there are actually 32 serial port on the computer. You will have to open the device and check for an error if it does not exist.

For example, opening  /dev/lp1 and writing a file to it writes the file as raw data to the first parallel port printer. Information on how these devices work is usually found in the How-To's and other system documentation.

Special functions specific to a device are programmed with the  ioctl()  function. For example, you'd use ioctl() on /dev/dsp to set the sound volume on your sound card.

The documentation for device files are often difficult to find. Sometimes documentation is contained in the kernel documentation (the /usr/doc/kernel.. directory) or in the kernel C header files (the /usr/src/linux/include/... directories). A list of some of the ioctl operations are listed in an appendix.

13.5.3 Proc File System

The /proc directory isn't really a directory at all. That is, it's a fake directory that's not physically on the hard disk, but you can still look into it and open the files it contains. The proc file system contains system information that you can access by opening the files and reading them. Some proc files may be written to in order to change system settings. For example, there are files that give you the information on how busy the CPU is, how much free memory you have, and the environment variables for the current process.

The contents of these files are described in the proc man page.

13.5.4 AudioCD: An Example Program

The following CD-ROM audio CD player illustrates kernel calls, a standard C library function, and using a device file.
with Ada.Text_IO, System;
use Ada.Text_IO;
procedure audiocd is
  -- Sample program for playing audio CD's

  -- DEVICES
  --
  -- This section deals with device files, in particular,
  -- the cdrom device

  DevCDROM : constant string := "/dev/cdrom";
  -- path to the CDROM device, usually /dev/cdrom

  type ioctlID is new integer;
  type aFileID is new integer;
  -- Define these as separate types for error checking.
  -- A ioctlID is never the same as a FileID.

  type byte is new integer range 0..255;
  for byte'size use 8;

  CDROMPLAYTRKIND : constant ioctlID := 16#5304#;
  CDROMSTOP : constant ioctlID := 16#5307#;
  CDROMSTART : constant ioctlID := 16#5308#;
  -- various CDROM ioctl functions as mentioned in the
  -- CDROM documentation in /usr/doc/kernel...

  type aDummyParam is new integer;
  -- define this as a separate type to make sure nothing
  -- important is used as a third parameter to ioctl_noparam

  -- a version of ioctl for functions that don't
  -- use a third parameter

  procedure ioctl_noparam( result : out integer;
       fid : aFileID;
       id : ioctlID;
       ignored : in out aDummyParam );
  pragma import( C, ioctl_noparam, "ioctl" );
  pragma import_valued_procedure( ioctl_noparam );
  type cdrom_ti is record
       start_track, start_index : byte;
       end_track, end_index : byte;
  end record;

  -- from /usr/src/linux/include/linux/cdrom.h
  -- PLAYTRKIND ioctl function uses cdrom_ti record

  procedure ioctl_playtrkind( result : out integer;
       fid : aFileID;
       id : ioctlID;
       info : in out cdrom_ti );
  pragma import( C, ioctl_playtrkind, "ioctl" );

  pragma import_valued_procedure( ioctl_playtrkind );
  -- KERNEL CALLS
  --
  -- Calls to the Linux kernel (besides ioctl).

  procedure open( id : out aFileID;
       path : string;
       flags : integer );
  pragma import( C, open, "open");
  pragma import_valued_procedure( open );
  -- open is a kernel call to open a file

  procedure close( result : out integer; id : aFileID );
  pragma import( C, close, "close");
  pragma import_valued_procedure( close );
  -- close is a kernel call to close a file

  -- C LIBRARY CALLS
  --
  -- Calls to the standard Linux C libraries

  procedure perror( prefixstr : string );
    pragma import( C, perror, "perror");
  -- perror is a standard C library call to print
  -- the last error message from a kernel call or the
  -- standard C libraries on the screen

  cd : aFileID;
  playinfo : cdrom_ti;
  dummy : ADummyParam;
  ioctl_result : integer;
  close_result : integer;
  ch : character;

begin

  Put_Line( "This program plays an audio CD in your CDROM drive" );
  New_Line;

  -- open the /dev/cdrom file so we can control the CDROM drive
  -- using ioctl

  Put_Line( "Openning " & DevCDROM & "..." );
  Open( cd, DevCDROM & ASCII.NUL, 0 );
  if cd < 0 then
     perror( "Error openning CDROM drive" );
  end if;
  -- start the CDROM drive

  Put_Line( "Spinning up cdrom..." );
  ioctl_noparam( ioctl_result, cd, CDROMSTART, dummy );
  if ioctl_result < 0 then
       perror( "Error spinning up the CDROM drive" );
  end if;

  -- display menu

  New_Line;
  Put_Line( "1 = Play, 2 = Quit" );
  New_Line;

  -- Main loop. Repeat until 2 is selected.

  loop
    Put( "Select a function (1-2): " );
    Get( ch );
    case ch is
    when '1' => playinfo.start_track := 1; -- first track
                       playinfo.start_index := 0; -- no effect
                       playinfo.end_track := 9; -- final track (inclusive)
                       playinfo.end_index := 0; -- no effect
         ioctl_playtrkind( ioctl_result, cd, CDROMPLAYTRKIND, playinfo );
    when '2' => ioctl_noparam( ioctl_result, cd, CDROMSTOP, dummy );
         exit;
    when others => Put_Line( "Pardon?" );
    end case;

    if ioctl_result < 0 then
         perror( "Error controlling CDROM drive" );
    end if;

  end loop;

  -- Close the CDROM device

  Close( close_result, cd );
  if close_result < 0 then
        perror( "Error controlling CDROM drive" );
  end if;

end audiocd;

13.6 Standard Input/Output/Error

Linux defines three default I/O streams. Standard input (Linux file id 0) is the file form which all keyboard input normally comes to your program. Standard output (Linux file id 1) is the default where the output of your program is written. Standard error (Linux file id 2) is the file to which error messages are written. Usually, standard input is from the keyboard, and standard output and error are directed to the screen. There are two output streams so that if you redirect the results of a command to a file, such as "ls > temp.out", any errors that occur will still appear on the screen.

The following program writes messages to standard output and standard error using Text_IO:

with ada.text_io;
use ada.text_io;

procedure stderr is
-- an example of writing messages to standard error

begin

  Put_Line( "This is an example of writing error messages to stderr" );
  New_Line;

  -- Text_IO defines a file called Standard_Error, always open,
  -- that you can write error messages to.

  Put_Line( Standard_Error, "This message is on standard error" );
  Put_Line( "This message is on standard output" );
  New_Line;

  -- you can use Set_Output to send all Put_Line's to Standard_Error

  Set_Output( Standard_Error );
  Put_Line( "This is also on standard error");

  Set_Output( Standard_Output );

  Put_Line( "But this is on standard output");
end stderr;

This is an example of writing error messages to stderr

This message is on standard error
This message is on standard output

This is also on standard error

But this is on standard output
Everything looks normal until you redirect the output of the program. This is the result when the standard output is redirected to a file called "out.txt". The error messages aren't redirected.
$ stderr > out.txt
This message is on standard error
This is also on standard error

To include the file and line number where an error occurred, use the GNAT.Source_Info.Source_Location function. This function returns a string in the standard GCC error format, suitable for beginning an error message.

C: GNAT.Source_Info.Source_Location is the equivalent of __FILE__:__LINENO__.
with Ada.Text_IO, GNAT.Source_Info;
use  Ada.Text_IO, GNAT.Source_Info;

procedure source_error is
  -- example of GNAT.Source_Info.Source_Location
  i : integer;
  j : integer := 0;
begin
  i := 5/j; -- division by zero
exception when others =>
  put_line( standard_error, Source_Location & ": exception raised" );
end source_error;

$ ./source_error
source_error.adb:10: exception raised
The error message comes from the put_line on the 10th line in source_error.adb. To identify where an exception occurred, you'll need to use the Gnat exception handling packages described in 12.15.  

13.7 Linux Binary Formats

Many people do not think of binary files as having a format because they contain machine code instructions. However, binary files are more than just raw microprocessor instructions. They contain information such as the type of the binary file and a list of all DLL's needed by the program to run.

Linux binary files come in two formats: ELF (Executable and Linking Format) and "a.out". They both have different characteristics and neither is better than the other. Most distributions let you install ELF or a.out compilers. The version of gnat for Linux is compiled for ELF. ELF is current Linux standard primarily because it provides better support for shared libraries.

Fun Fact: "a.out" is an abbreviation for "assember output".

Since the kernel has to do the loading and execution of programs, support for ELF, a.out or both must be selected when the kernel is compiled. Otherwise, the kernel will not recognize the format and will be unable to run the binary file.

13.9 Linux Libraries

A library is a set of object files that have been combined into a single file. You create a Linux library using the  ar  (archive), which takes ".o" object files can compiles them into or out of .a archive file. A Linux library files all start with "lib" and end with ".a". The command parameters for ar are a bit odd: check the man page for more details.

The ar comamand has many options. Two useful variations are ar cr  (create a new archive and add object files to it) and ar t (show a list of all object files in the archive file). See the example below for how these work.

A library which is directly linked into a program, so that it is added to the executable, is called a static library. To link in a static library, just include it's name with the gnatlink command (or gnatmake on simple projects). By default, gnat's own libgnat library is always static, because of concerns over multithreading.

For example, if you install and compile the official JPEG library sources, a static library file is created named libjpeg.a. To link this library into your program, you'd include -ljpeg when linking. Note that you don't use the entire name of the file: gnat assumes there is a beginning "lib" and ending ".a".

One use for static libraries is to create a library for others to use.

As a small example, suppose you have a package p and you want to create a static library to link into a program t. You have to put the library's object file (p.o) into a static library (eg. libp.a) and change p.ali to read-only so gnat knows there is no object file included with p. Then include -lp when making or linking.

armitage:/home/ken/ada/test# ls

p.ads  p.adb  p.ali  p.o  t.adb
armitage:/home/ken/ada/test# ar cr libp.a p.o
armitage:/home/ken/ada/test# rm p.o
armitage:/home/ken/ada/test# chmod -w p.ali
armitage:/home/ken/ada/test# ls
libp.a  p.ads  p.adb  p.ali  t.adb
armitage:/home/ken/ada/test# ar t libp.a
p.o
armitage:/home/ken/ada/test# gnatmake t -lp
(Strickly speaking, -lp should use gnatmake's linker options option, but this works.)

If the library is in a place other than your current directory, you'll need to use the -L option to indicate the directory to check.

A shared library ( or DLL, dynamic link library) is a library that is loaded into memory and shared between programs. It's not actually saved as part of the executable. All Linux shared libraries end in .so.a ("shared object archive??"). They are loaded when a program is executed.

[UNTESTED!] To create a shared library, compile the source code with the  -fPIC (position independent code) option and link using -shared. You also need to include -W1,-soname,nameofobject -- is this necessary under gnat if you use gnatlink? I think it probably is. [see Linux Application programming, pg 72]
 

 
Noteif you forget the —fPIC switch, your shared library will still load, but you won't be able to share the code between applications that use the library.
 
Note-fpic will also work on Intel processors because there is no maximum imposed for the global offset table, but it may not work on other processors: the —fPIC switch is the preferred method.
 
The shared libraries are usually stored in standard Linux directories, like /lib or /usr/lib. Once you copy a shared library into one of these directories, you have to run ldconfig to register the new shared library, otherwise Linux will not load it.

To load a shared library from the current directory instead of the standard Linux directories, use -L. (where period is the current directory).

Shared libraries have the advantage of making executable's smaller, but they are slower to load and execute and use up more memory than static libraries. The also have the advantage of being able to be ugraded separately from your program, provided you don't change the format of any of the subprograms.

Too many shared libraries mean that you have small files scattered throughout the lib directories, making your program harder to maintain.

Generally speaking, only subprograms that will be shared between programs should be shared libraries, and you should combine small, related libraries into one shared library. For example, all the standard C libraries are compiled into one shared library on Linux: libc.so.a.

[from usenet]

makedll.bat

gcc -c beep.adb
gnatbind -n beep.ali
gnatlink beep.ali -o beep.jnk -mdll -Wl,--base-file,beep.base
dlltool --dllname beep.dll --def beep.def --base-file beep.base --output-exp
beep.exp --output-lib libbeep.a
gnatbind -n beep.ali
gnatlink beep.ali -o beep.jnk beep.exp -mdll -Wl,--base-file,beep.base
dlltool --dllname beep.dll --def beep.def  --base-file beep.base --output-exp
beep.exp --output-lib libbeep.a
gnatbind -n beep.ali
gnatlink beep.ali beep.exp -o beep.dll -mdll

This is a def file I am using.

beep.def

EXPORTS

DllGetClassObject=DllGetClassObject@12 @2
DllCanUnloadNow=DllCanUnloadNow@0@3
DllRegisterServer=DllRegisterServer@0@4
DllUnregisterServer=DllUnregisterServer@0@5

 

NOTE: Since the exported functions or STDCALL, I need to provide the number of bytes used for parameters. If you were using standard pragma C stuff it would be:

MYFunction@XXXX

where XXXX is what ordinal in the DLL - BTW you can leave this out all together if you don't need it and just put the name of the function on the line.

[end]

Gnat has an option called -static which will link all shared libraries into your executable as if they were static libraries. This makes your executable completely self-contained, but may violate GPL licensing restrictions on certain libraries.

Gnat comes with one shared library,  libgnat.a. If you link a gnat program without the -static option, you have to copy this file into a standard library directory (e.g. /lib) and run ldconfig so that Linux will be able to find the gnat library when executing your programs. Gnat always automatically links in the library: you never have to type "-lgnat" explicitly when linking.

13.10 Libc5, Libc6 and Upward Compatibility

One of the difficulties with Linux programming is that the standard libraries are seldom upwardly compatible. For example, the ncurses 3 console drawing package is completely incompatible with ncurses 4. Likewise, ncurses 4 is completely incompatible with ncurses 5. In an open source enviroment, subprogram parameters can appear and disappear with each new release.

The Gnat compiler always links the standard C library into your programs. As a result, you have to be aware of the problems with Linux's standard C library, even if your Gnat program doesn't call its subprograms explicitly. Your Gnat executable always connected to the C library it was compiled against.

Like most open source libraries, the standard C library isn't upwardly compatible. Inspite of the fact libc version 5 and libc version 6 (now called glibc 2.0) share the same name, many functions and names have been changed between the two versions. Multithreading under libc5 is done with the linuxthreads library, an implementation of "pthreads". LinuxThreads is based the Posix 1003.1c thread model, with a few extensions. Linuxthreads is a built in part of libc6.

In the Linux world, even minor changes between libraries will create problems. There is very little upward compatibility. For example, a program may run on libc 6.0.x but won't run on libc 6.0.y because some symbol names have changed. Because of this dependency, your programs should be compiled against a specific Linux distribution. Don't assume that if a Red Hat disk and a Slackware disk are published in the same month that they are using exactly the same versions of the C library. By the same token, don't assume you can simply include libc 6.0.y with your program and update the user's version of libc by installing yours overtop. This can cause many programs to crash if they can't find the particular version of libc that they need.

The same is true of the gnat library, libgnat.a. ACT does not guarantee that a program compiled for gnat 3.11's gnat library will run with gnat 3.12's gnat library. In fact, it probably won't.

13.11 Linux Basics

[To be filled in -- KB]  

  <--Last Chapter Table of Contents Next Chapter-->