56
libraries. We look at what a system vendor must provide for a system to be
POSIX-compliant. We demonstrate two different program development problemsโporting
an existing
program to a POSIX-conforming system, and developing a program that is
designed to be POSIX-compliant.
The POSIX Development Environment
POSIX provides portability at the source level. This means that you transport your source
program to the target machine, compile it with the Standard C compiler using conforming
headers, and link it with the standard libraries. The system vendor provides t
he compiler, the
libraries, and headers. Strictly speaking, these are all black boxes and you do not need to know
how they work. However, it is instructive to look into some of these black boxes, and we will
do that in this chapter.
The Standard C Compiler
Each POSIX-conforming system must provide a POSIX-conformance document. This document
describes the behavior of the system for all
implementation-defined
features identified in the
standard. For maximum portability, applications should not depend upon any
particular
behavior that is implementation-specific. The conformance document is dull reading, but it is
valuable because it contains information on how to access the standard C language bindings.
For AT&T UNIX System V Release 4, the Standard C language bindings are accessed by
specifying
-Xc
on the
cc
command line. The command:
cc -Xc subs.c main.c -o prog
will compile
subs.c
and
main.c
and link them together to form
prog.
P
age 14
The Open Software Foundation's OSF/1 operating system comes with the GNU C compiler.
*
The Standard C bindings are accessed by specifying
-ansi
on the cc command line. A
command there looks like:
cc -ansi subs.c main.c -o prog
For other systems, you will have to buy (or at least look at) the conformance document, look
for on-line manual pages, or ask someone.
On most systems, the default is not Standard C but a C compiler that is compatible with the
historic behavior of that system. In many cases, your program will not notice the difference.
The historic behavior probably includes defining symbols that are not
part of Standard C and
POSIX. It is easier to specify strict conformance and clean up small problems as you go than to
deal with a large mess at the end of the project.
Strict ANSI conformance is a good answer to the question: "What can I do to make my
programs more portable?"
POSIX and C Libraries
POSIX defines a library of functions for conforming programs to use. Many of these functions
64
are also defined in the Standard C library.
Each function in the library requires you to include at least one header. This is done with a
statement like:
#include <stdio.h>
The header provides a prototype for the function, plus any necessary types and additional
macros to facilitate using the function.
The POSIX and C standards do not require headers to be source files. They may be some sort
of magic command to the compiler. The standards specify only the net effect of including a
header. On most systems (and all UNIX systems) the headers are files that
live in the directory
/usr/include
.
Many systems support multiple development environments. How do you get the POSIX
headers? You must define the symbol
_POSIX_SOURCE
before including any of the standard
headers. The best way to do this is to place the statement.
**
#define _POSIX_SOURCE 1
at the start of each file.
* The Open Software Foundation ships GNU C to resellers as part of the reference implementation.
The reseller might ship a different compiler with his or her product.
** The standard merely requires that the symbol
_POSIX_SOURCE
be defined. There is no required
value. I prefer to define symbols with values.
P
age 15
You could also place the option
-D_POSIX_SOURCE
on the
cc
command-line; however, this
is error prone. It is better to put the
#define
into your source file along with the rest of your
program. As a rule of thumb, restrict command-line macro definitions to
things that change from
one compile to the next. For example,
-DNDEBUG
turns off the debug test in
assert()
. Use
#define
statements for symbols that must always be defined, such as
_POSIX_SOURCE
.
On some systems the header files you include do not do much. They merely select one of
several possible other headers based on the symbols that you have defined. This might look
like:
#include <common/stdio.h>
#ifdef SYSVSOURCE
#include <sysV/stdio.h>
#endif
#ifdef _BSD_SOURCE
#include <BSD/stdio.h>
#endif
#ifdef _POSIX_SOURCE
#include <POSIX/stdio.h>
#endif
Under most circumstances, you do not need to know how the system defines the correct
symbols. The Header Files section in this book details every POSIX and Standard C header
file. If you follow the rules in this section, the standards guarantee the followi
ng:
53
1.
Every symbol required by the standards will be defined with a meaningful value.
2.
No symbol not permitted by the standards will be defined. This protects your application
from namespace pollution. Of course, if you include a header not specified by the standard,
all bets are off.
Converting Existing Programs
Porting an existing application to run on a new system requires two major steps. These tasks
can range from very easy to almost impossible. First, you have to transport the program to the
target computer. Second, you have to modify the program to run in th
e new environment. The
POSIX standard (and this book) can help you in both steps.
The POSIX standard defines the format of both the
cpio
and
tar
archives. You can create an
archive with the command:
ls
files
| cpio -oc >
archive
or:
tar -cf
archive files
* For example, if the header
<stdio.h>
defined the symbol count, there could be a conflict with
the symbol count in your program.
P
age 16
and load it onto the target with the command:
cpio -ic <
archive
or:
tar -
xvf archive
See your system documentation for the exact details. You will still need some form of
compatible disk, tape, or network to move the archive file to the target.
Once the files are moved, you will have to convert system-specific function calls to calls
defined by the POSIX standard. There are several aids in the reference guide in this book that
are designed to make conversion easier. For every function defined by
either POSIX or
Standard C, there is a conversion entry in the Functions section. This entry points out the
changes that may be required to convert the function from older UNIX systems to ones that
conform to the POSIX standard. The Porting section covers
functions in BSD and System V that
are not in POSIX and suggests ways to build equivalent functions using POSIX calls.
A Porting Example
One day, the boss walks in the door and says, "Here is a program that needs to be ported from
Berkeley UNIX to a Data General AViiON 310. Get it done quickly!"
Now, you could try to find the correct Data General manuals and port the program to the
AViiON, but the next day the boss will want it ported to some other machine. Since you are
76
clever, you decide to port the program to POSIX. It will then run on any POSI
X system.
Let's look at the program:
#include <stdio.h>
#include <sys/time.h>
main(argc,argv)
int argc;
char **argv;
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv,&tz);
printf("The current
time is:\n%s",
ctime(&tv.tv_sec));
if (tz.tz_dsttime)
printf("Daylight savings time\n");
else
printf("Standard time\n");
exit(0);
}
P
age 17
This program prints out the current time in the following format:
The current time is:
Sun Nov 11 18:44:00 1990
Standard time
Now, in the real world you would not be confronted by a program this tiny. It might be easier
to throw the whole thing away and write a new program from scratch.
*
However, we will look
at the process of porting this program.
As a first test, we can compile the program and see if it works. We may have a very portable
program. At least, we will get a hint at what must be fixed.
This program will not compile because there is no
<sys/time.h>header
. This can be
solved by deleting the
#include
statement. This will get us past that compiler error and
point out any remaining compatibility problems.
**
Next the compiler points out that there is no definition for
struct timeval
or
struct
timezone
. These seem to be used by the
gettimeofday()
function. A quick check of the
Functions section of this book reveals that there is no
gettimeofday()
function in PO
SIX.
However, it looks like
gettimeofday()
returns something that can be used as an argument
to
ctime()
. It also seems to return a daylight savings indication.
If we look up
ctime()
in the Functions section, it tells us:
1. The
#include<sys/time.h>
must be changed to
#include<time.h>
. (We
already knew that there was no
<sys/time.h>
!)
2.
ctime()
is equivalent to
asctime(localtime(timer)
).
77
This gives a good indication of what must be done to convert the program. The description of
localtime()
in the Functions section states:
The
localtime()
function converts a
time_t
pointed to by
timer
into year, month, day, hours,
minutes, seconds, etc., and stores the information in a
struct tm
. A pointer to the
struct tm
is
returned. The current time can be obtained with the
time()
functio
n.
* The option of throwing the existing program away and starting from scratch should not be ignored
even in much larger projects.
** In fact, many systems provide a BSD compatibility package. If we tried to run this program there is
a good chance it would work correctly without any changes. For the purposes of illustration, we will
ignore that possibility.
P
age 18
We can replace
gettimeofday()
with
localtime()
. A quick check of the
tm
structure
in the Data Structures section reveals that it contains a flag,
tm_isdst
, to indicate daylight
savings time. Our program now looks like:
#define _POSIXSOURCE 1
#include <stdio.h>
#include <time.h>
main(argc,argv)
int argc;
char **argv;
{
struct tm *tmptr;
time_t timer;
timer = time(NULL);
tmptr = localtime
(&timer);
printf("The current time is:\n%s",
ctime(&timer));
if (tmptr -> tm_isdst)
printf("Daylight savings time\n");
else
printf("Standard time\n");
exit(0);
}
This program will work and can be considered ''ported." There are a couple of things that we
should do to make sure that the program is 100% standards-conforming. First, we should check
that we have included all of the required headers. The
exit()
function
requires that we
include the
<stdlib.h>header
. While we are looking at
exit()
we should change the
0
to
EXIT_SUCCESS
. This change is not required for correct operation on POSIX systems. As
an act of kindness to those who will look at the program after we
are done with it, we will add
some comments (ensuring portability from one programmer to another!).
The final maximally portable program is shown in Example 2-1:
EXAMPLE 2-1. daytime.c
52
/* Define _POSIXSOURCE to indicate
* that this is a POSIX program
*/
#define _POSIX SOURCE 1
/* System Headers */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
main ( argc,argv)
int argc; /* Argument count -- unused */
char **argv; /* Argument list -- unused */
{
P
age 19
struct tm *tmptr; /* Pointer to date and time
* broken down by component.
* The only member used is
* tmdst
*/
time_t timer; /* Number
of seconds since
* January 1, 1970.
*/
timer = time(NULL); /* Get current time */
tmptr = localtime(&timer); /* Break it down */
printf("The current time is:\n
%s",
ctime(&timer));
if (tmptr -> tmisdst) /* tm_isdst is non-zero
* if daylight savings
* is in effect
*/
printf("Daylight savings time\n");
else
printf("Standard time\n");
exit(EXIT_SUCCESS); /* Return to system */
}
We can now tell the boss, "I ported the program to AViiON, and to ULTRIX, and to System
V.4. I even ported it to VAX/VMS. About that raise . . ."
An Alternate Approach
The previous example ported a program from an old system to one that supports the POSIX and
C standards. The new program is conforming but may no longer run on the old system. Of
course, we still have the old version for that system. If we are going to con
tinue to fix bugs and
enhance the old version, we will have two source bases to deal with. We can try to get around
that problem by using
#ifdefs
as in:
#ifdef BSD
struct timeval tv;
struct timezone tz;
#endif
73
#ifndef BSD
struct tm *tmptr; /* Pointer to date and time
* broken down by component.
* The only member used is
* tm_dst
*/
timet timer; /* Number of seconds since
* January 1, 1970.
*/
#endif
After a few ports this gets very ugly and hard to read.
Another scheme is to build BSD compatible functions out of POSIX functions. For programs
like this, the emulation does not have to be perfect or complete. You need to supply only the
specific things required by the application you are porting.
P
age 20
Of course, after a while you may have a large set of compatibility functions to support. User
frustration with the complexity of supporting a large number of ports was a major driving force
behind POSIX.
Standard Header Files
To write a POSIX program you must specify in your source code that you want it to be
POSIX-compliant (using
#define _POSIX_SOURCE
) and then use the library functions
that are defined by POSIX. You can become familiar with them by reading the remaining
chap
ters and using the reference section. The Header File section lists all of the standard
headers and the symbols that they define. This list merely hits the highlights so that you will
know what headers are available:
Header File
Function
<assert. h>
Defines the
assert()
macro. This is used to check for bugs.
<ctype. h>
Defines the character-testing functions such as
isdigit()
and
isupper().
<dirent. h>
Defines the contents of directory entries and the functions that
re
ad them.
<errno. h>
Defines all of the error codes.
<fcntl. h>
Defines symbols used by the file control functions
creat()
,
open()
, and
fcntl()
<float .h>
Defines a set of symbols used for floating-point processing.
<grp. h>
Defines the functions that read
the group database.
<limits. h>
Defines a set of implementation limits. This includes both hardware limits like
INT_MAX
and software limits like
NGROUPS_MAX
.
<locale.h>
Defines symbols for use in multi-national applications.
<math.h>
Defines standard math
functions such as
sin()
and
sqrt()
.
94
<pwd.h>
Defines the functions that read the user database. This is called
<pwd.h>
because the
user database file has historically been called
/etc/passwd
.
<setjmp.h>
Defines the
C
setjmp()/longjmp()
macros. The POSIX ex
tensions
sigsetjmp()
and
siglongjmp()
are also defined here.
<signal.h>
Defines the symbols and functions used by signals.
<stdarg. h>
Defines macros to support functions with a variable number of parameters.
P
age 21
Header File
Function
<stddef.h>
Defines
NULL, size_t
, and a few other popular symbols.
<stdio.h>
Defines the standard I/O library.
<stdlib.h>
Defines functions that historically did not require a header. These include
exit(),
malloc(), free()
, and many oth
ers.
<string.h>
Defines the string functions
strcat(), strlen()
,
strspn()
, etc.
<sys/stat.h>
Defines the
stat
structure and file manipulation functions such as
chmod()
<sys/times.h>
Defines the
times()
function and the structure it uses.
<sys/types.h>
Defi
nes the POSIX datatypes
dev_t,gid_t, ino_t
, etc.
<sys/utsname.h>
Defines the
uname()
function and the structure it uses.
<sys/wait.h>
Defines the
wait()
and
waitpid()
functions.
<termios.h>
Defines many symbols used to manipulate terminals.
<time.h>
Define
s the time-of-day functions.
<unistd.h>
Defines a large number of POSIX symbols. This header also defines all of the UNIX
functions which historically have not required a header. These include
chdir(),
close()
,
fork(), pipe()
, and so on.
<utime.h>
Defines
the
utime()
function and the structure it uses.
Section 4.1.2 of the C Standard states, ''A header is not necessarily a source file, nor are the <
and > delimited sequences in header names necessarily valid source file names." That is, the
compiler is free to define the symbols using any method that it
wants. A POSIX system may not
have any headers that you can look at. Having said that, let's look at a typical header file. A
sample
<utime.h>
is given in Example 2-2:
EXAMPLE 2-2. utime.h header file
58
#ifndef _UTIME_
#define _UTIME_
struct utimbuf
{
timet actime; /* access time */
timet modtime; /* modification time */
};
#ifdef __STDC__
int utime(const char *pa
th,
const struct utimbuf *times);
P
age 22
#else
extern int utime();
#endif /* __STDC__ */
#endif /* _UTIME_ */
This is a very simple header file but it still has many interesting points.
The header is wrapped with an
#ifndef _UTIME_
. This means that the header can be
included any number of times without causing any errors. The symbol
_UTIME_
is reserved
for the people who write system header files. All symbols that begin with an underscore
followed by either another underscore or an upper-case letter are for system headers. You
should not use them in your code.
The header then declares
struct utimebuf
, which is the main job of the header.
Lastly, if the header is being used by a Standard C compiler, the
utime()
function is
declared. If a compiler supports Standard C, the symbol
__STDC__
is defined by the
compiler to have the value 1. Some compilers define the symbol
__STDC__
to have a value
other than 1 to indicate "sort of standards-conforming."
Now let's look at
<sys/types.h>
which is slightly more complex:
EXAMPLE 2-3. sys/types.h header file
#ifndef _TYPES_
#define _TYPES_
#if (__STDC__ != 1) I defined(_IN_KERNEL)
/*
* Machine specific system types
*/
typedef struct{int r[1];} *physadr;
typedef unsigned short iord_t;
typedef int label_t[13];
typedef unsigned short pgadr_t;
typedef char swck_t;
typedef unsigned char use_t;
#define MAXSUSE 255
/*
61
* Machine independ
ent system parameters
*/
typedef long daddr_t;
typedef char *caddr_t;
typedef unsigned char uchar_t;
typedef unsigned char u_char;
typedef unsigned short u_
short;
typedef unsigned int u_int;
typedef unsigned long u_long;
typedef unsigned char unchar;
typedef unsigned int uint;
typedef unsigned short ushort;
typedef unsigned lo
ng ulong;
typedef ulong ino_tl;
typedef short cnt_t;
typedef ong ubadr_t;
P
age 23
#endif /* (__STDC__ != 1) || defined(_IN_KERNEL) */
#if (__STDC__ != 1) ( defined(_POSIX_SOURCE) ||
defined(_SYSV_SOURCE)
typedef unsigned long clock_t;
typedef unsigned long dev_t;
typedef unsigned lo
ng gid_t;
typedef unsigned long ino_t;
typedef unsigned long mode_t;
typedef unsigned long nlink_t;
typedef long off_t;
typedef long pid_t;
typedef
unsigned long size_t;
typedef long ssize_t;
typedef unsigned long uid_t;
#endif /* (__STDC__ != 1) || defined(_POSIX_SOURCE) */
#if (__STDC__ != 1) || defined(_SYSV_SOURCE)
typedef unsign
ed char uchar_t;
typedef unsigned short ushort_t;
typedef unsigned int uint_t;
typedef unsigned long ulong_t;
typedef char * addr_t;
typedef char * caddr_t;
typedef long daddr_t;
typedef short cnt_t;
typedef ulong_t paddr_t;
typedef short sysid_t;
typedef short index_t;
typedef short
lock_t;
typedef long id_t;
typedef short o_devt;
typedef unsigned short o_gid_t;
typedef unsigned short o_ino_t;
typedef unsigned short o_mode_t;
typedef
short o_nlink_t;
typedef short opid_t;
Documents you may be interested
Documents you may be interested