Last modified 3rd May 2005 The occam-pi C interface |
This page documents the C interface (CIF) for the KRoC occam-pi system. This allows processes written in C to run alongside and communicate with occam-pi processes, using an extended version of the CCSP/MESH API, based on the original INMOS C API.
[ C processes | process control | communication | mobiles | time | priority | low-level process control | occam-pi features | miscellaneous | external calls | index ]
The C interface for the KRoC occam-pi system attempts to follow the existing INMOS C API, so existing (and probably quite old) C applications can be ported with relative ease. Applications which previously used CCSP should also be portable with minimum effort. The existing C API has been extended to cover the new features of occam-pi (e.g. mobile data, channels, processes and barriers).
Starting a C process from occam requires an externally called allocation routine, that returns a Process * to occam as an INT. The process is then run (from occam) using a small stub routine, cifccsp.startprocess (INT addr), that returns only when the C process terminates/returns. The various CIF example programs ("cif/examples/" in the KRoC distribution) do allocation this way, in some cases cleaning up afterwards (if the C process is expected to terminate). For example:
#INCLUDE "cifccsp.inc" #PRAGMA EXTERNAL "PROC C.example.init (CHAN BYTE kyb?, scr!, err!, RESULT INT addr) = 0" #PRAGMA EXTERNAL "PROC C.example.cleanup (INT addr) = 0" PROC example (CHAN BYTE kyb?, scr!, err!) INT addr: SEQ C.example.init (kyb?, scr!, err!, addr) cifccsp.startprocess (addr) C.example.cleanup (addr) :
The two external calls are declared starting "C.", indicating that these are ordinary external C calls -- i.e. those functions execute in the stack of the KRoC run-time system and must not call any CIF functions that require a process context. A typical implementation of the above C functions could be:
#include "cifccsp.h" /* actual functions */ static void real_example_init (Channel *kyb, Channel *scr, Channel *err, Process **pptr) { *pptr = ProcAlloc (demo_process, 65536, 3, kyb, scr, err); return; } static void real_example_cleanup (Process **pptr) { ProcAllocClean (*pptr); *pptr = NULL; return; } /* interface glue */ void _example_init (int *ws) { real_example_init ((Channel *)(ws[0]), (Channel *)(ws[1]), (Channel *)(ws[2]), (Process **)(ws[3])); } void _example_cleanup (int *ws) { real_example_cleanup ((Process **)(ws[0])); }
The other required function is the actual "demo_process" process. Unlike the C functions above, this runs in a process context. I.e. it has its own stack (65536 bytes) and may communicate on the 3 channels passed to it. A simple hello-world process could be implemented using:
static void demo_process (Process *me, Channel *kyb, Channel *scr, Channel *err) { static char *message = "hello, C world!\n"; char *ch; for (ch = message; *ch != '\0'; ch++) { ChanOutChar (scr, *ch); } }
This simple process only uses ChanOutChar(); the various other CIF examples are more extensive, covering: building and running process networks; communication with parallel occam-pi processes; external C calls; blocking system-calls; time and timeouts; mobile channel-type parameters, communication and shared-end handling; plus a variety of others. To compile the above C fragment, a command similar to the following should be used, assuming the above C code is in a "demo.c" file:
bash$ gcc -O2 -Wall -fomit-frame-pointer -fno-defer-pop -c -o demo.o demo.c
The corresponding occam-pi code (e.g. in "mydemo.occ") would then be compiled with:
bash$ kroc mydemo.occ demo.o -lcif
The following sections describe the API for C processes, grouped by functionality. In most cases, these "functions" are pre-processor macros that translate to C function calls or inline assembler, defined in the "cifccsp.h" file.
These functions provide mechanisms to allocate, free and run processes. The functions ProcAlloc(), ProcInit(), ProcParam(), ProcAllocClean() and ProcInitClean() may be called from outside of a CIF process context.
Process *ProcAlloc (void (*func)(void), int stacksize, int nargs, ...)
Allocates and initialises a new C process with the entry-point func, that takes nargs parameters (given as var-args). A stack size of stacksize bytes is allocated for the process. On success, a Process * pointer is returned, NULL on failure.
Process *ProcInit (void (*func)(void), int *stackptr, int stacksize, int nargs, ...)
Allocates and initialises a new C process with the entry-point func, that takes nargs parameters (given as var-args). Instead of allocating a new stack, the user-specified stack at stackptr for stacksize bytes is used. On success, a Process * pointer is returned, NULL on failure.
void ProcParam (Process *p, ...)
Changes the parameters of the process p. The same number of user function-arguments as given to ProcAlloc() or ProcInit() must be given here. Note that this should only be used on a process that has been initialised, but not yet executed.
void ProcAllocClean (Process *p)
Frees a process p allocated using ProcAlloc().
void ProcInitClean (Process *p)
Frees a process p allocated using ProcInit().
void ProcPar (Process *first, ...)
Runs the NULL-terminated list of processes (starting with first) in parallel. Like the occam PAR, this only terminates once all sub-processes have terminated. For example:
ProcPar (p, q, NULL);
void ProcPriPar (Process *first, int priority, ...)
Runs the NULL-terminated list of processes and priorities (starting with first at priority priority) in parallel. Like the occam PAR, this only terminates once all sub-processes have terminated.
void ProcParList (Process **plist)
Similar to ProcPar, except that the processes are given in the NULL-terminated array plist. For example:
/* replicated PAR equivalent */ int n = 4; int i; Process **list = (Process **)DMemAlloc ((n + 1) * sizeof (Process *)); /* allocate */ for (i=0; i<4; i++) { list[i] = ProcAlloc (replicated_process, 4096, 1, i); } list[i] = NULL; /* run */ ProcParList (list); /* clean-up */ for (i=0; i<4; i++) { ProcAllocClean (list[i]); } DMemFree (list);
void ProcPriParList (Process **plist, int *priorities)
Similar to ProcPriPar, except that the processes are given in the NULL-terminated array plist, with corresponding priorities in the array priorities.
Runs the process p in parallel with the invoking process. This corresponds (approximately) to the FORK in occam-pi. When the process terminates, it is automatically free'd. For example:
Process *p = ProcAlloc (client_process, 4096, 0); ProcFork (p); /* p no longer valid */
These functions provide mechanisms to allocate, free and communicate over channels. Because occam-pi channels only consist of a single word, declarations of Channels may be made outright, instead of declaring a pointer and using ChanAlloc()/ChanAllocClean().
Allocates a new channel.
void ChanAllocClean (Channel *c)
Frees a channel allocated with ChanAlloc().
Initialises a channel. For example:
Channel c; ChanInit (&c);
void ChanInChar (Channel *c, unsigned char *cptr)
Inputs a single byte from the channel c into the byte pointed at by cptr.
void ChanInInt (Channel *c, unsigned int *iptr)
Inputs a single integer from the channel c into the integer pointed at by iptr.
void ChanIn (Channel *c, void *addr, int length)
Inputs length bytes from the channel c to address addr.
void ChanOutChar (Channel *c, unsigned char ch)
Outputs the single byte ch on channel c. For example:
void out_string (char *str, Channel *out) { for (; *str != '\0'; str++) { ChanOutChar (out, *str); } return; }
void ChanOutInt (Channel *c, unsigned int v)
Outputs the single integer v on channel c.
void ChanOut (Channel *c, void *addr, int length)
Outputs length bytes from address addr to the channel c.
int ProcAlt (Channel *first, ...)
This performs an occam ALT over the NULL-terminated list of channels starting with first. The index of the selected channel is returned (first corresponds to a return value of zero). For example:
void merge2int (Channel *in0, Channel *in1, Channel *out) { for (;;) { int idx, v; idx = ProcAlt (in0, in1, NULL); switch (idx) { case 0: ChanInInt (in0, &v); ChanOutInt (out, v); break; case 1: ChanInInt (in1, &v); ChanOutInt (out, v); break; default: SetErr (); } } }
int ProcAltList (Channel **list)
This performs an occam ALT over the NULL-terminated list of channels pointed to by list. The index of the selected channel is returned.
These functions provide for mobile communication. KRoC supports various types of static and dynamic mobile; dynamic mobiles are allocated and free'd using the dynamic memory allocation instructions DMemAlloc() and DMemFree(), described below. Dynamic mobile communication and assignment is strictly one-way in the implementation: processes that output a dynamic mobile must consider it undefined afterwards (unless the mobile is shared and the reference-count adjusted appropriately); processes that input dynamic mobiles should ensure any previously held dynamic mobile is free'd appropriately. The various mobile types currently supported by KRoC are:
Static mobiles: fixed-size mobiles (typically MOBILE RECORD structures), that are always allocated. Communication and assignment in the same memory-space are implemented by pointer-swapping. The ChanMIn()/ChanMOut() functions are used for static mobile communication.
Dynamic mobile arrays: run-time sized mobiles (e.g. MOBILE []BYTE arrays). In memory, the dimension counts follow the data pointer at increasing addresses. 1-dimensional arrays are communicated using ChanMIn64() and ChanMOut64(). Arrays of higher dimensions are communicated using ChanMInN() and ChanMOutN().
Dynamic mobile channel-types: a bundle of channel-words with a reference-count, optional type description and hook fields, and optional client-end and server-end semaphores (for handling shared ends). Communication of these is done using the ChanMInN() and ChanMOutN() functions with a word-count of 1. The semaphore operations are described below.
Dynamic mobile barriers: the occam-pi MOBILE BARRIER type, used for process synchronisation. These are always shared in occam-pi, C code can choose to output and keep (incrementing the reference-count), or output and lose (not touching the reference-count). Mobile barriers are comunicated using the ChanMInN() and ChanMOutN() functions with a word-count of 1. The C structure MBarrier is defined by CIF, reflecting the structure of the corresponding occam-pi type. Barrier-specific functions are described below.
Dynamic mobile processes: occam-pi mobile process variables. Communication of these is done using the ChanMInN() and ChanMOutN() functions with a word-count of 1. The C interface does not yet support creation, activation or suspension of mobile processes -- they may only be communicated through it currently.
The communication functions are:
void ChanMIn (Channel *c, void **pptr)
Inputs a static mobile from channel c whose address is pointed to by pptr. Due to the way static mobile communication operates, the mobile pointer (*pptr) must be valid.
void ChanMOut (Channel *c, void **pptr)
Outputs a static mobile whose address is pointed to by pptr to channel c. Due to the way static mobile communication operates, the mobile pointer (*pptr) must be valid.
void ChanMIn64 (Channel *c, long long *ptr)
Inputs a 2-word dynamic mobile from channel c to address ptr. The application should ensure that any dynamic mobile previous stored there is free'd appropriately. This function is only used for 1-dimensional dynamic mobile arrays.
void ChanMOut64 (Channel *c, long long *ptr)
Outputs a 2-word dynamic mobile from address ptr to channel c. After output, the application should consider the dynamic mobile to be undefined. This function is only used for 1-dimensional dynamic mobile arrays.
void ChanMInN (Channel *c, void *ptr, int words)
Inputs an n-word dynamic mobile from channel c to address ptr. The number of words specified by words represents the size of the communication, not the size of the mobile. The application should ensure that any dynamic mobile previously stored is free'd appropriately.
void ChanMOutN (Channel *c, void *ptr, int words)
Outputs an n-word dynamic mobile from address ptr to channel c. The number of words specified by words represents the size of the communication, not the size of the mobile. The application should consider the dynamic mobile output undefined after the communication.
The following functions are provided for handling time in CIF. These follow the occam-pi representation of time, a signed 32-bit integer that counts in micro-seconds (giving a range of approximately 35 minutes in the past or future). The CIF header file "cifccsp.h" defines a Time type, unless the pre-processor define "CIF_NO_TIMEDEF" is defined -- necessary when combining CIF with code that defines a Time type for itself, that must not be used with CIF time-related functions if a different type. Careful separation of code between source files can overcome such issues.
Reads the current time (in micro-seconds) into the (int-sized) Time variable pointed to by timep.
Suspends the current process for delay micro-seconds. For example:
/* sleep for 100ms */ ProcAfter (100000);
void ProcTimeAfter (Time after)
Suspends the current process until the time specified by after is reached. Due to the way time is handled in occam-pi, this can return immediately (after represents a time in the past).
Time ProcTimePlus (Time t1, Time t2)
Returns t2 added to t1, with wrap-around.
Time ProcTimeMinus (Time t1, Time t2)
Returns t2 subtracted from t1, with wrap-around.
The following functions deal with process priority in occam-pi. The current run-time supports 32 levels of priority, 0 being the highest (and default priority), and 31 the lowest. Process priority scheduling is strict, in that a higher priority process will always run before a lower priority process. However, event priority where used (PRI ALT in occam-pi, and the default behaviour of ProcAlt()) overrides process priority. CIF provides additional ProcProcPriAlt() functions (described here) which are aware of process priority, giving it precedence over event priority.
Returns the priority of the current proess.
Sets the priority level of the current process to priority. The priority is automatically adjusted to be within the supported range of the run-time system.
int ProcProcPriAlt (Channel *first, ...)
This performs a process-prioritised ALT over the NULL-terminated list of channels starting with first. The index of the selected channel is returned (first corresponds to a return value of zero).
int ProcProcPriAltList (Channel **list)
This performs a process-prioritised ALT over the NULL-terminated array of channels pointed to by list. The index of the selected channel is returned.
These functions provide for low-level process control, i.e. starting and stopping processes, and run-queue manipulation. General code should not need to use these, but they are provided for completeness and are required for certain things.
This schedules the process p for execution. This is typically used to resume a process that was stopped using ProcStop().
void ProcPriRun (Process *p, int priority)
This schedules the process p for execution with priority priority. This is typically used to resume a process that was stopped using ProcStop() at a specific priority level.
This deschedules the current process. A descheduled process can be resumed with either ProcRun() or ProcPriRun(). For example:
Process *waiting = NULL; void rendezvous (Process *me) { if (!waiting) { waiting = me; ProcStop (); /* deschedule */ } else { ProcRun (waiting); /* reschedule blocked process */ waiting = NULL; } return; }
This stores the current run-queue front-pointer and back-pointer into the 2-element array pointed to by pqptr.
This sets the run-queue front-pointer to the process p. This is generally used in conjunction with StLB() to update the current run-queue.
This sets the run-queue back-pointer to the process p. This is generally used in conjunction with StLF() to update the current run-queue.
void SavePri (int priority, Process **pqptr)
This stores the run-queue front-pointer and back-pointer for the specified priority level into the 2-element array pointed to by pqptr.
void StPriF (int priority, Process *p)
This sets the run-queue front-pointer for the specified priority level to p. This is generally used in conjunction with StPriB() to update the run-queue for a specific priority level.
void StPriB (int priority, Process *p)
This sets the run-queue back-pointer for the specified priority level to p. This is generally used in conjunction with StPriF() to update the run-queue for a specific priority level.
These functions provide access to a range of occam-pi specific language/run-time features.
This allocates a dynamic memory block of size bytes. Memory allocated this way is suitable for most purposes in KRoC, e.g. dynamic mobiles, static mobiles (assuming the block is never free'd), process stacks, etc. For example:
void dynmob_writestr (Channel *out, const char *str) { void *dynmob[2]; int slen = strlen (str); dynmob[1] = (void *)slen; dynmob[0] = DMemAlloc (slen); memcpy (dynmob[0], str, slen); ChanMOut64 (out, (long long *)dynmob); return; }
This frees a block of dynamic memory.
This performs an extended rendezvous enable on channel c. The operation of this is to wait until the channel is ready then return, leaving an outputting process blocked in the channel. Note that this may only be used with the input end of a channel.
void ChanXIn (Channel *c, void *ptr, int bytes)
This performs an extended input of bytes bytes from the channel c to the memory pointed to by ptr. Data is copied from the outputting process, but that process is not resumed. Note that the channel must be ready, guaranteed when waited for explicitly using ChanXAble() or selected by an alternative (ProcAlt(), etc.).
void ChanXMIn (Channel *c, void **pptr)
This performs an extended static-mobile input from the channel c to the memory pointed to by pptr. Due to the way that static mobile communication works, *pptr must be a valid pointer. Note that the channel must be ready, guaranteed when waited for explicitly using ChanXAble() or selected by an alternative (ProcAlt(), etc.).
void ChanXMIn64 (Channel *c, long long *ptr)
This performs an extended dynamic-mobile 64-bit input from the channel c to the memory pointed to by ptr. Note that the channel must be ready, guaranteed when waited for explicitly using ChanXMin64() or selected by an alternative (ProcAlt(), etc.).
void ChanXMInN (Channel *c, void *ptr, int words)
This performs an extended n-word dynamic mobile input of words words from channel c to the memory pointed to by ptr. Note that the channel must be ready, guaranteed when waited for explicitly using ChanXAble() or selected by an alternative (ProcAlt(), etc.).
This finishes an extended input on the channel c, rescheduling the blocked outputting process. For example:
void integer_tap (Channel *in, Channel *tap, Channel *out) { for (;;) { int x; ChanXAble (in); ChanXIn (in, &x, sizeof (int)); ChanOutInt (out, x); ChanXEnd (in); ChanOutInt (tap, x); } }
This function initialises the channel-type semaphore pointed to by s. This is used when creating and initialising shared channel-ends in CIF.
This function claims the channel-type semaphore pointed to by s, used when communicating on shared channel-ends. Note that an attempt to claim an already claimed semaphore will result in deadlock.
This function releases the channel-type semaphore pointed to by s, used when communicating on shared channel-ends. Note that the semaphore must already be claimed by this process, otherwise undefined behaviour may result.
MBarrier *MBarrierAlloc (void)
This function allocates, initialises and return a new mobile barrier The barrier is initialised with one process enrolled (i.e. the current process).
void MBarrierAllocClean (MBarrier *b)
This function frees resources associated with the mobile barrier bafter use. Note that it is up to the application to ensure correct manipulation of reference counts within shared channel-ends when communicating or assigning them.
void MBarrierInit (MBarrier *b)
This function initialises the existing mobile barrier b to its default state (with one, i.e. the current, process enrolled).
void MBarrierEnroll (MBarrier *b, int count)
This function enrolls count processes on the mobile barrier b. This should be used when communicating or assigning-from a mobile barrier, in addition to reference-count manipulation (the responsibility of the application); and when going parallel, enrolling one less than the number of parallel sub-processes.
void MBarrierResign (MBarrier *b, int count)
This function resigns count processes from the mobile barrier b. This should be used before a reference to an existing mobile barrier is lost or overwritten, in addition to reference-count manipulation (the responsibility of the application); and after a parallel, resigning one less than the number of parallel sub-processes.
void MBarrierSync (MBarrier *b)
This function synchronises on the mobile barrier b. Processes block until the last enrolled process synchronises, at which point the synchronisation is complete and blocked processes rescheduled.
These are functions that do not fit neatly into the above categories.
Reschedules the current process.
This generates a run-time error, reporting the filename and line-number where the SetErr() call was made.
Four macros are provided to handle external (blocking and non-blocking) C calls in CIF. When POSIX threads are enabled, external C calls to certain library functions must be wrapped by the EXTERNAL_CALL/EXTERNAL_CALLN macro. This ensures that the call executes within a genuine POSIX thread stack, otherwise certain functions will fail. External calls, both blocking and non-blocking, may not use process-context dependant CIF calls. In the case where POSIX threads are not enabled, the EXTERNAL_CALL/EXTERNAL_CALLN macros do nothing. Note that if the return value of an external call is required (and POSIX threads are enabled), suitable glue-code will be required to capture it.
Calls the function func with no arguments.
Calls the function func with the given arguments.
Calls the function func with no arguments in a separate thread (it may safely block in the OS kernel without blocking execution of occam-pi/CIF processes).
Calls the function func with the given arguments in a separate thread.
The following table gives an alphabetised index of the above CIF functions/macros.
BLOCKING_CALL | external blocking C call. |
BLOCKING_CALLN | external blocking C call. |
ChanAlloc | allocates a channel. |
ChanAllocClean | frees a channel. |
ChanIn | generic channel input. |
ChanInChar | byte channel input. |
ChanInInt | integer channel input. |
ChanInit | initialises a channel |
ChanMIn | static mobile input. |
ChanMIn64 | 64-bit dynamic mobile input. |
ChanMInN | generic dynamic mobile input. |
ChanMOut | static mobile output. |
ChanMOut64 | 64-bit dynamic mobile output. |
ChanMOutN | generic dynamic mobile output. |
ChanOut | generic channel output. |
ChanOutChar | byte channel output. |
ChanOutInt | integer channel output. |
ChanXAble | extended input enable. |
ChanXEnd | extended input end. |
ChanXIn | extended input. |
ChanXMIn | extended static mobile input. |
ChanXMIn64 | extended 64-bit dynamic mobile input. |
ChanXMInN | extended generic dynamic mobile input. |
CTSemClaim | claims a channel-type semaphore. |
CTSemInit | initialises channel-type semaphore. |
CTSemReelase | releases a channel-type semaphore. |
DMemAlloc | dynamic memory allocation. |
DMemFree | dynamic memory release. |
EXTERNAL_CALL | external C function call. |
EXTERNAL_CALLN | external C function call. |
GetPri | get process priority. |
MBarrierAlloc | allocates a mobile barrier. |
MBarrierAllocClean | frees a mobile barrier. |
MBarrierEnroll | enrolls processes on a mobile barrier. |
MBarrierInit | initialises a mobile barrier. |
MBarrierResign | resigns processes from a mobile barrier. |
MBarrierSync | synchronises on a mobile barrier. |
ProcAfter | delay process. |
ProcAlloc | allocates and initialises a new process. |
ProcAllocClean | frees a process. |
ProcAlt | alternation. |
ProcAltList | alternation. |
ProcFork | spawns a new concurrent process. |
ProcInit | allocates and initialises a new process with a specified stack. |
ProcInitClean | frees a process. |
ProcPar | runs processes in parallel. |
ProcParam | sets parameters of a process. |
ProcParList | runs processes in parallel. |
ProcPriPar | runs processes in parallel at specified priority levels. |
ProcPriParList | runs processes in parallel at specified priority levels. |
ProcPriRun | scheduled a process at a specific priority. |
ProcProcPriAlt | process prioritised alternative. |
ProcProcPriAltList | process prioritised alternative. |
ProcRun | schedules a process. |
ProcStop | deschedules a process. |
ProcTime | reads the current time. |
ProcTimeAfter | wait until specified time. |
ProcTimeMinus | subtracts time values. |
ProcTimePlus | adds time values. |
Reschedule | reschedules process. |
SaveL | load run-queue pointers. |
SavePri | load run-queue poitners for a specific priority. |
SetErr | generate a trappable run-time error. |
SetPri | set process priority. |
StLB | store run-queue back-pointer. |
StLF | store run-queue front-pointer. |
StPriB | store run-queue back-pointer for a specific priority. |
StPriF | store run-queue front-pointer for a specific priority. |
Copyright © 2005, Fred Barnes, Department of Computer Science, University of Kent. |