|
by Bill Ashby, IBM TPF Development
When we designed TPF ISO-C support, we spent a lot of effort
making it as compatible as possible with TARGET(TPF) without
compromising our basic goal of delivering support for a standard C
language implementation. One of the areas that we redesigned for
ISO-C is the function call stack. This redesign removed the
restriction of less than 4 K (4 KB) of automatic data per function
and allowed us to implement more efficient stack overflow handling
than TARGET(TPF) allows. Writable static storage (or RENT static in
ISO-C terms), works about the same way in ISO-C as in TARGET(TPF),
but some changes were required to remove the TARGET(TPF) 4 K size
restriction on static data for a single program segment.
This redesign introduced some subtle changes in the behavior of
the stack and static, which can only show themselves in interfaces
to routines that run in 24-bit addressing mode, in system virtual
memory (SVM), or that use real addresses. However, these
incompatibilities have required some code changes in a couple of
migration efforts recently, so it appears that raising awareness
about the differences between the two implementations may help
users recognize the kinds of problems that they might run into if
they are working with code that handles these kinds of
interfaces.
Stack Implementations TARGET(TPF) gets memory for its
function call stack by allocating 4 K working storage blocks. The
first few bytes of each block are used to link the stack together.
This has several consequences:
- TARGET(TPF) stack frames are not always adjacent in memory, and
there is no particular ordering to their addresses except that
stack frames in the same 4 K block are allocated from lower
addresses first.
- Each TARGET(TPF) stack frame must fit within a single 4 K block
minus the chaining overhead.
- Every TARGET(TPF) stack frame is below the 16-MB boundary
- Every TARGET(TPF) stack frame is contained in a single 4 K
hardware page.
The ISO-C stack is in a reserved area of virtual memory above the
end of usable real memory. Its virtual address space and segment
tables are allocated during CTIN, but no real frames are allocated
for it until an ISO-C run-time environment is initialized, when the
initial stack allocation (two pages, or 8 K, by default) is
allocated. Additional frames are added as needed; pages that back
the ISO-C function call stack are not deallocated until the ECB
exits, so the amount of real storage allocated to the ISO-C
function call stack can only increase. In contrast to the
TARGET(TPF) stack:
- The entire ISO-C stack is contiguous in its ECB virtual address
(EVA), the addresses of the active stack frames are all in
ascending order, and there is no space between adjacent stack
frames.
- The size of an ISO-C stack frame is limited only by the
remaining virtual address space for the entire stack and the
availability of real pages.
- Every ISO-C stack frame is above the 16-MB boundary.
- ISO-C stack frames may span two or more hardware pages.
Therefore, they are not necessarily contiguous either in real or in
system virtual memory.
Static Storage Implementations TARGET(TPF) and ISO-C both
implement static storage by allocating and initializing a static
block the first time they enter a program segment that defines
writable static storage. TARGET(TPF) uses working storage blocks
for its static blocks and ISO-C uses hardware pages mapped into the
heap virtual address space. Therefore, the differences between
TARGET(TPF) and ISO-C static are similar to the differences between
their function call stacks:
- Each TARGET(TPF) static block must fit within a single 4 K
block minus some overhead. The size of an ISO-C stack block is
limited only by the remaining virtual address space for the heap
and the availability of real pages.
- Every TARGET(TPF) static block is below the 16-MB boundary.
Every ISO-C static block is above the 16-MB boundary.
- Every TARGET(TPF) static block is contained in a single 4 K
hardware page. ISO-C static blocks may span more than one hardware
page and be noncontiguous in SVM or real memory.
In addition to writable static, ISO-C allows you to define
nonwritable static storage (or NORENT static). Because nonwritable
static is defined in the program machine code, and ISO-C programs
are stored above the 16-MB line (unless the 31-bit core resident
program area (CRPA) overflows; a core resident ISO-C program can
then be loaded into the 24-bit CRPA) and may be larger than 4 K,
nonwritable ISO-C static behaves (for the purpose of this
discussion) in the same way as ISO-C writable static except that it
is key protected.
Problem Scenario 1: Interfacing with Routines That Run in
24-Bit Mode All C programs that run on TPF 4.1, regardless of
whether they are TARGET(TPF) or ISO-C, must be allocated as running
in 31-bit mode. However, because the TARGET(TPF) stack and static
blocks reside below the 16-MB line, pointers to data contained in
them can be passed to routines that require 24-bit addressing.
If a program that does this migrates to ISO-C, the pointers to
data in its stack or static will not be valid in 24-bit mode,
causing a CTL-000004 addressing exception or other indeterminate
behavior if bits 8_31 of the pointers accidentally point to
something. This most likely will happen if a C program enters a BAL
segment that is allocated as a 24-bit program.
There are two ways to successfully complete this migration:
- Change the called routine so that it runs in 31-bit mode
or
- Move the data below the 16-MB line before the call.
Problem Scenario 2: Interfacing with Services That Run in SVM
or in Real Memory If you pass data in ISO-C stack or static
storage to a CP service that runs in SVM or executes hardware
instructions that require real addresses, the data may be truncated
where it crosses a hardware page boundary. For example, you could
write a C function that builds a list of data that is passed to a
CP service that runs in SVM, or a function that builds a CCW chain
for a hardware level I/O routine. In either case, if you build the
data in automatic or static storage, it will probably work in
TARGET(TPF) but will fail when the code is migrated to ISO-C if the
data crosses a real page boundary. (Note that this type of failure
would be rare because of the low probability that small amounts of
data will cross a page boundary.)
There are two general approaches to solving this problem:
- If the called routine gets control in EVM, change it so that it
copies the data passed to it into storage that it knows will be
contiguous in SVM or real storage.
- Change the caller to build the data it is going to pass in
storage that it knows will be contiguous in SVM or real storage,
such as a working storage block or 4 K frame.
By the way, it is possible to define C storage that does not cross
a real page boundary. The following realauto() macro will allocate
up to 2 K of automatic storage that is guaranteed to be contained
in a single real page. Note that, although this code is written to
compile with C/370 without errors or warnings, it is not portable.
/**********************************************************************/
/* Macro: realauto() */
/* */
/* Purpose: Allocate up to 2K in automatic storage that is */
/* guaranteed not to cross a real hardware page boundary. */
/* */
/* Macro call: realauto(type, identifier, storename); */
/* */
/* type - C data type of storage required. */
/* */
/* identifier - the name of the identifier that will */
/* point to the usable storage */
/* allocation. realauto() declares */
/* this as */
/* */
/* type *identifier; */
/* */
/* storename - name for realauto() to use to */
/* generate a storage definition which */
/* will include the required storage. */
/* This name should not be used except */
/* to call the realauto() macro. */
/* */
/* WARNING: The following includes graphic depictions of explicit,
*/
/* non-portable hardware- and compiler-specific C code. */
/* Viewer discretion is advised. */
/**********************************************************************/
#define realauto(type, identifier, storename) \
double storename[(sizeof (type) + 7) / 4 - 1] = { 0.0 }; \
type *identifier = \
((int)storename & 0x00000FFF) + sizeof (type) > 0x1000
? \
(type *)((int)storename + 0x00000FFF & 0xFFFFF000) : \
(type *)(int)&storename
Here is an example of how you could use the realauto() macro:
#include <string.h> /* memset() */
/**********************************************************************/
/* Declare types of data structures that must be contiguous in
real */
/* memory. */
/**********************************************************************/
typedef struct {
unsigned long hardware1;
unsigned long hardware2;
unsigned long hardware3;
unsigned long hardware4;
} hardwareList;
typedef char hardwareBlock[0x400];
typedef unsigned short parameterList[7];
/**********************************************************************/
/* Declare routine which requires structures that are contiguous
in */
/* real memory. */
/**********************************************************************/
extern void foo(hardwareList *);
/**********************************************************************/
/* The following routine defines 3 structures that are contiguous
*/
/* in real memory, and calls the routine declared above. */
/**********************************************************************/
void bar(void)
{
realauto(parameterList, plistPtr, noname0);
realauto(hardwareList, listPtr, noname1);
realauto(hardwareBlock, blockPtr, noname2);
memset(plistPtr, 0, sizeof *plistPtr);
(*plistPtr)[0] = 0x0123;
(*plistPtr)[1] = 0x4567;
listPtr->hardware1 = 0x89AB0000 + sizeof *plistPtr;
listPtr->hardware2 = (unsigned long)plistPtr;
listPtr->hardware3 = 0xCDEF0000 + sizeof *blockPtr;
listPtr->hardware4 = (unsigned long)blockPtr;
foo(listPtr);
}
You can use a similar approach with static or heap storage.
|