Copyright ©1988, 1989, 1994 Mohsen Banan.
Copyright ©1994-1997 Neda Communications, Inc.
A great deal of the development of OSI is in its final stages. Fruits of these efforts are beginning to appear in our daily lives as implementation of OSI protocols. The future of OSI is very exciting and its impact on our lives will be significant.
This book focuses on creating an environment (a model, and architecture and a platform) for software implementation of OSI protocols. It is assumed that the reader is familiar with the concepts of OSI Reference Model. This book establishes a reference model for portable software implementation of OSI in C.
Although the primary intent of this book is to establish a platform for implementation of International Standards Organization (ISO) specification of OSI protocols, a great deal of the contents of this book applies to layered software implementation in general.
The C programming language has proven itself as a popular portable and efficient language for implementation of data communications software. The implementation environment described in this book is centered around C. It is assumed that the reader has a working knowledge of C.
Software implementation of OSI protocols that adhere to the ”Open C Environment” (OCE) described in this book can be integrated to realize efficient real open systems. The environment presented in this book has been used for implementation of a number of ISO specification of OSI protocols. Any C compiler that conforms to the ANSI C specification can be used to port this implementation platform to the target environment.
It is the aim of this book to refine the abstract descriptions of the OSI model as they apply to software implementation in C. An implementation environment that provides for efficient integration of OSI protocols is created. Specification of OSI protocols, C language and the environment described in this book provide for portable and efficient realization of real open systems.
Implementors, system architects, system programmers, application programmers and anyone who is interested in understanding the implementation aspects of OSI can benefit from reading this book. This book can be used to teach the practical aspects of data communications software development in conjunction with other books that deal with the concepts of Open Systems Interconnections.
The structure of this book is as follows. Chapter 1 provides an introduction to the issues involved in implementation of OSI. Chapter 2 describes the Open C Environment. Chapter 3 outlines a number of conventions followed in defining the Open C Platform. Chapter 4 outlines the common features of the implementation layers. Chapter 5 deals with the architecture of network management. Chapter 6 describes the interface and service definition for Open C Platform. Chapter 7 provides an example usage of Open C Platform. Chapter 8 deals with implementation issues. Appendix A contains a set of UNIX style manual pages for facilities of Open C Platform.
Many of the implementation techniques outlined in this book are very similar to the ones practiced by NBS 1, Retix and a number of other implementors. Little, if any, of the credit for the implementation techniques should go to the author. My goal has been to combine these commonly practiced good techniques into an environment that may facilitate the realization of OSI.
I wish to thank my colleagues at Retix who inspired the ideas behind many of the concepts appearing in this book.
A number of individuals at several companies have contributed to the creation and expansion of sections of this manual as well as the software itself. Neda Communications, Inc. leads the effort of maintaining and documenting the majority of the software.
Significant contributions to this work resulted from the use of OCP by AT&T Wireless Services.
Finally, a number of individuals who have made contributions to the OCP library and this document deserve special recognition.
We look forward to continued growth of the OCP library. Please send your feedback, including code contributions, to
Mohsen Banan - Neda Communications, Inc. mohsen@neda.com
This manual is written in LATEXinfo. LATEXinfo is a documentation system that uses a single source file for both on–line documentation and a printed manual. See the LATEXinfo Manual for more details [1].
The on–line documentation is in the form of an Info file. An on-line Info file is a file formatted so that the Info documentation reading program can operate on it. Info files are divided into pieces called nodes, each of which contains the discussion of one topic. M-x info in emacs and xinfo under X11 are two Info documentation reading programs that can be used for on-line manipulation of this manual.
Module openCPlatform
Was released as Revision: on by The name tag for this release is The file capturing this release is
This manual is mostly complete and has gone through a few drafts. However, it is far from being flawless.
This document should be fully correct in what it does say; and it is therefore open to criticism on anything it does include — from specific examples and descriptive text, to the ordering of chapters and sections. If something is confusing or incorrect, then perhaps the document should be fixed.
As you use this document, please mark pages with corrections so you can later look them up and send them in. Please reference any comments to the chapter name and section name, since page numbers and chapter and section numbers will change.
Comments concerning this document should be addressed to:
Typically each OSI layer has two sets of specifications: One for the services provided by the protocol and one for the protocol. Service specifications for each layer are normally described in a high level of abstraction. The interfaces for the service definition is left at the level of primitive. Furthermore, the ISO specifications do not deal with any details concerning the mechanism used to exchange primitives across a layer interface.
Lack of a standard interface or a standard mechanism for exchange of primitives across a layer interface can cause severe problems in implementation of machine independent OSI software.
OSI Reference Model offers a number of features that are well aligned with today’s software implementation methodology. To name a few:
A well defined model, architecture and interface provides for integration of independent implementations of OSI protocols. It is the aim of this book to refine the abstract descriptions of the OSI model and create a software implementation environment (A model, an architecture and a platform) that facilitates the implementation of OSI.
Given today’s state of technology a number of observations are made.
The author therefore feels that the time is right for creation of a software environment that can facilitate the realization of OSI. Such a platform should be:
This has been the motivation behind the creation of ”Open C Environment” (OCE) for implementation of OSI. OCE is a set of architectural guide lines, conventions and a platform for portable implementation of OSI software. Independent implementations of OSI protocols that adhere to OCE can easily be integrated to realize real open systems.
A platform of facilities that can ease the implementation of OSI protocols is provided. One of the primary goals of this book is to formally define the interface to this platform of facilities.
The nature of the different services expected of the different layers results into particular services expected from the implementation environment. The lower layers (Transport, Network, Data Link and Physical) are responsible for transport of unstructured data. The upper layers (Application, Presentation and Session) deal with the structure, syntax and semantics of the communication.
The upper layers expect more services form their implementation environment than the lower layers. The implementation environment expected by the upper layers can be considered as an extension of the lower layers implementation environment.
It is recognized that at this time only the bottom five layers have matured. This book’s primary focus is to create an implementation environment for the lower layers.
One of the goals for the creation of Open C Environment for implementation of OSI is portability and environment independence. Two basic categories of target environments are identified.
The Hosted-Environment is an environment in which the existance of an operating system and multitasking capabilities may be assumed. The availability of facilities such as scheduling, timers, synchronization and dynamic memory allocation are often available in Hosted-Environments. UNIX, VMS, MS-DOS and VRTX are examples of hosted environments.
The Unhosted-Environment is an environment in which the existance of no facilities may be assumed. The underlying environment should at least provide:
Bare, intelligent, front-end processors are examples of Unhosted-Environments.
Open C Environment is designed to exist in Hosted and Unhosted environments. In Hosted-Environments, the interface to host facilities should be mapped on to the Open C Platform interface definitions. Design of OCE does consider well-behaved existance in Hosted-Environments.
The type of environment specific facilities defined in OCE are basic and simple. These facilities can easily be implemented from scratch in Un-Hosted environments.
A C compiler, an assembler, a linker, and a loader are expected of a development environment necessary for use of OCE.
The primary development environment used by the author is UNIX.
This section defines the relationship between OCP and the various components in it’s Environment. In the following discussion refer to the diagram in Figure 1.1
The diagram as a whole represents a particular implementation of an Open C Layer. The Application, represented as the highest level component, is the body of software that is unique to a given application or service. The CPU/Memory combination, at the lowest level, represents the minimum computing functionality that OCP requires. The Operating System, which may or may not exist on a given computing platform, provides some set of services to OCP. Finally, a particular C Compiler is used to generate machine executable code.
For example, in the case of a Unix workstation, the CPU might be a 64-bit processor with gigabytes of virtual memory. The operating system would provide a wide range of services including disk file I/O, interprocess communication, and network services.
On the other hand, a portable messaging device might use a 16-bit processor with 64 kilobytes of memory while the operating system might be nothing more than a simple task scheduler.
OCP isolates the Application from all of this underlying complexity and variability. Once OCP is ported to a given Open C Environment (CPU/Memory, Operating System, and Compiler) any Application developed under one Open C Environment can easily be ported to another.
Definitions, notation, abbreviation and terminology used in this book are consistent with the terminology and principles established by ISO for Open Systems interconnection.
The OSI reference model is based on a number of ’abstract’ descriptions. In our implementation model, these abstract descriptions are refined to define the precise context they take as they apply to this implementation architecture.
A service access point (SAP) is defined as the interface between a service user and a service provider. Each SAP is identified by the service provider through a SAP-Address-Selector.
An (N) connection is an association established by the (N) layer between two or more (N+1) entities for the transfer of data. An (N) connection end point (CEP) is a terminator at one end of an (N) connection within an (N)-SAP. An (N) connection end point identifier (CEP-ID) is a unique representation of a CEP within the scope of the (N) service
An Open C Layer is the implementation of an OSI protocol that conforms to Open C Environment architecture and guidelines. Direct function calls are used for exchange of primitives across Open C layers. This simple and efficient primitive exchange mechanism allows for integration of Open C Layers into multi-layered communication entities. Other methods for exchange of service primitives (such as interprocess or interprocessor communication facilities) often result into performance degradation across layer boundaries.
Once the upper and the lower interfaces of a Layer are defined (based on the service definitions), independent implementors can implement Open C Layers. Since the upper interface of implementation of (N) layer protocols match the lower interface of implementation of (N+1) layer protocols, several Open C Layers can be integrated into a multi-layered communication software entity.
An Open C Layer is a cohesive piece of software that provides a well defined service and has a set of well defined interfaces. An OSI Open C Layer implements an OSI protocol. Open C Layers have a consistent design architecture. Each implementation of an OSI protocol, provides to its user the set of services defined for that protocol at its upper interface. It assumes the services of the layer below it to be present at its lower interface.
A typical software layer (N) appears to its user as a link module with four defined interfaces. The figure below (Figure 2.1) presents an interface model for the (N) software module.
The (N) Open C Layer is expected to provide the services defined for the (N) layer at its upper interface. The (N) Open C Layer’s upper interface is a series of function calls (primitives). Each function call accepts a group of arguments (parameters). Each function call is non-blocking.
(N) request and responses, collectively referred to as (N) action primitives, are function calls into the (N) Open C Layer. (N) action primitives are invoked by the (N) service user. The code for (N) action primitives is inside the (N) Open C Layer.
(N) indication and confirmations, collectively referred to as (N) event primitives, are function calls outside of the (N) software module. (N) event primitives are invoked by the (N) software module. The code for (N) event primitives is expected to be provided by the (N) service user. The entry point for (N) event primitives is conveyed to the (N) layer during the creation of the service access point by (N) service user.
The (N) Open C Layer may use the services defined for the (N-1) layer at its lower interface. The lower interface of the (N) software module matches the upper interface of (N-1) software module.
Operation of an OSI layer may be monitored and manipulated by the Network Management Administrator. To provide for this each Open C Layer is responsible for providing an interface that allows for network management administration.
Within each Open C Layer, often a Layer Management Entity (LME) is responsible for defining and providing the network administration interface.
An Open C Layer can rely on the services provided by the Open C Platform. The interface and the description of the services provided by the Open C Platform is described in this book.
The following is a list of significant events that result into execution of code inside of (N) Open C layer.
Good programming conventions can result in improvements in consistency, portability, readability and writability. The development environment and the nature of the software developed have a large influence on the styles and conventions that are adapted. As the development environment and software engineering methodology evolve, the stylistic notations and conventions will also evolve.
The identifier naming conventions described here are consistent with ISO’s conventions in specification of ASN1 definitions. These naming conventions are also consistent with the conventions followed in the C bindings produced by MAP and TOP groups. There are some differences but these are minor.
The identifier naming conventions include:
The layout of expressions and statements follow those in the book written by Kernighan and Ritchie entitled ”The C Programming Language”.
The few simple conventions mentioned above are adequate for describing the basic guide lines. The following section describe in more detail the naming conventions followed and the rationale behind it.
The naming conventions recommended in this section are specific to the C programming language. These conventions take full advantage of the identifier naming facilities offered by the standard definition of C language. Lower and upper case letters as well as ’_’ are used in this naming convention. All identifiers are expected to be unique with in the first 24 characters.
Concept of a module and hiding in C is limited to the source file. The scope of an identifier outside of a source file is global. Explicit importing and exporting of identifiers is not supported in standard C. This naming convention tries to address this known deficiency in C.
All identifiers are composed of two elements: module prefix and qualifier. Each module is identified by a module prefix. A module prefix is a short name (normally 1 to 4 characters long) followed by an ’_’. The case of the module prefix specifies the scope of the identifier with regard to that module. Identifiers exported by a module, have an all upper-case module prefix. An all lower case module prefix signifies that although the identifier is needed by more than one source file within the implementation of the module, it is purely private to the module and need not be exposed to any users of the module. Purely local identifiers need not have the module prefix component and consist of a pure qualifier. In addition to conveying the scope of the identifier, this convention results in prevention of naming collisions across independent modules.
The qualifier component of an identifier mainly conveys the semantic attributes of the identifier. The qualifier component may consist of many words. With the exception of the first word, all following words start with upper-case. The first character of the qualifier conveys some type information. Variables, functions, parameters and structure fields begin with lower-case, the then upper and lower-case mixed. Typedefs, struct/union/enum tags named with upper-case groups. Although not clearly specified in the language definition, typedefs name space is different from struct/union/enum tags name space. Name space overloading is encouraged.
This convention results into clear usage of the same name for a number of related identifiers. This is demonstrated in the following example.
Same descriptive name ”sapSelector” is used for the structure tag identifier, the type definition identifier and the variable identifier for a generic instance.
A set of recommendations are proposed for qualifier naming.
A number of abbreviations are commonly used to express well defined concepts with in the scope of OSI implementation. These abbreviations are commonly used for identifier naming.
The primary intentions of the identifier naming convention mentioned above is to convey important information about identifiers while keeping the names short and natural.
The following is a partial list of the intentions of this naming convention.
Recognizing the restrictions that may be imposed by the development environments that may be used, the following conventions are recommended.
With these restrictions even MS-DOS development environment can be supported.
The concepts of ASN.1 sequences-of and sets-of are not inherent in C. Arrays are adequate for static usage. Dynamic usage is often implemented through the usage of linked lists. For implementation purposes set-of and sequence-of are considered the same. Relevance of order is the user’s view and not an implementation issue. Only the implementation of sequence-of is described here.
ASN.1 sequence concept maps to struct in C. With this understanding, for design purposes a shorthand notation is adapted to make the concept of sequence-of more natural to C. The following keyword extension are made:
Their usage is similar to struct and union. sequence specifies an element of a list. sequenceof specifies the head of a list. These are based on the QU_ and SEQ_ module. sequence and sequenceof can be used for data or instance declaration.
Consider the following example:
Which is equivalent to:
For a sequenceof instance declaration, consider:
Which is equivalent to:
For a sequenceof type declaration, consider:
Which is equivalent to:
Implementation of sets-of can be done as sequences-of. The user may convey the irrelevance of order through the usage of:
reserved word extensions.
Open C Environment conforms to the ANSI C standard. At present, a large number of existing C compilers have not implemented all the new features in ANSI C. New features of C, (function prototypes, structure assignment and new reserved words) may be used only if backwards compatibility is maintained. This can often be accomplished through conditional compilation.
Pre-processor identifier ANSIC is reserved for this purpose. Only environments that conform to the standard should have it defined. The following example demonstrates the proper usage of the new features.
Structure assignment and use of structures as function arguments is also discouraged unless compatibility with older compilers is maintained.
Environment specific facilities may be implemented in a variety of ways. To support more than one target environment through the same source code, conditional compilation features are often used. Three basic elements of the operating environment are recognized as:
Conventional compile time identifiers that identify the operating environment are placed in a file called ”oe.h”. When porting the Open C Platform ”oe.h” must be properly configured to reflect the target environment.
When developing on a PC-AT running XENIX, my oe.h is configured as: _______________________________
#ifndef _OE_H_ /*{*/
#define _OE_H_
/*
* CPU.
* Used for byte order representation of integers.
* Supported CPUs are:
* INTEL for 8086 family (8088, 80186, 80188, 80286)
* VAX for DEC VAX mini-computers.
* M68K for Motorola MC68000 family processors 10
* NS16 for National Semiconductor NS16000 series processors.
*/
#define INTEL
/*
* Operating System.
* Used for OS specific interprocess communication and
* other OS specific facilities.
* Supported Operating Systems are:
* SIMU No operating system at all. 20
* SYSV System V Unix release {2,3}.
* BSD Berkeley Unix release 4.{2,3}.
* MSDOS
* VMS
*/
#define SYSV
/*
* Compiler.
* Used to identify the compiler. 30
* This can be used to work around abnormalities of the C compiler.
* Supported Compilers are:
* KANDR Traditional compilers.
* ANSIC Standard conformant.
*/
#define ANSIC
#endif /*}*/ _________________________________________________________________________________
_________________________________________________________________________________________
|
All Open C Environment source modules include the global portable standard definitions definitions file estd.h. Documentation on estd.h is provided in appendix A. Basic portable data type definitions are defined in estd.h.
Each layer has a number of typical exposed interfaces. As an example let’s consider a sample software layer called ”Some Service Provider” (SSP_). (SSP_) is expected to provide some defined services to the layer above it (UPPER_). It can rely on the services offered by the layer below it (LOWER_). The usual interfaces of the SSP_ layer are listed below:
Consistent with the naming conventions mentioned in the previous section, all exposed interfaces of this module are prefixed by SSP_. The indirect function invocation (*func)() notation is used to indicate the event primitive interaction with other layers.
Each layer can expect to be initialized before providing any services. During the initialization, the module can obtain its required resources and become ready to provide its expected services. The entry point to initialize the SSP_ layer is SSP_init().
Each Open C Layer must have a mechanism to be terminated and re-initialized. After termination a layer is not expected to maintain any history of what had happened prior to termination. The module must be re-initialized before being used. During a reset the layer should release all the resources that it had previously obtained. The entry point to terminate the SSP_ layer is SSP_term().
All initialization functions should be idempotent, meaning multiple invocations of the initialization facility do not result in an error. The following code fragment illustrates a conventional implementation of initialization and termination facilities.
All service provider layers have a means to support multiple service users simultaneously. This is supported through the concept of Service Access Points.
A service access point (SAP) is defined as the interface between a service user and a service provider. Each SAP is identified by the service provider through a SAP address selector. Above the network layer SAP address selectors are derived from SAP address suffixes.
Each service user is expected to create a service access point before using any of the services of the provider. Each layer has a function to create a SAP and a function to delete a SAP. Upon SAP creation, the service provider, associates SAP address selector of a service user entity with the address of a number of function(s) within the service user entity. These functions will be used by the service provider to handle indications and confirms (primitive events) at the upper interface.
Each Open C Layer is expected to provide its services at its upper interface. The set of action primitives provided by the layer must be known by the service user. Entry points for events primitives must have been conveyed to the layer during the creation of the service access point. The calling sequence for event primitive must be consistent with the service user’s expectations.
Open C Layers may rely on the services provided by the layers below them. The set of action primitives provided by the layer below must be known. Entry points for event primitives are conveyed to the layer below during the SAP creation.
Each layer has a layer management entity (LME) responsible for administration of the layer. This subject is discussed in more detail in chapter 4.
OSI resources such as SAPs and CEPs are often dynamically used. Creation, usage and deletion of these resources often follow a common pattern. This common pattern is described in this section.
Usage of resources is often through a descriptor based scheme. Through a create operation a unique descriptor referencing an instance of the resource is obtained. This descriptor is used as a reference to the resource in all future transactions. Two basic variations on this scheme are described below. The one way resource usage model and the two way resource usage model. The one way resource usage is typically more appropriate for interfaces that only requires action primitives. The two way resource usage is typically more appropriate for interfaces that require action and event primitives.
In the one way model, a resource typically has:
in stdio module is an example of the one way model for resource usage. fileName is the address of the resource and FILE is the provider’s reference.
In the two way model, a resource typically has:
Let’s consider the case of a Connection End Point.
conReq and dataReq are action primitives, conCnf is an event primitive. Prior to the creation of a CEP, the service user prepares its private model of the CEP. During the creation of the CEP (conReq), a reference to this (cepUserRef) is conveyed to the service provider module. The service provider module (conReq) in turn returns a provider reference. All future action primitives dealing with the CEP will use the provider reference. All future event primitives dealing with the CEP will use the user reference.
The layered architecture plus the consistent layer interface provides for ease of integration. Several communication layer implementations can be integrated into a multi-layered communication software entity.
The Global module responsible for integration of the communication layers is expected to be called the ”G_” module. A number of features are expected of the G_ module. These features are described in the next section.
The following basic structure is typical of the program that integrates one or more layers into a communication system.
By convention the G_ module is responsible for integration of all other modules in the executable entity. By convention ”g.h” is designated to contain global configuration and integration information to be shared among independent modules. G_Env may be shared among all modules to convey global environment information.
The specific nature of G_ module and ”g.h” is specific to each target environment. The following figure illustrates the usage of g.h for configuration and integration purposes. ____________________________________________________
/*
* SCCS Revision: %W% Released: %G%
*/
#ifndef _G_H_ /*{*/
#define _G_H_
#include "du.h"
typedef struct G_Env { 10
Bool hardReset;
Bool softReset;
} G_Env;
extern DU_Pool *G_mainDuPool;
#define K_schQuLen 22 /* Number of Scheduler queue items */
#endif /*}*/_____________________________________________________________________________________________________________________________________________________________________________________________________________________
_________________________________________________________________________________________
|
Potential usages of the G_ module are demonstrated in other examples in this book.
The Network Management implementation architecture outlined here is based on [2]. The concepts described in [2] trickle down to the lower layers. Layer management facilities, should therefore be designed to support these concepts. This sections deals with Common Management Information Services as they apply to the lower layers.
Following figure illustrates OCE’s view of Network Management Architecture.
Each layer has a Layer Management Entity (LME) which provides an interface for manipulating and monitoring that layer’s performance. Each System has an entity that can access individual LME and perform Network Management Functions. This entity will be called ”Layer Management Entity Interface” Common Management Information Services Element (CMISE) is responsible for monitoring the system on behalf of a Network Management Administrator (NMA).
The Common Management Information Protocol (CMIP) provides request/response service between a CMISE in one real open system and a CMISE in a second real open system which may be carrying out management activities in that real open system on behalf of the CMISE in the first open system. The CMIP also provides an event reporting service between an event reporting CMISE and an event monitoring CMISE.
Note that there are no ”modify”, ”delete”, or ”create” operations. Creation must occur at the Module Management Entity level. Deletion can be accomplished by setting the Notification Mask to zero. Modify is accomplished via the ”set” operation.
The Module Management Module uses the following data types:
These are the currently supported types of entities which we can manage.
Each managable entity may be assigned one or more notification types which are to be generated when an event on that managable entity is raised. The module itself may specify an initial set of notification types for the managable entity, but the manager entity may modify that set.
A general rule to follow in deciding what notification type to use, is that the higher-order bits should indicate more urgent events while lower-order bits should indicate less urgent events.
NOTE: The maximum number of bits allowed here is 16, as enum types are 16-bits when using some compilers.
This is the current set of events which may be generated by a module’s management entity.
IMPORTANT NOTE:
If additional event types are added, be sure to add comments specifying what optional parameters are passed to the Alert function (see MM_registerDestination()) when an event of this type is raised.
This function is to be called by all applications making use of any of the Module Management facilities.
Initialize the Module Management Entity module.
These functions are to be called by each module which wishes to use module management facilities.
Allocate management resources for a code module or protocol layer.
Register a managable entity for use by the specified module.
subsubsectionSet Threshold
Set the maximum or minimum threshold value for a managable entity.
Increment the numeric value of the specified managable entity (probably either a counter or a guage) by the specified value.
Start a timer. When it expires, an event will be raised.
Stop a previously started timer.
Generate a message for logging, using a printf-style format.
Each MMA talks to multiple Module Management Entities (MME’s).
Register a new destination to which events may be sent. A destination is a place where an event is sent. All Module Management Agents should register at least one destination – the Module Management Manager. Additional destinations may be registered, such as to a log file, to send email, etc.
Modify the set of notification types which should be sent to this destination.
Event notification does not happen asynchronously. The reason for this is that the event could be raised during interrupt routines, critical sections, etc. We therefore enqueue the event notification for action when this function is called.
This function should be called on a regular basis, either in a main loop, or via a timer expiration.
Get the current value of a managable entity.
Get the current value of a managable entity.
Set the current value of a managable entity.
Get the current value of a managable entity.
Each LME should define the complete set of management information types and facilities that it supports. Mechanism for access to LME of each layer is same as the layer’s other interfaces – Direct non-blocking function calls with a well defined set of arguments.
Platform facilities are grouped into sets of related facilities. Module naming conventions, mentioned earlier is used to highlight this grouping. A list of these common facilities is provided in the following table:
EH_ and TM_ provide exception handling, event logging and tracing facilities. This type of basic facilities are required by any type of serious software development.
SF_ (System Facilities) module defines an interface for a number of inherently non portable facilities. Implementation of many of the facilities defined here can be made portable by relying on SF_ facilities.
SCH_ (Scheduling) module provides for efficient usage of the CPU in multi-processing environments.
BO_ provides for simple CPU independent value representation. BS_ defines an interface for manipulation of blocks of data.
A set of inherently environment dependent services are expected of the operating environment. Environment independent services and interfaces defined in Open C Platform can be implemented by relying on the primitive environment specific facilities available in each environment.
Ability to Obtain memory from the environment, a periodic interrupt and protection against preemption are among the few environment specific facilities that are assumed by Open C Platform. Other environment specific facilities such as atomic queue operations, and timer facilities may be available in some environments. When available these facilities may be used for efficient implementation of Open C Platform. Facilities described in this section are considered to be low level. Their direct usage by the application is not recommended.
During the initialization each module may reserve some memory for its usage and upon a soft reset each module may return the memory back to the environment. The lower layers should not assume the existence of a dynamic memory allocation facility such as malloc. All the memory that required by a module is expected be reserved upon initialization.
Provide a model for such environment specific facilities.
Asynchronous intercations with synchronous processing can result into inconsistencies. To prevent this the availability of a simple preemption protection mechanism is assumed. When the synchronous software is accessing a critical resource, it protects itself against preemption and once the critical section is completed preemption status is restored.
Provide a model for this service. Enabling and disabling interrupts is an extreme way of implementing this facilities in un-hosted environments.
Most modern CPUs include support for atomic queue (circular doubly linked list) operations. When available this facility may be exploited for coordination of synchronous and asynchronous interactions.
Provide a model for this service. The QU_ module describes the characteristics of circular doubly linked lists. QU_ facilities are not protected against asynchronous preemption and should not be used when asynchronous preemption can result into inconsistencies. SF_quInsert and SF_quRemove are expected to be atomic (non-preemptable during the entire operation).
SF_quInsert inserts elem at the tail of head. elem need not be initialized. If head was empty, -1 is returned. Otherwise 0 is returned.
SF_quRemove removes the first element of the queue from the head if there is one. If head was not empty *elem is a pointer to the removed element. The removed element will not be initialized at the completion of SF_quRemove. If head was empty, SF_quRemove returns -1 and *elem is untouched. Otherwise 0 is returned.
An example implementation of SCH_ module based on SF_qu facilities is illustrated in the following code fragment. __________________________________________________________________________________
#ifdef SCCS_VER /*{*/
static char sccs[ ] = "%W% Released: %G%";
#endif /*}*/
#include "estd.h"
#include "sf.h"
#include "eh.h"
/*
* Scheduler Information. 10
*/
typedef struct SchInfo {
struct SchInfo *next;
struct SchInfo *prev;
Int (*func)(); /* Function to Call */
Ptr arg;
} SchInfo;
typedef struct SchInfoSeq {
SchInfo *first; 20
SchInfo *last;
} SchInfoSeq;
STATIC SchInfoSeq availSchInfo;
STATIC SchInfoSeq activeSchInfo;
STATIC SchInfo *schInfoBuf;
STATIC SchInfo *schInfoBufEnd;
30
Void SCH_init(maxSchInfo)
Int maxSchInfo;
{
SchInfo *schInfo;
/*
* Create a Pool
*/
schInfoBuf = (SchInfo *)
SF_memObtain(maxSchInfo * sizeof(*schInfoBuf)); 40
if (!schInfoBuf) {
EH_fatal("SF_memObtain");
}
schInfoBufEnd = &schInfoBuf[maxSchInfo -1];
QU_init(&availSchInfo);
QU_init(&activeSchInfo);
for (schInfo = schInfoBuf; schInfo <= schInfoBufEnd; ++schInfo){
SF_quInsert(&availSchInfo, schInfo);
}
} 50
Void SCH_term()
{
SF_memRelease(schInfoBuf);
}
Void SCH_submit(func, arg)
Int (*func)();
Ptr arg;
{ 60
SchInfo *schInfo;
Int status;
/*
* Queue Up the function and argument for synchronus processing.
* Notice The Qu insertion must be protected.
*/
if ((status = SF_quRemove(&availSchInfo, &schInfo)) != 0) {
EH_fatal("No SchInfo");
return ; 70
}
schInfo->func = func;
schInfo->arg = arg;
if (SF_quInsert(&activeSchInfo, schInfo)) {
/* PORTATION SPECIFIC
* Use environment specific facilities to wake up.
*/
sys$wake(0, 0); 80
}
}
Void SCH_block()
{
/*
* Should map into environment specific facilities
* that puts this process into sleep.
*/
if (activeSchInfo.first == &activeSchInfo) { 90
/* PORTATION SPECIFIC
* Use Environment Specific facilities to Block
*/
sys$hiber();
}
}
Void SCH_run()
{
SchInfo *schInfo; 100
Int status;
while (SF_quRemove(&activeSchInfo, &schInfo) == 0) {
(*schInfo->func)(schInfo->arg);
SF_quInsert(&availSchInfo, schInfo);
}
} _________________________________________________________________________________________
_________________________________________________________________________________________
|
In this example it is assumed that environment specific facilities for scheduling are provided through syshiber and syswake.
In multi-processing hosted environments, it is desirable to share the CPU with other processes. In these environments the availability of facilities for blocking and waking up are assumed. When available these facilities may be used in the implementation of SCH_ facilities.
SCH_ section defines the scheduling facility to be used by Open C Platform users.
Environment specific timer facilities are expected to be enhanced to conform to TMR_ facilities described later in this chapter. In un-hosted environments the only expected timer facility from the environment is a periodic interrupt.
In environments where multi processing is supported, it may be desirable to time share the CPU with other processes. In this scenario when the communication software has no more work to do it can block and deliver CPU to other processes.
SCH_ module can be used for rescheduling of known modules. One of the common usages of SCH_ module rescheduling of further processing with in the same module. This happens most often to prevent re-entry to non-re-entrant code.
Take the case of an (N-1) Action Primitive resulting into an (N-1) Event Primitive. If (N) layer code is non-re-entrant, this should not happen. The expected behavior of (N-1) module is:
The following example illustrates an example of such a sequence. _____________________________________
#ifdef SCCS_VER /*{*/
static char sccs[ ] = "%W% Released: %G%";
#endif /*}*/
#include <stdio.h>
#include "estd.h"
#include "tm.h"
#include "getopt.h"
#include "g.h"
10
Void SSP_init(), SSP_sapCreate(), SSP_actionPrim();
Void USER_init();
PUBLIC G_Env G_env;
main(argc, argv)
Int argc;
String argv[ ];
{
Int c; 20
TM_init();
while ((c = getopt(argc, argv, "T:t:")) != EOF) {
switch ( c ) {
case 'T':
case 't':
TM_setUp(optarg);
break;
case '?': 30
default:
G_exit(1);
}
}
G_env.hardReset = FALSE;
G_env.softReset = FALSE;
while ( !G_env.hardReset ) {
SCH_init(K_schQuLen); 40
SSP_init();
USER_init();
TM_validate();
while ( !G_env.softReset ) {
SCH_block();
SCH_run();
}
SCH_term(); 50
}
G_exit(1);
}
G_exit(code)
Int code;
{
exit(code);
} 60
/*
* USER_ module.
*/
Void userEventPrim()
{
printf("SSP Event Primitive, invoked by the scheduler\n");
G_env.softReset = TRUE;
G_env.hardReset = TRUE; 70
}
Void USER_init()
{
SSP_sapCreate(userEventPrim);
SSP_actionPrim();
}
/*
* Some Service Provider (SSP_) Module. 80
*/
static Void (*sapEventPrim)();
Void SSP_init()
{
/* Initialization could have been done here */
}
Void SSP_sapCreate(eventPrim)
Void (*eventPrim)(); 90
{
sapEventPrim = eventPrim;
}
Void SSP_actionPrim()
{
/* Let’s say this action primitive was to result
* into an event primitive, but since we can not rely
* on the user context being re-enterant.
* The event primitive is scheduled for exection 100
* outside the user’s context.
*/
printf("SSP Action Primitive, invoked by USER\n");
SCH_submit(sapEventPrim, (Ptr)0);
} _________________________________________________________________________________________
_________________________________________________________________________________________
|
BO_ facilities provide primitive abstract data presentation facilities for simple types of values.
BO_ facilities convert CPU presentation of values to a machine independent byte ordering and vice versa. 8 bit values, 16 bit values, 32 bit values, and octet string types are converted to an abstract presentation commonly used by the lower layer protocols. This abstract presentation is expressed through a sequence of octets. Octets at the lower address always contain the most significant byte of the value. Least Significant Byte of the value is always at the higher address.
netPtr points to where the machine independent presentation value should be stored. Upon completion of all BO_ facilities netPtr is incremented by the appropriate value so that it can be used in subsequent BO_ operations. cpuValue is the machine dependent presentation of a value.
Get and Put verbs in BO_get/BO_put are with respect to the abstract presentation (the network). BO_get always converts the abstract presentation of contents of netPtr into a machine dependent value (cpuValue). Note that since cpuValue is an out put value and is not passed as a pointer BO_get facility must be implemented as a MACRO. BO_put always converts the machine dependent presentation of a value into an abstract presentation. netPtr is always incremented.
A Byte String (BS_) is a consecutive memory address range. A byte string is specified by its starting address and its length. BS_ module operates on byte strings.
BS_memCopy copies nuOfBytes from string src to dst. src and dst are assumed to be non over lapping.
BS_memCmp compares byte string src against dst .
Byte string operations are often one of the most execution intensive parts of a protocol implementation. Environment specific facilities provided in the target environment can sometimes be used to implement these facilities more efficiently that they can be done as portable code.
QU_ facilities provide a uniform mechanism for manipulation of doubly linked circular lists. A queue is a circular doubly linked list. Queues are often used for implementation of sequences and sets. A queue entry is linked to the next by a pair of pointers. The first pointer (next) is the forward link. It specifies the location of the succeeding entry. The second pointer (prev) is the backward link, it specifies the location of the preceding entry.
A queue is specified by a queue header (QU_Head). Structure of queue header is same as queue element (two pointers). The forward link of the header (first) is called head of the queue. The backward link of the header (last) is called the tail of the queue.
IMPORTANT NOTE 1:
Make no assumptions about the structure of this type. It may change in the future. All manipulation of queue elements must be accomplished solely by the functions in this queue module.
(As an example, if this code is ever moved into a multi-threading environment, some elements may be added to the header, for mutual exclusion while manipulating the queue pointers.)
IMPORTANT NOTE 2:
Do not declare queue pointers in your own structures. Instead, declare the first element of your structures as either QU_ELEMENT or QU_HEAD. No variable name is necessary.
Two basic operations can be performed on queues: insertion of entries and removal of entries.
QU_ facilities are not protected against asynchronous preemption and should not be used when asynchronous preemption can result into inconsistencies. SF_quInsert and SF_quRemove are expected to be atomic (non-preemptable during the entire operation).
QU_init, QU_insert, QU_remove and QU_move all operate on an abstract data type ”QU_Elem”. Objects that queue management facilities manipulate are expected to be data types that allow for a QU_Elem to be casted over them.
QU_init initializes a QU_Elem so that it can be used in subsequent operations. A QU_Elem is initialized by having its ”next” and ”prev” field point to itself. An initialized QU_Elem is an empty circular list.
QU_insert(q1, q2) inserts linked list q1 before list q2. The result is a linked list that contains all members of q1 and q2.
It is interesting to note that the order of arguments is not important. The result of QU_insert(q1, q2) and QU_insert(q2, q1) is the same. Figure 6.3 illustrates this.
The facility QU_remove removes QU_Elem q from the list to which it belonged. QU_Elem q is initialized upon completion of QU_remove.
QU_move moves QU_Elem q1 to end of q2. This is equivalent to the commonly used coding sequence:
QU_ also provides the following set of macros to simplify coding.
QU_INITIALIZE(q)
SEQ_ module provides simple fixed size dynamic memory allocation capabilities for linked list elements. In conjunction with the QU_ module, sequences and sets implemented as linked lists can conveniently be maintained.
Memory for a number of elements within a sequence can initially be obtained through SEQ_poolCreate facility. As new elements of a set or a sequence are needed they can be obtained through SEQ_elemObtain facility. Sequence or set elements can be released back into the pool through the SEQ_elemRelease facility.
The following code fragment demonstrate the use of Queue management facilities. __________________________
#ifdef SCCS_VER /*{*/
static char sccs[ ] = "%W% Released: %G%";
#endif /*}*/
#include <stdio.h>
#include "estd.h"
#include "queue.h"
#include "seq.h"
typedef struct SomeInfo { 10
struct SomeInfo *next;
struct SomeInfo *prev;
#define DATASIZE 16 /* No Special Significance */
Char data[DATASIZE];
Int len;
} SomeInfo;
typedef struct SomeInfoSeq {
SomeInfo *first;
SomeInfo *last; 20
} SomeInfoSeq;
SEQ_PoolDesc someInfoPool;
SomeInfoSeq someInfoSeq;
Void seqInsert(), seqProcess(), process();
main()
{
static Char *someData = "Some Data";
30
#define POOLSIZE 22 /* 22 is one of my favorite numbers */
someInfoPool = SEQ_poolCreate(sizeof(*someInfoSeq.first),
POOLSIZE);
QU_init(&someInfoSeq);
seqInsert(someData, strlen(someData)+1);
seqProcess();
}
/* 40
* Insert An Element into someInfoSeq
*/
Void seqInsert(data, len)
Char *data;
Int len;
{
SomeInfo *someInfo;
someInfo = (SomeInfo *) SEQ_elemObtain(someInfoPool);
BS_memCopy(data, someInfo->data, len); 50
someInfo->len = len;
QU_insert(&someInfoSeq, someInfo);
}
Void seqProcess()
{
SomeInfo *someInfo;
Char *p;
while ((someInfo = someInfoSeq.first) != 60
(SomeInfo *) &someInfoSeq) {
QU_remove(someInfo);
process(&someInfo->data[0], someInfo->len);
SEQ_elemRelease(someInfoPool, someInfo);
}
}
Void process(data, len)
Char *data;
Int len; 70
{
printf("Processing %s\n", data);
} _________________________________________________________________________________________
_________________________________________________________________________________________
|
This example does not perform any useful task but demonstrates how the Queue manipulation facilities can be used to transfer some data through a queue. Flow of this example program is:
Create a Non-Volatile Queue.
Open an already-existant Non-Volatile Queue.
Close a previously opened or created Non-Volatile Queue.
Delete a Non-Volatile Queue.
Retrieve a pointer to the data in the element at the head of the queue.
Retrieve a pointer to the data in the element at the tail of the queue.
Retrieve a pointer to the data in the element at the specified location in the queue.
Insert an element at the head of a Non-Volatile Queue.
Insert an element at the tail of a Non-Volatile Queue.
Insert an element before the specified element of a Non-Volatile Queue.
Insert an element after the specified element of a Non-Volatile Queue.
Remove the element from the Non-Volatile Queue, which is at the queue’s head.
Remove the element from the Non-Volatile Queue, which is at the queue’s tail.
Remove a specified element from the Non-Volatile Queue
Obtain a handle to the first element of the queue.
Obtain a handle to the last element of the queue.
Obtain a handle to the next element in the queue, given the current element.
Obtain a handle to the previous element in the queue, given the current element.
Compare two NVQ_Element handles for equivilency.
This module provides a uniform interface for handling of software exceptions.
EH_ defines the Exception Handling interface. An abstract description of the service to be performed for each of these facilities is described. The specific actions to be performed by each of these facilities may be altered by the integrator depending on the availability of the operating environment facilities.
The Log Module (LOG_) generates log messages in order to facilitate program monitoring. Within OCP, each module may have its own particular degree of LOG_ tracing enabled at run-time. Developers may also use LOG_ tracing within applications in a similar manner.
Use of the Log Module has several advantages over other traditional tracing methods such as printf() statements:
Loging may also be globally enabled or disabled at compile time without performing any changes to the source code, other than changing the value of a defined value in a single include file.
Global initialization of LOG_ is performed by LOG_init(), which should be called once at the start of a user program and prior to any other LOG_ facilites. LOG_Init() creates two queues of log module information - an active queue and a setup queue. All information about LOG_ users is entered in these queues.
Use LOG_config() to redirect LOG_ output to a device or file other than stdout.
LOG_open() creates a new log module information entry in the active queue. modName is assigned to the entry’s module name member. It’s bit mask setting is determined by the state of the setup queue. If there exists an entry in the setup queue with the same module name (typically made by a call to LOG_SETUP), then the mask is taken from that queue. Otherwise the mask is set to zero (loging disabled).
LOG_open() returns a pointer to the new entry in the active queue in hModCB.
Log messages are generated by the LOG_event() facility, which accepts an argument list string and a variable number of associated arguments.
The Trace Module (TM_) selectively and dynamically generates trace messages in order to facilitate program debugging. Within OCP, each module may have its own particular degree of TM_ tracing enabled at run-time. Developers may also use TM_ tracing within applications in a similar manner.
Use of the Trace Module has several advantages over other traditional tracing methods such as printf() statements:
Tracing may also be globally enabled or disabled at compile time without performing any changes to the source code, other than changing the value of a defined value in a single include file. This allows fully debugged programs to occupy minimal space.
The specific actions actually performed by each of the TM_ facilities may be altered by the integrator depending upon the availability of the operating environment facilities. For instance time stamping may not be available on systems lacking a time-of-day facility.
IMPORTANT NOTE:
The public interface to the Trace Module, as presently defined, consists of a set of all-upper-case macros. The mixed-case function calls are included here for backwards compatibility only and should not be used for new applications.
The macro interface contains hidden #ifdef TM_ENABLED preprocessor directives, thereby eliminating the need to bracket each and every reference to TM_ module facilities with conditional compilation directives. This way, for those applications that require minimal memory usage, the Trace Module can be eliminated entirely by undefining TM_ENABLED in oe.h.
Global initialization of TM_ is performed by TM_INIT(), which should be called once at the start of a user program and prior to any other TM_ facilites. TM_INIT() creates two queues of trace module information - an active queue and a setup queue. All information about TM_ users is entered in these queues.
Use TM_CONFIG() to redirect TM_ output to a device or file other than stdout.
TM_VALIDATE() searches the setup queue for the names of invalid TM_ user modules and deletes them from the queue.
Independent module tracing functionalities may be selected by creating tracing modules through the TM_SETUP() and TM_OPEN() facilities. Within each TM_ user module 16 different tracing types may be selected. Each type is specified by a bit within a bit mask. Trace types range from 0x0000 to 0xffff.
The type of trace information to be displayed, is defined by the owner of the module with the exception of trace type 0 (TM_ENTER). Trace type (TM_ENTER) is by convention used for external function entry tracing.
The criteria for displaying the trace information is that the bitwise and (&) result of the trace statement’s mask and the dynamic tracing mask associated with that module is non-zero.
TM_SETUP() reads an argument list of the form ”MODULE_NAME,BITMASK” and enters the module name and bit mask in either the setup queue or the active queue. If an associated user module has not yet registered itself with TM_ via a call to TM_OPEN(), then a new trace module information entry is made in the setup queue. Otherwise, the information goes in an exisiting entry in the active queue. For example, if args equals "G_,ffff" then a queue entry will be made in which the G_ module is assiged the bit mask 0xffff. If the G_ module has not yet registered itself, then this information will go in the setup queue. Otherwise it will go in the active queue.
TM_OPEN() creates a new trace module information entry in the active queue. modName is assigned to the entry’s module name member. It’s bit mask setting is determined by the state of the setup queue. If there exists an entry in the setup queue with the same module name (typically made by a call to TM_SETUP), then the mask is taken from that queue. Otherwise the mask is set to zero (tracing disabled).
TM_OPEN() returns a pointer to the new entry in the active queue in hModCB.
Trace messages are generated by the TM_TRACE() facility, which accepts an argument list string and a variable number of associated arguments.
The following code fragment demonstrates the usage of the Trace Module. _________________________________
#define TM_ENABLED
#include <stdio.h>
#include "estd.h"
#include "tm.h"
#include "getopt.h"
Void G_init(), T_init(), T_action();
10
TM_ModDesc G_tmDesc;
main(argc, argv)
Int argc;
String argv[ ];
{
Int c;
G_init();
20
while ((c = getopt(argc, argv, "T:t:")) != EOF) {
switch ( c ) {
case 'T':
case 't':
TM_setUp(optarg);
break;
case '?':
default:
exit(1);
} 30
}
TM_trace(G_tmDesc, TM_ENTER, "G_ can be used by orphand modules\n");
T_init();
TM_validate();
T_action();
}
40
Void G_init()
{
TM_init();
G_tmDesc = TM_open("G_");
}
/*
* T_ MODULE
*/
50
TM_ModDesc t_tmDesc;
Void T_init()
{
t_tmDesc = TM_open("T_");
}
Void T_action()
{
static Char *someData = "SOME DATA"; 60
#ifdef TM_ENABLED
TM_trace(t_tmDesc, TM_ENTER, "Action, someData=%s\n",
TM_prAddr(someData));
TM_dumpHex(t_tmDesc, TM_ENTER, "someData",
someData, strlen(someData)+1);
#endif
#ifdef TM_ENABLED
if (TM_query(t_tmDesc, TM_BIT1)) { 70
printf("%s This way we can extend TM_ capabilities\n",
TM_here());
}
#endif
} _________________________________________________________________________________________
_________________________________________________________________________________________
|
In the previous example, if you wished to enable trace options for a module, you would specify, on the command line, the following:
-T ¡module_name¿,¡hex_bits¿
where ¡module_name¿ is a valid OCP module, i.e. SCH_, TMR_, etc, and ¡hex_bits¿ is a hexidecimal value (without a leading ”0x”) specifying which trace bits for that module to enabled.
Multiple sets of -T options may be specified to enable tracing for more than one module.
For example:
General usage dictates that lower-order bits produce less output. The lowest-order bits are often useful even during normal operation of the application. Bits above the first byte are reserved for trace options that provide a lot of output, such as dumping of complete PDUs.
The actual format of a Trace Module output may vary between implementations. The following example, however, is typical of many hosted systems. Each trace contains the source file name and line number from which the trace was generated, followed by a variable number of user data fields. For example:
SAP_ module is responsible for SAP_ address management. An overview of SAP address representation is first provided.
A hierarchical SAP addressing scheme is used. At the Network layer, communicating entities are identified by their NSAP addresses. The Network layer uniquely identifies each of the open systems by their NSAP addresses. Let’s take the case of a layer (N) above the Network layer. In this case, an (N) address consists of two parts:
The (N)-SAP address is a unique representation of a SAP to the OSI environment. The (N)-SAP address selector is a unique representation of a SAP to the service provider. The SAP address selector is communicated to the service provider during the creation of the SAP.
All software layers use a uniform representation for SAP address selectors and SAP addresses. Data abstractions used by software layers to represent SAP addressing are in addr.h.
A SAP address selector in general has the form:
The Network layer SAP address is of particular importance and has the form:
Above the network layer, SAP addresses are constructed on top of N_SapAddr. For example, the Presentation SAP address has the form:
SAP_selGet is facility that converts an ascii representation of a SAP address into a representation suited for SapSelector or SapAddr storage.
SAP_selCmp is a facility that compares two SapSelectors for equality. Zero is returned if the two were identical. A non-zero value is returned if the two were not identical.
Many data communication protocols rely on the availability of timer facilities, yet these facilities are inherently environment dependent. The TMR_ module defines a model and an interface for providing timer facilities to Open C Layers, regardless of the environment, provided that all implementations of the TMR_ module conform to the interface defined here.
A timer is created through the TMR_start() or TMR_create() facilities. Unless the timer is canceled through the TMR_stop() or TMR_cancel() facilities it will expire at the specified time. The time for the expiration of a timer is specified in millisecond relative to now. When the timer expires, the TMR_ module schedules a user-supplied function for synchronous invocation. The granularity of timers is environment specific. Timers created through TMR_start() or TMR_create() are not ”sticky”. If a periodic timer is desired it must be repeatedly re-created.
TMR_start() and TMR_create() both start a new timer and arrange, via the scheduler module (SCH_), for the synchronous invocation of the user-supplied function pfHandler when this timer expires.
A limited amount of user data can be associated with each timer. A pointer to the user specific data buffer is passed as a parameter to pfHandler, i.e.
The timer expiration time is specified in absolute milliseconds relative to present. The time argument must be positive; a negative value or a value of 0 is illegal.
TMR_stop() and TMR_cancel() both halt an active timer and remove it from the timer queue. hTimer is a timer descriptor previously obtained by TMR_create() or TMR_start().
A limited amount of user data can be associated with each timer. The size limit of the user data is implementation specific. Likewise, the semantics of this data are specific to the user and irrelevant to the timer module.
The location of the user data may be obtained by the TMR_getData() facility.
Conversely, given a pointer to some user data, the timer associated with that data can be obtained by the TMR_getDesc() facility.
The timing mechanisms that underlie the TMR_ module are implementation-specific. For instance, in unhosted environments, the TMR_ module can be implemented based on a periodic interrupt. On the other hand, in hosted environments, the TMR_ module can be implemented based on operating system-supplied timing mechanisms such as the Unix signal() facility. In either case the interface described below can be used for initialization and integration of the TMR_ module.
The following code fragment demonstrates the use of the timer facilities. __________________________________
/*
* tmr_ex.c
*
* This simple program demonstrates the use of timer facilities
* for a relative measurement of CPU speed.
*/
#include <stdio.h> 10
#include <signal.h>
#ifdef MSDOS
#include <conio.h>
#endif
#include "estd.h"
#include "eh.h"
#include "tmr.h"
#include "getopt.h" 20
#include "tm.h"
#include "sch.h"
#define CLOCK_PERIOD 1100 /* millisec. Granularity of the timer tick is 54.94msec
for DOS */
#define SAMPLE_PERIOD 2000 /* millisec. Sampling Period */
#define LOAD_FACTOR 10 /* Simulated Load */
typedef enum TmrId {
PURE, 30
IDLE
} TmrId;
typedef struct G_Env {
Char *progName;
/* Application Specific Information */
} G_Env;
#ifdef MSDOS
int G_isrActive; 40
#endif
LgUns pureCpu; /* Measure of “CPU cycles” in one Sample */
LgUns idleCpu; /* Measure of “CPU cycles” in one Sample, under load */
Bool reset = FALSE;
LgUns samplePeriod = SAMPLE_PERIOD;
Int loadFactor = LOAD_FACTOR;
Int clockPeriod = CLOCK_PERIOD;
Bool pureSampling;
PUBLIC G_Env G_env; 50
Void samplePureCpu();
Void IDLE_tmrHandler(void*);
Void IDLE_init(void);
Void IDLE_action(void*);
Void LOAD_init(int), LOAD_action(void*);
Void G_usage(void);
Void G_exit();
60
/*<
* Function:
*
* Description:
*
* Arguments:
*
* Returns:
* 70
>*/
int
main(int argc, char **argv)
{
Int c;
Bool badUsage;
G_env.progName = argv[0];
TM_init();
80
/*
* Process the command line arguments.
*/
badUsage = FALSE;
while ((c = getopt(argc, argv, "T:s:l:h:c:")) != EOF) {
switch (c) {
case 'T':
TM_setUp(optarg);
break;
90
case 's':
samplePeriod = atol(optarg);
break;
case 'l':
loadFactor = atoi(optarg);
break;
case 'c':
clockPeriod = atoi(optarg); 100
break;
case 'h':
default:
badUsage = TRUE;
break;
}
}
if (badUsage) { 110
G_usage();
G_exit(1);
}
printf("\nclock period = %d milliseconds", clockPeriod);
printf("\nsample period = %d milliseconds", (int)samplePeriod);
printf("\nload factor = %d\n", loadFactor);
/*
* OCP Intialization. 120
*/
signal(SIGINT, G_exit); /* ctrl-c handler */
SCH_init(22);
TMR_init(22, clockPeriod);
TMR_startClockInterrupt(clockPeriod);
TM_validate();
/*
* Step 1.
*/ 130
samplePureCpu();
/*
* Step 2.
*/
IDLE_init();
LOAD_init(loadFactor);
#ifdef MSDOS
while (!kbhit()) { 140
#else
while (1) {
#endif
SCH_block();
SCH_run();
}
G_exit(0);
return 0;
} 150
/*<
* Function: G_exit
*
>*/
Void
G_exit(Int code)
{ 160
TMR_stopClockInterrupt();
exit(code);
}
/*<
* Function: G_usage
*
>*/
Void 170
G_usage(void)
{
String usage1 = "[-T module_name,trace_mask] [-s sample_period] [-l load_factor] [-c clock_period]";
printf("\n%s: Usage: %s\n", G_env.progName, usage1);
}
/*<
* Function: samplePureCpu
* 180
* Description:
*
* Count the number of passes made through the scheduler in one sample
* period with nothing else happening. This is the “pure CPU” measure.
*
* while (pureSampling) {
* ++pureCpu;
* SCH_block();
* SCH_run();
* } 190
*
*
>*/
Void samplePureCpu()
{
TMR_Desc tmrDesc;
TmrId *tmrId;
static int n;
200
/*
* Create a timer which expires after ’samplePeriod’ milliseconds
* and which then invokes (via the scheduler) the function ’IDLE_tmrHandler’
*/
tmrDesc = TMR_create((LgInt)samplePeriod,(void*)IDLE_tmrHandler);
tmrId = (TmrId *) TMR_getData(tmrDesc);
*tmrId = PURE;
pureSampling = TRUE;
pureCpu = 0;
210
while (pureSampling) {
/* do this until the timer expires */
++pureCpu;
SCH_block();
SCH_run(); /* expired timers are invoked here */
}
/* Make a note of the value of pureCpu */
printf("%d: pureCpu = %ld\n", n++, pureCpu);
} 220
/*<
* Function: IDLE_tmrHandler
*
* Description:
*
* Repetitively calculate and display the ratio of the idleCpu counter to
* the pureCpu counter. As loadFactor is made larger this ratio will become
* smaller. 230
*
>*/
Void
IDLE_tmrHandler(void *tmrData)
{
TMR_Desc tmrDesc;
TmrId *tmrId;
static int n;
240
#ifdef MSDOS
if (kbhit())
G_exit(0);
#endif
tmrId = (TmrId *)tmrData;
/* Did we get here as the result of samplePureCpu() ?
* If so, just set the flag and exit.
*/ 250
if (*tmrId == PURE) {
pureSampling = FALSE;
n = 0;
/* we must have gotten here as the result of IDLE_init(), so
* perform the calculation
*/
} else if (*tmrId == IDLE) {
if ( idleCpu != 0 )
printf("%d: idleCpu=%ld, idleCpu/pureCpu=%f\n", n++, idleCpu, 260
(float)idleCpu/(float)pureCpu);
else
printf("Oops! idleCpu = %ld\n", idleCpu);
idleCpu = 0;
tmrDesc = TMR_create((LgInt)samplePeriod, (void*)IDLE_tmrHandler);
tmrId = (TmrId *) TMR_getData(tmrDesc);
*tmrId = IDLE;
} else {
EH_oops(); 270
}
}
/*<
* Function: IDLE_init
*
* Description:
*
* Schedule the invocation of IDLE_tmrHandler() after one sample period. 280
* Schedule the immediate invocation IDLE_action().
*
>*/
Void
IDLE_init(void)
{
TMR_Desc tmrDesc;
TmrId *tmrId;
290
idleCpu = 0;
tmrDesc = TMR_create((LgInt)samplePeriod, (void*)IDLE_tmrHandler);
tmrId = (TmrId *) TMR_getData(tmrDesc);
*tmrId = IDLE;
SCH_submit((void*)IDLE_action, (Ptr)0,0,"in IDLE_init()");
}
/*<
* Function: IDLE_action
* 300
* Description:
*
* This function increments the idleCpu counter and then reschedules itself.
* In effect, this is as infinite loop but performed through the scheduler,
* allowing other concurrent action to take place.
*
* As the CPU becomes more heavily loaded with other scheduled activity,
* this function will be invoked less frequently.
>*/
310
Void
IDLE_action(void *argunused)
{
++idleCpu;
SCH_submit((void*)IDLE_action, (Ptr)0,0,"in IDLE_action()");
}
/*<
* Function: LOAD_init 320
*
* Description:
*
* Schedule the immediate invocation of LOAD_action().
*
>*/
Void
LOAD_init(Int load)
{ 330
loadFactor = load;
SCH_submit((void*)LOAD_action, (Ptr)0,0,"in LOAD_init()");
}
/*<
* Function: LOAD_action
*
* Description:
*
* This function hogs some CPU time and then reschedules itself. 340
* Thus, the larger loadFactor, the fewer CPU cycles there are available
* for other activity.
*
>*/
Void
LOAD_action(void *argsunused)
{
Int i;
350
/* Hog some time */
for (i=0; i < loadFactor; ++i) {
}
/* Schedule to do it again */
SCH_submit((void*)LOAD_action, (Ptr)0,0,"in LOAD_action()");
}
360 _________________________________________________________________________________________
_________________________________________________________________________________________
|
DU_ module provides an environment independent interface to a set of facilities providing data unit management capabilities. Propagation of protocol data through the lower layers is accomplished through the DU_ module. Data is not copied as it propagates through Open C Layers. By minimizing memory-to-memory block transfers higher performance is achieved.
When propagating down the layers, initially a data unit is allocated at the top layer. This data unit is stored in a buffer that is large enough to contain all protocol control information that any of the lower layers may prepend to it. (N+1) Open C Layer passes a ”view” of the data unit as an (N)-SDU to (N) Open C Layer. (N) Open C Layer appends its (N)-PCI to the view and delivers its (N)-PDU to (N-1) Open C Layer. This is repeated until the data unit is reached to the bottom layer.
When propagating up the layers, initially the bottom layer allocates the data unit. (N-1) Open C Layer delivers a view of the data unit as an (N-1)-SDU to (N) Open C Layer. (N) layer strips its PCI from the (N)-PDU and delivers the resulting (N)-SDU to (N+1) layer. This is repeated until the data unit is reached to the top layer.
The Data Unit management module is a set of facilities and data abstractions that can create, manipulate, and delete data units. DU_ facilities may be implemented based on SF_mem facilities described in the previews sections. Description of DU_ services in this section is based around such an implementation. This concrete example implementation eases the understanding of the DU_ module interface description.
Four basic data structures are central to DU_ module:
provides a convenient abstraction over this commonly manipulated data structure. One field in struct DU_Elem is a pointer to du_BufInfo . Each DU_Elem contains an area of memory reserved for the user. The size of this user information area is implementation specific. Each DU_Elem is expected to allow for a QU_elem to be casted over it.
An allocated DU_View and a free DU_View are illustrated in the following two figures.
DU_poolCreate is expected to be invoked during initialization of the system. DU_poolCreate creates a data unit pool and returns a pool descriptor. All Open C Layers may rely on the existence of a buffer pool called G_duMainPool. The external declaration of G_duMainPoool is expected to be in g.h. Specified number of du_BufInfo and DU_Views are reserved. Several buffer pools with potentially different buffer sizes may coexists in Open C Environment.
DU_alloc involves the allocation of one du_BufInfo from the specified pool and the allocation of one DU_Elem. DU_Elem is then associated with du_BufInfo. The DU_Elem is a specific view private to the caller. Each DU_Elem is expected to allow for a QU_elem to be casted over it. DU_alloc performs a QU_init to the allocated DU_View . The amount of data specified for allocation, must be smaller than the maximum buffer size associated with the specified pool. The remaining space (maximum buffer size minus the requested buffer size) can be accessed through the DU_prepend facility.
The following figure illustrates the status of DU_ structures after a DU_alloc.
DU_link provides an additional view over an existing du_BufInfo . The DU_Link facility enables the user to maintain more than one view of the same buffer.
DU_free deallocates the specified DU_View . If no other views of the buffer exists then the du_BufInfo associated with DU_View is also deallocated. du_BufInfo is preserved for as long as there is at least one DU_View referring to it. The number of references to each du_BufInfo is maintained in refCount field. Upon allocation this refCount is set to 1. Each DU_link increments refCount by 1. Each DU_free decrements refCount by 1.
The following figure demonstrates the status of DU_ structures after DU_free invocation.
The external interface to the buffer management module is described below.
A buffer pool descriptor (DU_PoolDesc) must be obtained before any views that are associated with it can be allocated. The return value of this function must be used in future usage of DU_ facilities referring to this pool. bufSize specifies the maximum size of the buffers that can be allocated from this pool. nuOfBufs specifies how many du_BufInfos will be available for allocation. nuOfBufs specifies how many DU_Views will be available for allocation or linkage.
Open C Layers can assume the existence of one buffer pool named G_duMainPool. Therefore:
is expected of G_ module.
DU_alloc, allocates a new du_BufInfo structure from the specified pool pool. pool must have been obtained through DU_poolCreate . A DU_Elem is allocated and initialized according to the specified nuOfBytes. A reference to DU_Elem is returned to the user as a DU_View. NULL is returned if no du_BufInfo were available or if no DU_View were available or if the nuOfBytes argument was larger than the maximum buffer size associated with pool. du_BufInfo.refCount is set to 1.
DU_link provides an additional view over an existing du_BufInfo. view must have been obtained through DU_alloc. NULL is returned if there are no DU_Views remaining. du_BufInfo.refCount is incremented.
DU_free deallocates view. view must have been obtained through DU_alloc or DU_link. du_BufInfo.refCount is decremented. If refCount becomes zero, the du_BufInfo is no longer in use and du_BufInfo is returned to the buffer pool from which it was originally allocated.
DU_prepend facility prepends bytes at the beginning of the specified data unit. It is often used to create space for the Protocol Control Information (PCI) to be prepended by Open C Layers. It is the responsibility of the user to to assure the availability of space. DU_prepend does not do any range checking for excessive prepends. The appended bytes are not initialized to any specific value.
DU_strip facility strips (removes) nuOfBytes bytes from the beginning of the view. It is often used to remove protocol control information from PDUs as they propagate up the layers.
DU_adjust facility adjusts (moves) the end of buffer pointer by nuOfBytes bytes. The effect of DU_adjust is not private to view. All views of DU_Elem.bufInfo are affected.
DU_data facility returns a pointer to the first byte of data as viewed through the specified view. If view is NULL, NULL is returned.
DU_size facility returns the current length of the buffer as observed by the specified view. If view is a NULL pointer, zero is returned.
In order to access the user specific information associated with a view two facilities are provided. Given a view, DU_vToUinfo delivers a pointer to the area of memory that can be used by the user. Given a pointer to an area of memory that was previously obtained through DU_vToUinfo, DU_uInfoToV can be used to obtain the DU_View associated with the uInfo .
The following code fragment demonstrates the use of buffer management facilities. __________________________
/*
* This example program demonstrates the propagation of a
* Data Unit through Open C Layers;
* UPPER_, N_ and LOWER_.
*/
#include "estd.h"
#include "eh.h"
#include "du.h"
10
DU_PoolDesc *G_duMainPool; /* Buffer Pool */
#define MAXBUFSIZE 1548 /* no special significance */
#define NUOFBUFS 22 /* no special significance */
#define NUOFVIEWS (2*NUOFBUFS) /* no special significance */
main()
{
static char n_sdu[ ] = "(N)-SDU";
DU_View view;
20
G_duMainPool = DU_poolCreate(MAXBUFSIZE, NUOFBUFS, NUOFVIEWS);
if ( ! G_duMainPool ) {
EH_fatal("G_duMainPool");
}
UPPER_init(&n_sdu[0], (Int) sizeof(n_sdu));
}
UPPER_init(data, size)
Char *data;
Int size; 30
{
/* Allocate a Data Unit */
view = DU_alloc(G_duMainPool, size);
if ( ! view ) {
EH_fatal("view");
}
BS_memCopy(data, DU_data(view), size);
/* deliver n_sdu to (N) layer */
N_dataReq(view); 40
}
N_dataReq(view)
DU_View view;
{
static char n_pci[ ] = "(N)-PCI ";
#define NPCI_SIZE (sizeof(n_pci)-1) /* Don’t want the ’\0’ */
/* Add (N) layer Protocol Control Information
* and pass it down to layer below. 50
*/
DU_prepend(view, NPCI_SIZE);
BS_memCopy(n_pci, DU_data(view), NPCI_SIZE);
LOWER_dataReq(view);
}
LOWER_dataReq(view)
DU_View view;
{ 60
printf("(N-1)-SDU = %s\n", (char *) DU_data(view));
DU_free(view);
}
_________________________________________________________________________________________
_________________________________________________________________________________________
|
Upon execution this example program produces:
The flow of this example program is:
Allocate a new buffer. The buffer will contain, initially, one segment which is large enough to hold (at least) the specified number of octets.
If the minimum size is not known, zero may be passed, and a default zero-size buffer segment will be allocated, initially.
Free the specified buffer and all of its associated segments.
Prepend a single octet to a buffer. If there is insufficient space in the current segment, a new segment is allocated, of the default size.
Get the next octet in the buffer.
Return the most recently retrieved octet to the input buffer stream.
This function allocates a new buffer segment, and assigns the specified string to that segment. The internal buffer pointers are left in such a state as to allow prepending additional octets. Any additional octets prepended will cause a new buffer segment to be created, as the segment for this prepended chunk takes up its own whole segment.
This function prepends one buffer to another. The internal buffer pointers are left in such a state as to allow prepending additional octets.
When parsing, this function returns a pointer to the next chunk of the PDU. The size of the chunk is determined by the value of *pChunkLength when this function is called, and by the amount of data remaining in the current (or first non-zero-length) segment. A chunk of no more then the requested chunk length will be provided.
Append a string to the end of a buffer. This function is primarily for use when receiving data from the network, which is to later be parsed.
This function appends one buffer to another. The internal buffer pointers are left in such a state as to allow appending additional octets.
Reset the internal buffer pointers for another parse of the buffer.
Copy an entire buffer. All data is copied, so string data is independent of the source buffer (as opposed to the way BUF_cloneBufferPortion() works).
Clone a portion of a buffer. A new buffer handle is provided, that contains a (possibly) partial list of the segments from the cloned buffer. The new buffer and original buffer may each be freed or manipulated independently, with the caveat that the String Data pointed to by the segments is the same in both buffers. Any modifications to the data within the cloned buffer that is in the common portion to the original buffer will be reflected in both buffers.