This is an appendix to Understanding Unix/Linux
Programming
written for people interested in Ada programming on POSIX
systems.
who Is FirstHere is a first draft of who: who1.adb
Compile it like this:
% gnatmake -P who1
Notice that we have switched to using a project
file. We do that to keep the extra command line arguments to
gnatmake in one file.
Run the program, and compare it with the system version of
who:
% ./who1 [...] % who [...] %
Here is the finished version of who2.adb.
Compile and test this version:
% gnatmake -P who2 [...] % ./who2 sparre pts/1 Jan 15 15:28 (:20.0) sparre pts/2 Jan 15 15:29 (:20.0) sparre pts/0 Jan 15 15:35 (:20.0) sparre pts/3 Jan 15 15:34 (:20.0) sparre pts/4 Jan 16 06:51 (:20.0) sparre :20 Jan 13 13:25 % who sparre pts/1 2007-01-15 15:28 (:20.0) sparre pts/2 2007-01-15 15:29 (:20.0) sparre pts/0 2007-01-15 15:35 (:20.0) sparre pts/3 2007-01-15 15:34 (:20.0) sparre pts/4 2007-01-16 06:51 (:20.0) sparre :20 2007-01-13 13:25 %
Finally, here is code that implements the picture: cp1.adb
Compile and test the program:
% gnatmake -P cp1 [...] % ./cp1 cp1 copy.of.cp1 % ls -l cp1 copy.of.cp1 -rw-r--r-- 1 sparre sparre 41061 2007-01-16 09:28 copy.of.cp1 -rwxr-xr-x 1 sparre sparre 41061 2007-01-16 09:25 cp1 %
When we want to read from a file, the most efficient way of accessing
it, is to ask the operating system to pretend that the file is actually a
part of the memory allocated to our program. We do that using one of the
POSIX.Memory_Mapping.Map_Memory functions.
What makes this much more efficient is first of all that we leave the job of copying data to and from disk to the operating system. This is something the operating system controls anyway, so we can just as well leave all the control to it.
The second thing that makes this much more efficient is that we don't have to make as many system calls. The reading of the UTMP data has been cut down to four system calls. In the other versions there are two system calls plus one system call per entry in the UTMP file (here on my laptop this adds up to 34 system calls).
It is only the core of procedure Who we
have to change. The Show procedures are just the same.
We need an object to handle the access to the UTMP file
(UTMP_File, we need to store the size of the file as we map it
into memory (UTMP_File_Size) and we need the address of the
file data in our memory space (UTMP_Memory):
UTMP_File : File_Descriptor; UTMP_File_Size : System.Storage_Elements.Storage_Offset; UTMP_Memory : System.Address;
We open the UTML file, read its size, and ask to have it mapped into our process memory:
begin
UTMP_File := Open (Name => To_POSIX_String (POSIX.UTMP.File_Name),
Mode => Read_Only);
UTMP_File_Size := Storage_Offset (File_Size (UTMP_File));
UTMP_Memory := Map_Memory (Length => UTMP_File_Size,
Protection => Allow_Read,
Mapping => Map_Shared,
File => UTMP_File,
Offset => 0);
Now we pretend to create an array of UTMP records, and locate it at the same place in memory as the UTMP file:
declare
Bit_Count : constant Natural :=
Natural (UTMP_File_Size) * Storage_Element'Size;
Record_Count : constant Natural :=
Bit_Count / POSIX.UTMP.Instance'Size;
Records : array (1 .. Record_Count) of POSIX.UTMP.Instance;
for Records'Address use UTMP_Memory;
Finally we loop through the array, and show the records in it:
begin
for Index in Records'Range loop
Show (Item => Records (Index));
end loop;
end;
Unmap_Memory (First => UTMP_Memory,
Length => UTMP_File_Size);
Close (File => UTMP_File);
end Who;
The complete source code is available as who4.adb. Please build and test it. How does it
compare with the system who program?
Compile and test this version:
% gnatmake -P who4 [...] % ./who4 christin :0 Feb 9 18:21 sparre :20 Feb 9 19:00 sparre pts/1 Feb 21 13:17 (:20.0) sparre pts/0 Feb 21 12:59 (:20.0) sparre pts/2 Feb 21 13:12 (:20.0) sparre pts/7 Feb 21 09:05 (:20.0) sparre pts/8 Feb 21 09:05 (:20.0) sparre pts/6 Feb 21 09:05 (:20.0) % who christina :0 2007-02-09 18:21 sparre :20 2007-02-09 19:00 sparre pts/1 2007-02-21 13:17 (:20.0) sparre pts/0 2007-02-21 12:59 (:20.0) sparre pts/2 2007-02-21 13:12 (:20.0) sparre pts/7 2007-02-21 09:05 (:20.0) sparre pts/8 2007-02-21 09:05 (:20.0) sparre pts/6 2007-02-21 09:05 (:20.0) % % LANG=C who christina :0 Feb 9 18:21 sparre :20 Feb 9 19:00 sparre pts/1 Feb 21 13:17 (:20.0) sparre pts/0 Feb 21 12:59 (:20.0) sparre pts/2 Feb 21 13:12 (:20.0) sparre pts/7 Feb 21 09:05 (:20.0) sparre pts/8 Feb 21 09:05 (:20.0) sparre pts/6 Feb 21 09:05 (:20.0) %
The files who3.adb, utmp_buffer.ads and utmp_buffer.adb are included for direct
comparison with the examples in the book. Please remember that buffered I/O
is generally less efficient than memory mapping of
files.
package POSIX.IO is
[...]
type Position is
(From_Beginning, From_Current_Position, From_End_Of_File);
procedure Seek
(File : in File_Descriptor;
Offset : in IO_Offset;
Result : out IO_Offset;
Starting_Point : in Position := From_Beginning);
[...]
end POSIX.IO;
The procedure POSIX.IO.Seek allows you to set the current
position to locations after the end of the file. [...]
Use who4.adb as a basis for your
who am i implementation.
If you want to implement whoami, you may want to look at
the specifications for package
POSIX.Process_Identification and package
POSIX.User_Database.
Ignore this.
Ignore this.
tail command, page 70Consider using the functions
POSIX.Memory_Mapping.Map_Memory and
Ada.Strings.Fixed.Index for this problem.
Compare the speed of your implementation of tail with that
of the system version.
Go to next chapter.
Understanding Unix/Linux Programming, Bruce Molay,
ISBN 0-13-008396-8.