<--Last Chapter | Table of Contents | Next Chapter--> |
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.
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
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 psIn 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 lfwHere, 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 psBelow 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.
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.
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:
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:
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; }
It 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. |
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:
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.
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
|
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.
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.
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.
Devices are recognized by their names:
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.
The contents of these files are described in the proc man page.
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;
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 outputEverything 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 raisedThe 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.
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.
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
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]
if 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. |
-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. |
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
This is a def file I am using.
EXPORTS
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.
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.
<--Last Chapter | Table of Contents | Next Chapter--> |