6 pREPC+ ANSI C Library


6.1 Introduction

Most C compilers are delivered with some sort of run-time library. These run-time libraries contain a collection of pre-defined functions that can be called from your application program. They are linked with the code you develop when you build your application. However, when you attempt to use these libraries in a real-time embedded system, they encounter one or more of the following problems:

The pREPC+ ANSI C Library solves all of the above problems. First, it is designed to work with the pSOS+ Real-Time Multitasking Kernel and the pHILE+ file system manager, so all operating system dependent issues have been addressed and resolved. Second, it is designed to operate in a multitasking environment, and finally, it complies with the C Standard Library specified by the American National Standards Institute.

6.2 Functions Summary

The pREPC+ library provides more than 115 run-time functions. Following the conventions used in the ANSI X3J11 standard, these functions can be separated into 4 categories:

The Character Handling Functions provide facilities for testing characters (for example, is a character a digit?) and mapping characters (for example, convert an ASCII character from lowercase to uppercase).

The String Handling Functions perform operations on strings. With these functions you can copy one string to another string, append one string to another string, compare two strings, and search a string for a substring.

The General Utilities provide a variety of miscellaneous functions including allocating and deallocating memory, converting strings to numbers, searching and sorting arrays, and generating random numbers.

I/O is the largest and most complex area of support. The I/O Functions include character, direct, and formatted I/O functions. I/O is discussed in Section 6.3, ``I/O Overview.''

Detailed descriptions of each function are provided in pSOSystem System Calls.

NOTE: The pREPC+ ANSI C library opens all files in binary mode regardless of the mode parameter passed to the fopen() call. This includes text files on MS-DOS file systems.

6.3 I/O Overview

There are several different levels of I/O supported by the pREPC+/pSOS+/pHILE+ environment, providing different amounts of buffering, formatting, and so forth. This results in a layered approach to I/O, because the higher levels call the lower levels. The main levels are shown in Figure 6-1.

Figure 6-1 . I/O Structure of the pREPC+ Library

The pREPC+ I/O functions provide a uniform method for handling all types of I/O. They mask the underlying layers and allow application programs to be hardware and device independent. A user application can, however, call any of the layers directly, depending on its requirements.

The lowest, most primitive way of doing I/O is by directly accessing the hardware device involved, for example a serial channel or a disk controller. Programming at this level involves detailed knowledge of the device's control registers, etc. Although all I/O eventually reaches this level, it is almost never part of the application program, as it is too machine-dependent.

The next step up from the actual device is to call a device driver. Under the pSOS+ kernel, all device drivers are called in a similar fashion, via the pSOS+ I/O Supervisor, which is explained in Chapter 7, ``I/O System.'' For reading and writing data, all that is generally required is a pointer to the buffer to read into or write from, a byte count, and a way to identify the device being used.

The pSOS+ I/O Supervisor provides the fastest, most direct route for getting a piece of data to a device. In some cases, this is the best way. Generally, however, it is better to use the pREPC+ direct, character, or formatted I/O services.

The pHILE+ file system manager manages and organizes data as sets of files on storage devices and in turn does all of the actual I/O. The pHILE+ I/O path depends on the type of volume mounted and is described in detail in Chapter 5, ``pHILE+ File System Manager.''

pHILE+ services (such as open_f and write_f) can be called directly. However, if you use the pREPC+ file I/O functions, which in turn call the pHILE+ file system manager, your application code will be more portable.

The pREPC+ direct I/O and character I/O functions read and write sequences of characters. The formatted I/O functions perform transformations on the input and output and include the familiar printf() and scanf() functions.

6.3.1 Files, Disk Files, and I/O Devices

Under the pREPC+ library, all I/O is directed to and from ``files.'' The pREPC+ library divides files into two categories: I/O devices and disk files. They are treated as similarly as possible, but there are intrinsic differences between the two.

Disk files are part of a true file system managed by the pHILE+ file system manager. There is a file position indicator associated with each disk file, which marks the current location within the file. It is advanced whenever data is read from or written to the file. In addition, it can be changed via system calls.

The pHILE+ file system manager manages four types of volumes. These are pHILE+ formatted volumes, CD-ROM volumes, MS-DOS volumes, and NFS (Network File System) volumes. The pREPC+ library does not distinguish between the underlying volume types and therefore works equally well with all four volume types. However, there are a number of small differences between the various volumes that may affect the results of certain pREPC+ functions. Function descriptions indicate those cases where the volume type may affect function results and how those functions would be affected.

I/O devices correspond to pSOS+ logical devices, and are usually associated with devices such as terminals or printers. From an application's standpoint, their main difference from disk files is that they have no position indicator. Data being read from or written to an I/O device can be thought of as a continuous stream.

When reading and writing disk files, the pREPC+ library calls the pHILE+ file system manager, which in turn calls the pSOS+ I/O Supervisor. When reading and writing I/O devices, the pREPC+ library calls the pSOS+ I/O Supervisor directly.

Before a file (a disk file or an I/O device) can be read or written, it must be opened using fopen(). One of the fopen() function's input parameters is a name that specifies the file to open. Disk files are designated by pHILE+ pathnames, while I/O devices are identified by pSOS+ logical device numbers.

Examples:

3.2 designates an I/O device with logical device number 3.2.

3.2/abcd designates a disk file stored on logical device 3.2.

abcd designates a disk file in the current directory.

When fopen() opens a disk file, it generates a pHILE+ open_f() system call. When it opens an I/O device, fopen() calls the pSOS+ de_open() service. Regardless of whether fopen() opens an I/O device or a disk file, it allocates a FILE data structure, which is discussed in section 6.3.2.

6.3.2 File Data Structure

As mentioned in the previous section, when a file is opened, it is allocated a data structure of type FILE. In the pREPC+ library this is a 32-bit address of a pREPC+ file structure. fopen() returns a pointer to this allocated data structure. All file operations require the pointer to this structure as an input parameter to identify the file. If it is not explicitly given, it is implied, as in the case of functions which always use the standard input or output device (See section 6.3.4).

The FILE data structure is used to store control information for the open file. Some of the more important members of this structure include the address of the file's buffer, the current position in the file, an end-of file (EOF) flag, and an error flag. In addition, there is a flag that indicates whether the file is a disk file or an I/O device.

Some of these fields have no meaning for I/O devices, such as the position indicator.

6.3.3 Buffers

Open files normally have an associated buffer that is used to buffer the flow of data between the user application and the device. By caching data in the buffer, the pREPC+ library avoids excessive I/O activity when the application is reading or writing small data units.

When first opened, a file has no buffer. Normally a buffer is automatically assigned to the file the first time it is read or written. The buffer size is defined by the entry LC_BUFSIZ in the pREPC+ Configuration Table. The pREPC+ component allocates the buffer from pSOS+ region 0. If memory is not available, the calling task may block based on the values in the pREPC+ configuration table entries LC_WAITOPT and LC_TIMEOPT. If a buffer cannot be obtained an error is returned to the read or write operation.

Note that if the default buffer assigned by the pREPC+ library is not appropriate for a particular file, a buffer may be supplied directly by calling the setbuf() or setvbuf() functions.

A special case arises when a file is assigned a buffer of length 0. This occurs if LC_BUFSIZ is zero, and as an option to the setvbuf() call. In this case, no buffer is assigned to the file and all I/O is unbuffered. That is, every read or write operation through the pREPC+ library will result in a call to a pHILE+ device driver as the case may be.

Finally, note that the three standard files, stdin, stdout, and stderr, are not affected by the value of LC_BUFSIZ. See section 6.3.5 for a discussion of the default buffering of these three files.

6.3.4 Buffering Techniques

This section describes the buffering techniques used by the pREPC+ library. There are two cases to consider, writing and reading.

On output, data is sent to the file's buffer and subsequently transferred (or "flushed") to the I/O device or disk file by calling a pSOS+ device driver (for an I/O device) or the pHILE+ file system manager (for a disk file). The time at which a buffer is flushed depends on whether the file is line-buffered or fully-buffered. If line-buffered, the buffer is flushed when either the buffer is full or a new line character is detected. If fully-buffered, the buffer is flushed only when it is full. In addition, data can be manually flushed, or forced, from a buffer at any time by calling the fflush() function.

By default, I/O devices are line-buffered, whereas disk files are fully-buffered. This can be changed after a file is opened by using the setbuf() or setvbuf() functions.

When reading, the pREPC+ library retrieves data from a file's buffer. When attempting to read from an empty buffer, the pREPC+ library calls either a pSOS+ driver or the pHILE+ file system manager to replenish its contents. When attempting to replenish its internal buffer, the pREPC+ library reads sufficient characters to fill the buffer. The pSOS+ driver or the pHILE+ file system manager may return fewer characters than requested. This is not necessarily considered as an error condition. If zero characters are returned, the pREPC+ library treats this as an EOF condition.

Note that the buffering provided by the pREPC+ library adds a layer of buffering on top of the buffering implemented by the pHILE+ file system manager.

6.3.5 stdin, stdout, stderr

Three files are opened automatically for every task that calls the pREPC+ library. They are referred to as the standard input device (stdin), the standard output device (stdout) and the standard error device (stderr). They can be disk files or I/O devices and are defined by entries in the pREPC+ Configuration Table. stdin, stdout and stderr are implicitly referenced by certain input/output functions. For example, printf() always writes to stdout, and scanf() always reads from stdin.

stdout and stderr are opened in mode w, while stdin is opened in mode r. Modes are discussed in the fopen() description given in the system calls reference. Each file is assigned a 256 byte buffer. LC_BUFSIZ has no effect on the buffer size of these three files.

The buffering characteristics for stdin and stdout depend on the type of files specified for these devices. In the case of an I/O device, they are line-buffered. For a disk file, they are fully-buffered. stderr is an exception. Regardless of whether stderr is attached to a disk file or an I/O device, it is fully-buffered.

Like any other file, the buffer size and buffering technique of these files can be modified with the setbuf() and setvbuf() function calls.

The pREPC+ library attempts to open stdin, stdout and stderr for a task the first time the task issues a pREPC+ system call. If any of these files cannot be opened, the pREPC+ library calls the k_fatal service with a 0x3F03 error code as an input parameter.

When opened, the pathname of the files is obtained from the pREPC+ configuration table. Even though each task maintains a separate file structure for each of the three standard files, they all use the same stdin, stdout, and stderr device or file. This may not be desirable in your application. The freopen() function can be used to dynamically change the pathnames of any file, including stdin, stdout, and stderr, in your system. For example, to change the stdout from its default value of I/O device 1.00 to a disk file (2.00/std_out.dat) you would use the following function:

freopen("2.00/std_out.dat", "w", stdout);


When using freopen with the three standard files, two rules should be observed. First, the mode of the standard files should not be altered from their default values, and second, you should not use pathnames that include the strings ``stdin'', ``stdout'', or ``stderr''.

6.3.6 Streams

Streams is a notion introduced by the X3J11 Committee. Using the X3J11 Committee's terminology, a stream is a source or destination of data that is associated with a file. The Standard defines two types of streams: text streams and binary streams. In the pREPC+ library, these are identical. In fact, in the pREPC+ library, a stream is identical to a file. Therefore, the terms file and stream have been used interchangeably in the manual.

6.4 Memory Allocation

The following pREPC+ functions allocate blocks of memory:

calloc()
malloc()
realloc()


When any of these functions are called, the pREPC+ library, in turn, calls the pSOS+ region manager by generating a rn_getseg call. The pREPC+ library always requests segments from Region 0. Therefore, you must reserve enough space in Region 0 for the memory required by your application and for the memory used by the pREPC+ library for file buffers (see section 6.3.3).

The rn_getseg call's input parameters include wait/nowait and timeout options. The wait/nowait and timeout options used by the pREPC+ library when calling rn_getseg are specified in the pREPC+ Configuration Table. Note that if the wait option is selected, it is possible for any of the functions listed above to result in blocking the caller. Also note that the number of bytes actually allocated by each rn_getseg call depends on Region 0's unit_size.

The following functions result in memory deallocation:

free()
realloc()
fclose()
setbuf()
setvbuf()


The free() function is called by a user for returning memory no longer needed. The remaining functions implicitly cause memory to be released. The pREPC+ library deallocates memory by generating a rn_retseg call to the pSOS+ kernel.

Chapter 2, ``pSOS+ Real-Time Kernel,'' contains a complete discussion of the pSOS+ region memory manager.

6.5 Error Handling

Most pREPC+ functions can generate error conditions. In most such cases, the pREPC+ library stores an error code into an internal variable maintained by pSOS+ called errno and returns an "error indicator" to the calling task. Usually this error indicator takes the form of a negative return value. The error indicator for each function, if any, is documented in the individual function calls. Error codes are described in detail in the error codes reference.

The pREPC+ library maintains a separate copy of errno for each task. Thus, an error occurring in one task will have no effect on the errno of another task. A task's errno value is initially zero. When an error indication is returned from a pREPC+ call, the calling task can obtain the errno value by referencing the macro errno. This macro is defined in the include file <errno.h>. Note that once the task has been created, the value of errno is never reset to zero unless explicitly set by the application code.

The pREPC+ library also maintains two error flags for each opened file. They are called the end-of-file flag and the error flag. These flags are set and cleared by a number of the I/O functions. They can be tested by calling the feof() and ferror() functions, respectively. These flags can be manually cleared by calling the clearerr() function.

6.6 Restarting Tasks That Use the pREPC+ Library

It is possible to restart a task that uses the pREPC+ library. Because the pREPC+ library can execute with preemption enabled, it is possible to issue a restart to a task while it is in pREPC+ code. Note that the t_restart operation does not release any memory, close any files, or reset errno to zero. If you wish to have clean_ups, then have the task check for restarts and do them as it begins execution again.

NOTE: Restarting a task using pREPC+ that is using pHILE+ (that is, has a disk file open) may leave the disk volume in an inconsistent state. See Section 5.8.1.1 on page 5-46.

6.7 Deleting Tasks That Use the pREPC+ Library

To avoid permanent loss of pREPC+ resources, the pSOS+ kernel does not allow deletion of a task which is holding any pREPC+ resource. Instead, delete returns error code ERR_DELLC which indicates the task to be deleted holds pREPC+ resources.

The exact conditions under which the pREPC+ library holds resources are complex. In general, any task that has made a pREPC+ service call may hold pREPC+ resources. fclose(0), which returns all pREPC+ resources held by the calling task, should be called by the task to be deleted prior to calling t_delete.

pNA+ and pHILE+ components also hold resources that must be returned before a task can be deleted. These resources are returned by calling close(0) and close_f(0) respectively. Because the pREPC+ library calls the pHILE+ file system manager, and the pREPC+ library calls the pNA+ component (if NFS is in use), these services must be called in the correct order. Below is a sample code fragment which a task can use to delete itself:

fclose(0));     /* close pREPC+ files */
close_f(0);     /* return pHILE+ resources */
close(0);  /* return pNA+ resources */
free((void *) -1);   /* return pREPC+ resources */
t_delete(0);       /* and commit suicide */


Obviously, close calls to components not in use should be omitted.

Because only the task to be deleted can make the necessary close calls, the simplest way to delete a task is to restart the task and pass arguments requesting self deletion. Of course, the task being deleted must contain code to handle this condition.

6.8 Deleting Tasks With exit( ) or abort( )

The exit() and abort() calls are implemented in the pREPC+ library as macros that are defined in the header file prepc.h. These macros, which the user needs to modify depending on which components are present in the system, can be used to return all system resources and delete the task.





psos_support@isi.com

Copyright © 1996, Integrated Systems, Inc. All rights reserved.