3.3   Basic I/O

Basic I/O is the lowest level of I/O in VxWorks. The basic I/O interface is source-compatible with the I/O primitives in the standard C library. There are seven basic I/O calls, shown in the following table.

Table 3-1:  Basic I/O Routines


Call
Description

creat( )  
Create a file. 
remove( )  
Remove a file. 
open( )  
Open a file. (Optionally, create a file.)  
close( )  
Close a file. 
read( )  
Read a previously created or opened file. 
write( )  
Write a previously created or opened file. 
ioctl( )  
Perform special control functions on files or devices. 

3.3.1   File Descriptors

At the basic I/O level, files are referred to by a file descriptor, or fd. An fd is a small integer returned by a call to open( ) or creat( ). The other basic I/O calls take an fd as a parameter to specify the intended file. An fd has no meaning discernible to the user; it is only a handle for the I/O system.

When a file is opened, an fd is allocated and returned. When the file is closed, the fd is deallocated. There are a finite number of fds available in VxWorks. To avoid exceeding the system limit, it is important to close fds that are no longer in use. The number of available fds is specified in the initialization of the I/O system.

3.3.2   Standard Input, Standard Output, and Standard Error

Three file descriptors are reserved and have special meanings:

0 = standard input
1 = standard output
2 = standard error output

These fds are never returned as the result of an open( ) or creat( ), but serve rather as indirect references that can be redirected to any other open fd.

These standard fds are used to make tasks and modules independent of their actual I/O assignments. If a module sends its output to standard output (fd = 1), then its output can be redirected to any file or device, without altering the module.

VxWorks allows two levels of redirection. First, there is a global assignment of the three standard fds. Second, individual tasks can override the global assignment of these fds with their own assignments that apply only to that task.

Global Redirection

When VxWorks is initialized, the global assignments of the standard fds are directed, by default, to the system console. When tasks are spawned, they initially have no task-specific fd assignments; instead, they use the global assignments.

The global assignments can be redirected using ioGlobalStdSet( ). The parameters to this routine are the global standard fd to be redirected, and the fd to direct it to.

For example, the following call sets global standard output (fd = 1) to be the open file with a file descriptor of fileFd:

ioGlobalStdSet (1, fileFd);

All tasks in the system that do not have their own task-specific redirection write standard output to that file thereafter. For example, the task tRlogind calls ioGlobalStdSet( ) to redirect I/O across the network during an rlogin session.

Task-Specific Redirection

The assignments for a specific task can be redirected using the routine ioTaskStdSet( ). The parameters to this routine are the task ID (0 = self) of the task with the assignments to be redirected, the standard fd to be redirected, and the fd to direct it to. For example, a task can make the following call to write standard output to fileFd:

ioTaskStdSet (0, 1, fileFd);

All other tasks are unaffected by this redirection, and subsequent global redirections of standard output do not affect this task.

3.3.3   Open and Close

Before I/O can be performed to a device, an fd must be opened to that device by invoking the open( ) routine (or creat( ), as discussed in the next section). The arguments to open( ) are the file name, the type of access, and, when necessary, the mode:

 fd = open ("name", flags, mode);

The possible access flags are shown in Table 3-2.

Table 3-2:  File Access Flags


Flag
Hex Value
Description

O_RDONLY  
Open for reading only. 
O_WRONLY  
Open for writing only. 
O_RDWR  
Open for reading and writing. 
O_CREAT  
200 
Create a new file. 
O_TRUNC  
400 
Truncate the file. 

The mode parameter is used in the following special cases to specify the mode (permission bits) of a file or to create subdirectories:

fd = open ("name", O_CREAT | O_RDWR, 0644);

The open( ) routine, if successful, returns an fd (a small integer). This fd is then used in subsequent I/O calls to specify that file. The fd is a global identifier that is not task specific. One task can open a file, and then any other tasks can use the resulting fd (for example, pipes). The fd remains valid until close( ) is invoked with that fd:

close (fd);

At that point, I/O to the file is flushed (completely written out) and the fd can no longer be used by any task. However, the same fd number can again be assigned by the I/O system in any subsequent open( ).

When a task exits or is deleted, the files opened by that task are not automatically closed, because fds are not task specific. Thus, it is recommended that tasks explicitly close all files when they are no longer required. As stated previously, there is a limit to the number of files that can be open at one time.

3.3.4   Create and Remove

File-oriented devices must be able to create and remove files as well as open existing files. The creat( ) routine directs a file-oriented device to make a new file on the device and return a file descriptor for it. The arguments to creat( ) are similar to those of open( ) except that the file name specifies the name of the new file rather than an existing one; the creat( ) routine returns an fd identifying the new file.

fd = creat ("name", flag);

The remove( ) routine removes a named file on a file-oriented device:

remove ("name");

Do not remove files while they are open.

With non-file-system oriented device names, creat( ) acts exactly like open( ); however, remove( ) has no effect.

3.3.5   Read and Write

After an fd is obtained by invoking open( ) or creat( ), tasks can read bytes from a file with read( ) and write bytes to a file with write( ). The arguments to read( ) are the fd, the address of the buffer to receive input, and the maximum number of bytes to read:

nBytes = read (fd, &buffer, maxBytes);

The read( ) routine waits for input to be available from the specified file, and returns the number of bytes actually read. For file-system devices, if the number of bytes read is less than the number requested, a subsequent read( ) returns 0 (zero), indicating end-of-file. For non-file-system devices, the number of bytes read can be less than the number requested even if more bytes are available; a subsequent read( ) may or may not return 0. In the case of serial devices and TCP sockets, repeated calls to read( ) are sometimes necessary to read a specific number of bytes. (See the reference entry for fioRead( ) in fioLib). A return value of ERROR (-1) indicates an unsuccessful read.

The arguments to write( ) are the fd, the address of the buffer that contains the data to be output, and the number of bytes to be written:

actualBytes = write (fd, &buffer, nBytes);

The write( ) routine ensures that all specified data is at least queued for output before returning to the caller, though the data may not yet have been written to the device (this is driver dependent). write( ) returns the number of bytes written; if the number returned is not equal to the number requested, an error has occurred.

3.3.6   File Truncation

It is sometimes convenient to discard part of the data in a file. After a file is open for writing, you can use the ftruncate( ) routine to truncate a file to a specified size. Its arguments are an fd and the desired length of the file:

status = ftruncate (fd, length);

If it succeeds in truncating the file, ftruncate( )returns OK. If the size specified is larger than the actual size of the file, or if the fd refers to a device that cannot be truncated, ftruncate( )returns ERROR, and sets errno to EINVAL.

The ftruncate( ) routine is part of the POSIX 1003.1b standard, but this implementation is only partially POSIX-compliant: creation and modification times are not updated. This call is supported only by dosFsLib, the DOS-compatible file system library.

3.3.7   I/O Control

The ioctl( ) routine is an open-ended mechanism for performing any I/O functions that do not fit the other basic I/O calls. Examples include determining how many bytes are currently available for input, setting device-specific options, obtaining information about a file system, and positioning random-access files to specific byte positions. The arguments to the ioctl( ) routine are the fd, a code that identifies the control function requested, and an optional function-dependent argument:

result = ioctl (fd, function, arg);

For example, the following call uses the FIOBAUDRATE function to set the baud rate of a tty device to 9600:

status = ioctl (fd, FIOBAUDRATE, 9600);

The discussion of specific devices in 3.7 Devices in VxWorks summarizes the ioctl( ) functions available for each device. The ioctl( ) control codes are defined in ioLib.h. For more information, see the reference entries for specific device drivers.

3.3.8   Pending on Multiple File Descriptors: The Select Facility

The VxWorks select facility provides a UNIX- and Windows-compatible method for pending on multiple file descriptors. The library selectLib provides both task-level support, allowing tasks to wait for multiple devices to become active, and device driver support, giving drivers the ability to detect tasks that are pended while waiting for I/O on the device. To use this facility, the header file selectLib.h must be included in your application code.

Task-level support not only gives tasks the ability to simultaneously wait for I/O on multiple devices, but it also allows tasks to specify the maximum time to wait for I/O to become available. For an example of using the select facility to pend on multiple file descriptors, consider a client-server model in which the server is servicing both local and remote clients. The server task uses a pipe to communicate with local clients and a socket to communicate with remote clients. The server task must respond to clients as quickly as possible. If the server blocks waiting for a request on only one of the communication streams, it cannot service requests that come in on the other stream until it gets a request on the first stream. For example, if the server blocks waiting for a request to arrive in the socket, it cannot service requests that arrive in the pipe until a request arrives in the socket to unblock it. This can delay local tasks waiting to get their requests serviced. The select facility solves this problem by giving the server task the ability to monitor both the socket and the pipe and service requests as they come in, regardless of the communication stream used.

Tasks can block until data becomes available or the device is ready for writing. The select( ) routine returns when one or more file descriptors are ready or a timeout has occurred. Using the select( ) routine, a task specifies the file descriptors on which to wait for activity. Bit fields are used in the select( ) call to specify the read and write file descriptors of interest. When select( ) returns, the bit fields are modified to reflect the file descriptors that have become available. The macros for building and manipulating these bit fields are listed in Table 3-3.

Table 3-3:  Select Macros


Macro
Function

FD_ZERO  
Zeros all bits. 
FD_SET  
Sets bit corresponding to a specified file descriptor. 
FD_CLR  
Clears a specified bit. 
FD_ISSET  
Returns 1 if specified bit is set, otherwise returns 0. 

Applications can use select( ) with any character I/O devices that provide support for this facility (for example, pipes, serial devices, and sockets). For information on writing a device driver that supports select( ), see Implementing select( ).

Example 3-1:  The Select Facility

/* selServer.c - select example  
 * In this example, a server task uses two pipes: one for normal-priority  
 * requests, the other for high-priority requests. The server opens both  
 * pipes and blocks while waiting for data to be available in at least one  
 * of the pipes.  
 */
#include "vxWorks.h" #include "selectLib.h" #include "fcntl.h"
#define MAX_FDS 2 #define MAX_DATA 1024 #define PIPEHI "/pipe/highPriority" #define PIPENORM "/pipe/normalPriority"
/************************************************************************ * selServer - reads data as it becomes available from two different pipes * * Opens two pipe fds, reading from whichever becomes available. The * server code assumes the pipes have been created from either another * task or the shell. To test this code from the shell do the following: * -> ld < selServer.o * -> pipeDevCreate ("/pipe/highPriority", 5, 1024) * -> pipeDevCreate ("/pipe/normalPriority", 5, 1024) * -> fdHi = open ("/pipe/highPriority", 1, 0) * -> fdNorm = open ("/pipe/normalPriority", 1, 0) * -> iosFdShow * -> sp selServer * -> i * At this point you should see selServer's state as pended. You can now * write to either pipe to make the selServer display your message. * -> write fdNorm, "Howdy", 6 * -> write fdHi, "Urgent", 7 */
STATUS selServer (void) { struct fd_set readFds; /* bit mask of fds to read from */ int fds[MAX_FDS];    /* array of fds on which to pend */ int width;      /* number of fds on which to pend */ int i;        /* index for fd array */ char buffer[MAX_DATA]; /* buffer for data that is read */
/* open file descriptors */ if ((fds[0] = open (PIPEHI, O_RDONLY, 0)) == ERROR) return (ERROR); if ((fds[1] = open (PIPENORM, O_RDONLY, 0)) == ERROR) return (ERROR);
/* loop forever reading data and servicing clients */ FOREVER { /* clear bits in read bit mask */ FD_ZERO (&readFds);
/* initialize bit mask */ FD_SET (fds[0], &readFds); FD_SET (fds[1], &readFds); width = (fds[0] > fds[1]) ? fds[0] : fds[1]; width++;
/* pend, waiting for one or more fds to become ready */ if (select (width, &readFds, NULL, NULL, NULL) == ERROR) return (ERROR);
/* step through array and read from fds that are ready */ for (i=0; i< MAX_FDS; i++) { /* check if this fd has data to read */ if (FD_ISSET (fds[i], &readFds)) { /* typically read from fd now that it is ready */ read (fds[i], buffer, MAX_DATA); /* normally service request, for this example print it */ printf ("SELSERVER Reading from %s: %s\n", (i == 0) ? PIPEHI : PIPENORM, buffer); } } } }