ici::doc::pod3::platform(3)

๐Ÿ“ฆ version 3.2.1, in ubuntu jammy

platform ๐Ÿ’…โœจ

okay bestie, gather round, because this is the ici::doc::pod3::platform(3) page and it's lowkey a whole vibe โ€” it's the C software portability definitions and functions situation, from the ICI library functions fam. ๐Ÿซถ


โœจ NAME โœจ

platform โ€” C software portability definitions and functions ๐Ÿฆ‹


๐ŸŒธ SYNOPSIS ๐ŸŒธ

#include "platform.h"

[see description for available functions]

๐Ÿ’– DESCRIPTION ๐Ÿ’–

so platform is literally a library of functions whose entire job is making it easier to port software written in C. it hands you an API that lets your application code reach the resources of an abstract POSIX-compliant "least common denominator" operating system โ€” which is usually a big old subset of whatever the actual underlying operating system is serving. ๐Ÿ‘‘

most of what the platform library does is aimed at making communication code portable: you get the common functions for shared memory, semaphores, and IP sockets. the implementation of that abstract O/S API shifts around depending on which actual operating system your app is running on, but the API's behavior is always the same. so if your application calls the platform library functions instead of native O/S system calls, yeah, it might give up some O/S-specific capability โ€” but it gains portability at little if any cost in performance. periodt. ๐Ÿ’…

word size differences between platforms? handled by values of the SPACE_ORDER macro. "space order" is the base 2 log of the number of octets in a word: for 32-bit machines the space order is 2 (2^2 = 4 octets per word), and for 64-bit machines it's 3 (2^3 = 8 octets per word). math ate, no cap. ๐Ÿงฎโœจ

some applications really need a consistent, platform-independent way to represent large integers, and for that purpose platform defines the new types vast and uvast (unsigned vast) which are consistently defined to be 64-bit integers no matter what the platform's native word size is. iconic behavior honestly.

the platform.h header file #includes a whole little squad of the most frequently needed header files: sys/types.h, errno.h, string.h, stdio.h, sys/socket.h, signal.h, dirent.h, netinet/in.h, unistd.h, stdlib.h, sys/time.h, sys/resource.h, malloc.h, sys/param.h, netdb.h, sys/uni.h, and fcntl.h. ๐ŸŽ€

beyond all that, platform tries to enhance compatibility by providing standard macros, type definitions, external references, or function implementations that are missing from a few supported O/S's but supported by all the others. and finally, there are entirely new, generic functions provided to establish a common body of functionality that subsumes significantly different O/S-specific capabilities. it's giving "i got you covered." ๐ŸŒˆ


๐Ÿฉน PLATFORM COMPATIBILITY PATCHES ๐Ÿฉน

the platform library literally "patches" the APIs of supported O/S's to guarantee that all of the following items can be utilized by application software:

  • The strchr(), strrchr(), strcasecmp(), and strncasecmp() functions.
  • The unlink(), getpid(), and gettimeofday() functions.
  • The select() function.
  • The FD_BITMAP macro (used by select()).
  • The MAXHOSTNAMELEN macro.
  • The NULL macro.
  • The timer_t type definition.

๐Ÿงš PLATFORM GENERIC MACROS AND FUNCTIONS ๐Ÿงš

the generic macros and functions in this section may be used in place of comparable O/S-specific functions, to enhance the portability of your code. and bestie, here's the slay: the implementations of these macros and functions are no-ops in environments where they're inapplicable, so they're always safe to call. always. ๐Ÿซถ

  • FDTABLE_SIZE โ€” returns the total number of file descriptors defined for the process (or VxWorks target). ๐Ÿ“
  • ION_PATH_DELIMITER โ€” returns the ASCII character โ€” either '/' or '\' โ€” that's used as a directory name delimiter in path names for the file system used by the local platform. ๐Ÿ›ฃ๏ธ
  • oK(expression) โ€” simply casts the value of expression to void, which is a way of handling function return codes that are not meaningful in this context. "i see you, i just don't care." ๐Ÿ™ˆ
  • CHKERR(condition) โ€” an "assert" mechanism. it causes the calling function to return -1 immediately if condition is false. ๐Ÿšจ
  • CHKZERO(condition) โ€” an "assert" mechanism. it causes the calling function to return 0 immediately if condition is false. ๐Ÿšจ
  • CHKNULL(condition) โ€” an "assert" mechanism. it causes the calling function to return NULL immediately if condition is false. ๐Ÿšจ
  • CHKVOID(condition) โ€” an "assert" mechanism. it causes the calling function to return immediately if condition is false. ๐Ÿšจ
  • void snooze(unsigned int seconds) โ€” suspends execution of the invoking task or process for the indicated number of seconds. nap time fr fr ๐Ÿ˜ด
  • void microsnooze(unsigned int microseconds) โ€” suspends execution of the invoking task or process for the indicated number of microseconds. a micro-nap, if you will ๐Ÿ’ค
  • void getCurrentTime(struct timeval *time) โ€” returns the current local time in a timeval structure (see gettimeofday(3C)). โฐ
  • void isprintf(char *buffer, int bufSize, char *format, ...) โ€” a safe, portable implementation of snprintf(); see the snprintf(P) man page for details. isprintf() differs from snprintf() in that it always NULL-terminates the string in buffer, even if the length of the composed string would equal or exceed bufSize. buffer overruns are reported by log message; and unlike snprintf(), isprintf() returns void. safety first, babes ๐Ÿ›ก๏ธ
  • size_t istrlen(const char *sourceString, size_t maxlen) โ€” a safe implementation of strlen(); see the strlen(3) man page for details. istrlen() differs from strlen() in that it takes a second argument, the maximum valid length of sourceString. the function returns the number of non-NULL characters in sourceString preceding the first NULL character in sourceString, provided that a NULL character appears somewhere within the first maxlen characters of sourceString; otherwise it returns maxlen. ๐Ÿ“
  • char *istrcpy(char *buffer, char *sourceString, int bufSize) โ€” a safe implementation of strcpy(); see the strcpy(3) man page for details. istrcpy() differs from strcpy() in that it takes a third argument, the total size of the buffer into which sourceString is to be copied. istrcpy() always NULL-terminates the string in buffer, even if the length of the sourceString string would equal or exceed bufSize (in which case sourceString is truncated to fit within the buffer). โœ‚๏ธ
  • char *istrcat(char *buffer, char *sourceString, int bufSize) โ€” a safe implementation of strcat(); see the strcat(3) man page for details. istrcat() differs from strcat() in that it takes a third argument, the total size of the buffer for the string that's being aggregated. istrcat() always NULL-terminates the string in buffer, even if the length of the sourceString string would equal or exceed the sum of bufSize and the length of the string currently occupying the buffer (in which case sourceString is truncated to fit within the buffer). ๐Ÿ”—
  • char *igetcwd(char *buf, size_t size) โ€” normally just a wrapper around getcwd(3). it differs from getcwd(3) only when FSWWDNAME is defined, in which case the implementation of igetcwd() must be supplied in an included file named "wdname.c". this adaptation option accommodates flight software environments in which the current working directory name must be configured rather than discovered at run time. very "we don't ask, we're told" energy ๐Ÿš€
  • void isignal(int signbr, void (*handler)(int)) โ€” a portable, simplified interface to signal handling that's functionally indistinguishable from signal(P). it assures that reception of the indicated signal will interrupt system calls in SVR4 fashion, even when running on a FreeBSD platform. ๐Ÿ’ฅ
  • void iblock(int signbr) โ€” simply prevents reception of the indicated signal by the calling thread. it provides a means of controlling which of the threads in a process will receive the signal cited in an invocation of isignal(). you get to pick the main character thread ๐Ÿ‘‘
  • char *igets(int fd, char *buffer, int buflen, int *lineLen) โ€” reads a line of text, delimited by a newline character, from fd into buffer and writes a NULL character at the end of the string. the newline character itself is omitted from the NULL-terminated text line in buffer; and if the newline is immediately preceded by a carriage return character (i.e., the line is from a DOS text file), then the carriage return character is likewise omitted from the NULL-terminated text line in buffer. end of file is interpreted as an implicit newline, terminating the line. if the number of characters preceding the newline is greater than or equal to buflen, only the first (buflen - 1) characters of the line are written into buffer.
    • on error the function sets *lineLen to -1 and returns NULL. ๐Ÿ˜ญ
    • on reading end-of-file, the function sets *lineLen to zero and returns NULL.
    • otherwise the function sets *lineLen to the length of the text line in buffer, as if from strlen(3), and returns buffer. ๐Ÿ“–
  • int iputs(int fd, char *string) โ€” writes to fd the NULL-terminated character string at string. no terminating newline character is appended to string by iputs(). on error the function returns -1; otherwise the function returns the length of the character string written to fd, as if from strlen(3). โœ๏ธ
  • vast strtovast(char *string) โ€” converts the leading characters of string, skipping leading white space and ending at the first subsequent character that can't be interpreted as contributing to a numeric value, to a vast integer and returns that integer. ๐Ÿ”ข
  • uvast strtouvast(char *string) โ€” same as strtovast() except the result is an unsigned vast integer value. ๐Ÿ”ข
  • void findToken(char **cursorPtr, char **token) โ€” locates the next non-whitespace lexical token in a character array, starting at *cursorPtr. the function NULL-terminates that token within the array and places a pointer to the token in *token. it also accommodates tokens enclosed within matching single quotes, which may contain embedded spaces and escaped single-quote characters. if no token is found, *token contains NULL on return from this function. ๐Ÿ•ต๏ธโ™€๏ธ
  • void *acquireSystemMemory(size_t size) โ€” uses memalign() to allocate a block of system memory of length size, starting at an address that's guaranteed to be an integral multiple of the size of a pointer to void, and initializes the entire block to binary zeroes. returns the starting address of the allocated block on success; returns NULL on any error. ๐Ÿง โœจ
  • int createFile(const char *name, int flags) โ€” creates a file of the indicated name, using the indicated file creation flags. this function provides common file creation functionality across VxWorks and Unix platforms, invoking creat() under VxWorks and open() elsewhere. for return values, see creat(2) and open(2). ๐Ÿ“„
  • unsigned int getInternetAddress(char *hostName) โ€” hands you back the IP address of the host machine you named, or zero if it just can't figure it out ๐Ÿ’€
  • char *getInternetHostName(unsigned int hostNbr, char *buffer) โ€” drops the host name of the machine at hostNbr into buffer and returns buffer, or returns NULL if literally anything goes wrong. Size your buffer at (MAXHOSTNAMELEN + 1) bestie, don't skimp ๐Ÿ’…
  • int getNameOfHost(char *buffer, int bufferLength) โ€” writes the first (bufferLength - 1) characters of the local machine's host name into buffer. Returns 0 when it slays, -1 on any error.
  • unsigned int getAddressOfHost() โ€” gives you the IP address for the local machine's host name, or 0 if anything's off.
  • void parseSocketSpec(char *socketSpec, unsigned short *portNbr, unsigned int *hostNbr) โ€” takes socketSpec apart and pulls the host number (IP address) and port number out of the string. The expected format is "{ @ | hostname }[:<portnbr>]", where @ means "the host name of the local machine" ๐Ÿ . If it can suss out the host number, it writes it into *hostNbr; if not, it writes 0 into *hostNbr. If a port number is given and lands in the range 1024 to 65535, it goes into *portNbr; otherwise 0 goes into *portNbr.
  • void printDottedString(unsigned int hostNbr, char *buffer) โ€” builds that classic dotted-string (xxx.xxx.xxx.xxx) glow-up of the IPv4 address sitting in hostNbr and writes it into buffer. buffer has to be at least 16 long, no negotiating โœจ
  • char *getNameOfUser(char *buffer) โ€” writes the user name of whoever's invoking the task or process into buffer and returns buffer. buffer must be at least L_cuserid big, which is a constant chillin' in the stdio.h header file. Returns buffer, obviously ๐Ÿซถ
  • int reUseAddress(int fd) โ€” makes the address bound to the socket fd reusable, so you can close that socket and immediately reopen and re-bind it to the same port number. Returns 0 on success, -1 on any error.
  • int makeIoNonBlocking(int fd) โ€” makes I/O on the socket fd non-blocking; returns -1 on failure. Here's the tea: if you try to read on a non-blocking socket when nothing's pending, or write when its output buffer is full, it will not block. Instead it returns -1 and sets errno to EWOULDBLOCK. Iconic behaviour honestly.
  • int watchSocket(int fd) โ€” flips on the "linger" and "keepalive" options for the socket fd. Peep socket(2) for the details. Returns 0 on success, -1 on any failure.
  • void closeOnExec(int fd) โ€” makes sure fd is NOT open in any child process fork()ed from the invoking process. Heads up though: this does absolutely nothing on a VxWorks platform ๐Ÿ™ˆ

๐Ÿฉน EXCEPTION REPORTING ๐Ÿฉน

The functions in this section give you platform-independent ways to report on processing exceptions. Very demure, very portable ๐Ÿ’œ

The whole mechanism underneath ICI's exception reporting is a pair of functions that stash error messages in a privately managed pool of static memory. Those two โ€” postErrmsg() and postSysErrmsg() โ€” are built to return super fast with zero chance of failing themselves. That said, they are not safe to call from an interrupt service routine (ISR). Even though each one just copies its text into the next free spot in the error message memory pool, that pool is protected by a mutex โ€” and multiple processes might be queued up waiting on that mutex, so the total time to run the function is non-deterministic. No cap.

Built on top of postErrmsg() and postSysErrmsg() are putErrmsg() and putSysErrmsg(), which can take longer to return. Each one just calls the matching "post" function, but then calls writeErrmsgMemos(), which calls writeMemo() to print (or otherwise deliver) every message currently posted to the pool, then destroys all those posted messages and empties the pool. Clean sweep ๐Ÿงน

The recommended general policy on using ICI's exception reporting functions โ€” which the functions in the ION distribution libraries are supposed to follow โ€” goes like this:

In the implementation of any ION library function or any ION task's top-level driver function, any condition that prevents the function from continuing execution toward producing the effect it is designed to produce is considered an "error".
Detection of an error should result in the printing of an error message and, normally, the immediate return of whatever return value is used to indicate the failure of the function in which the error was detected. By convention this value is usually -1, but both zero and NULL are appropriate failure indications under some circumstances such as object creation.
The CHKERR, CHKZERO, CHKNULL, and CHKVOID macros are used to implement this behavior in a standard and lexically terse manner. Use of these macros offers an additional feature: for debugging purposes, they can easily be configured to call sm_Abort() to terminate immediately with a core dump instead of returning a error indication. This option is enabled by setting the compiler parameter CORE_FILE_NEEDED to 1 at compilation time.
In the absence of either any error, the function returns a value that indicates nominal completion. By convention this value is usually zero, but under some circumstances other values (such as pointers or addresses) are appropriate indications of nominal completion. Any additional information produced by the function, such as an indication of "success", is usually returned as the value of a reference argument. [Note, though, that database management functions and the SDR hash table management functions deviate from this rule: most return 0 to indicate nominal completion but functional failure (e.g., duplicate key or object not found) and return 1 to indicate functional success.]
So when returning a value that indicates nominal completion of the function โ€” even if the result might be interpreted as a failure at a higher level (e.g., an object identified by a given string is not found, through no failure of the search function) โ€” do NOT invoke putErrmsg().
Use putErrmsg() and putSysErrmsg() only when functions are unable to proceed to nominal completion. Use writeMemo() or writeMemoNote() if you just want to log a message.
Whenever returning a value that indicates an error:
- If the failure is due to the failure of a system call or some other non-ION function, assume that errno has already been set by the function at the lowest layer of the call stack; use putSysErrmsg (or postSysErrmsg if in a hurry) to describe the nature of the activity that failed. The text of the error message should normally start with a capital letter and should NOT end with a period. - Otherwise โ€” i.e., the failure is due to a condition that was detected within ION โ€” use putErrmsg (or postErrmg if pressed for time) to describe the nature of the failure condition. This will aid in tracing the failure through the function stack in which the failure was detected. The text of the error message should normally start with a capital letter and should end with a period.
When a failure in a called function is reported to "driver" code in an application program, before continuing or exiting use writeErrmsgMemos() to empty the message pool and print a simple stack trace identifying the failure.

  • char *system_error_msg( ) โ€” returns a short lil text string describing the current system error, as identified by the current value of errno.
  • void setLogger(Logger usersLoggerName) โ€” sets the user function that writes messages to a user-defined "log" medium. The logger function's calling sequence has to match this prototype exactly:
void    usersLoggerName(char *msg);

The default Logger function just writes the message to standard output. Simple. Iconic. ๐ŸŒธ

  • void writeMemo(char *msg) โ€” writes one log message, using the message logging function that's currently defined.
  • void writeMemoNote(char *msg, char *note) โ€” writes a log message just like writeMemo(), but with the user-supplied, context-specific text in note tagging along ๐Ÿซถ
  • void writeErrMemo(char *msg) โ€” writes a log message like writeMemo(), plus text describing the current system error.
  • char *itoa(int value) โ€” returns a string representation of the signed integer in value, nominally so you can immediately use it as an argument to putErrmsg(). [Big note: the string gets built in a static buffer, so this function is not thread-safe. ๐Ÿ’€]
  • char *utoa(unsigned int value) โ€” returns a string representation of the unsigned integer in value, nominally for immediate use as an argument to putErrmsg(). [Same warning bestie: the string is constructed in a static buffer; this function is not thread-safe. ๐Ÿ’€]
  • void postErrmsg(char *text, char *argument) โ€” constructs an error message that notes the name of the source file containing the line where this function was called, the line number, the text of the message, and โ€” if it's not NULL โ€” a single textual argument you can use to give more specific info about the nature of the reported failure (like the value of one of the arguments to the failed function). The error message gets appended to the list of messages in a privately managed pool of static memory that's ERRMSGS_BUFSIZE bytes long.

If text is NULL, is a string of zero length, or starts with a newline character (i.e., *text == '\0' or '\n'), the function returns immediately and no error message is recorded.

The errmsgs pool is designed to be big enough to hold error messages from every level of the calling stack at the moment an error is hit. But if the unused space left in the pool is smaller than the size of the new error message, that message gets silently omitted. In that case, as long as at least two bytes of unused space remain in the pool, a message made of a single newline character gets appended to the list to signal that a message was dropped for being too long. Not the vibe, but that's the deal ๐Ÿ™ˆ

  • void postSysErrmsg(char *text, char *arg) โ€” bestie this is postErrmsg() but โœจextraโœจ: the message it builds also tacks on text describing whatever the current system error is. And text gets truncated as needed so that its length plus the length of that system-error description never goes over 1021 bytes. No cap. ๐Ÿซถ
  • int getErrmsg(char *buffer) โ€” pulls the oldest error message out of the message pool, drops it into buffer, and yeets it from the pool so new messages have room to breathe. Returns zero if the pool can't be locked for update OR if there are no more messages left in the pool; otherwise it returns the length of the message it copied into buffer. Heads up for safety: the size of buffer should be ERRMSGS_BUFSIZE. ๐Ÿ’…

And lowkey important: if a returned error message is just a single newline character, that always means an error message got silently omitted because there wasn't enough space left on the message pool to hold it. ๐Ÿ’€

  • void writeErrmsgMemos( ) โ€” calls getErrmsg() over and over until the pool is empty, using writeMemo() to log every single message in the pool. Messages that got omitted for being too long show up as logged lines that look like "[message omitted due to excessive length]".
  • void putErrmsg(char *text, char *argument) โ€” literally just calls postErrmsg() and then writeErrmsgMemos(). That's the whole gig. ๐ŸŒธ
  • void putSysErrmsg(char *text, char *arg) โ€” and this one literally just calls postSysErrmsg() and then writeErrmsgMemos(). Matching set, iconic. โœจ
  • void discardErrmsgs( ) โ€” calls getErrmsg() repeatedly until the pool is empty, throwing away every message. Gone. Not sorry. ๐Ÿ™ˆ
  • void printStackTrace( ) โ€” Linux machines only, bb! Uses writeMemo() to print a trace of the process's current execution stack, starting at the lowest level</> of the stack and going all the way up to the main() function of the executable.

Real talk, three things to know: (a) printStackTrace() is only implemented for Linux platforms at this time; (b) symbolic names of functions can only be printed if the -rdynamic flag was enabled when the executable was linked; (c) only the names of non-static functions will show up in the stack trace. ๐Ÿฆ‹

Want the full picture of what the executable was doing when that stack trace snapshot got taken? Use the Linux addr2line tool. To do it, cd into a directory where the executable file lives (like /opt/bin) and fire off an addr2line command like this:

addr2line -e name_of_executable stack_frame_address

where both name_of_executable and stack_frame_address are taken from one of the lines of the printed stack trace. addr2line will print the source file name and line number for that stack frame. Obsessed. ๐Ÿ‘‘


๐ŸŽ€ WATCH CHARACTERS ๐ŸŽ€

The functions in this section serve up platform-independent capabilities for recording "watch" characters that flag when protocol events happen. Peep bprc(5), ltprc(5), cfdprc(5), etc. for the deets on the watch character production options that the protocol packages give you. ๐ŸŒˆ

  • void setWatcher(Watcher usersWatcherName) โ€” sets the user function that gets used for recording watch characters to some user-defined "watch" medium. The watcher function's calling sequence has to match this prototype exactly:
void    usersWatcherName(char token);

The default Watcher function is simple: it just writes the token to standard output. periodt. ๐Ÿ’–

  • void iwatch(char token) โ€” records one "watch" character, using whatever watch character recording function is currently defined.

โญ SELF-DELIMITING NUMERIC VALUES (SDNV) โญ

The functions in this section encode and decode SDNVs โ€” portable, variable-length numeric variables that expand to whatever size is needed to hold the values inside them. SDNVs get used extensively in the BP and LTP libraries. ๐Ÿงš

  • void encodeSdnv(Sdnv *sdnvBuffer, uvast value) โ€” figures out how many octets of SDNV text are needed to contain value, sticks that number into the length field of the SDNV buffer, and encodes value in SDNV format into the first length octets of the text field of the SDNV buffer. ๐Ÿ’…
  • int decodeSdnv(uvast *value, unsigned char *sdnvText) โ€” determines the length of the SDNV sitting at sdnvText and returns that number, after extracting the SDNV's value from those octets and storing it in value. Returns 0 if the encoded number value won't fit into an unsigned vast integer. ๐Ÿ˜ญ

๐Ÿ”ฅ ARITHMETIC ON LARGE INTEGERS (SCALARS) ๐Ÿ”ฅ

The functions in this section do simple arithmetic on unsigned Scalar objects โ€” structures that wrap up big positive integers in a machine-independent way. Every Scalar is made of two integers: a count of units [ranging from 0 to (2^30 - 1), i.e., up to 1 gig] and a count of gigs [ranging from 0 to (2^31 -1)]. So a Scalar can represent a numeric value up to 2 billion billions, i.e., 2 million trillions. main character numbers only. ๐Ÿ‘‘

  • void loadScalar(Scalar *scalar, signed int value) โ€” sets the value of scalar to the absolute value of value.
  • void increaseScalar(Scalar *scalar, signed int value) โ€” adds to scalar the absolute value of value.
  • void reduceScalar(Scalar *scalar, signed int value) โ€” adds to scalar the absolute value of value. (yeah bestie, that's what it says ๐Ÿ˜ญ)
  • void multiplyScalar(Scalar *scalar, signed int value) โ€” multiplies scalar by the absolute value of value.
  • void divideScalar(Scalar *scalar, signed int value) โ€” divides scalar by the absolute value of value.
  • void copyScalar(Scalar *to, Scalar *from) โ€” copies the value of from into to.
  • void addToScalar(Scalar *scalar, Scalar *increment) โ€” adds increment (a Scalar, not a C integer) to scalar.
  • void subtractFromScalar(Scalar *scalar, Scalar *decrement) โ€” subtracts decrement (a Scalar, not a C integer) from scalar.
  • int scalarIsValid(Scalar *scalar) โ€” returns 1 if the arithmetic done on scalar hasn't caused overflow or underflow.
  • int scalarToSdnv(Sdnv *sdnv, Scalar *scalar) โ€” if scalar points to a valid Scalar, stores the value of scalar in sdnv; otherwise sets the length of sdnv to zero.
  • int sdnvToScalar(Scalar *scalar, unsigned char *sdnvText) โ€” if sdnvText points to a sequence of bytes that, when read as the text of an Sdnv, has a value representable in a 61-bit unsigned binary integer, then this function stores that value in scalar and returns the detected Sdnv length. Otherwise it returns zero.

Quick note: Scalars and Sdnvs are both representations of potentially large unsigned integer values, and any Scalar can alternatively be represented as an Sdnv. BUT โ€” it is possible for a valid Sdnv to be too large to represent in a Scalar. ๐Ÿ’€


๐Ÿ’œ PRIVATE MUTEXES ๐Ÿ’œ

The functions in this section give you platform-independent management of mutexes for synchronizing the operations of threads or tasks living in a common private address space. ๐Ÿซถ

  • int initResourceLock(ResourceLock *lock) โ€” establishes an inter-thread lock for use in locking some resource. Returns 0 if it worked, -1 if it did not. ๐Ÿ˜ญ
  • void killResourceLock(ResourceLock *lock) โ€” deletes the resource lock that lock refers to. bye bb ๐Ÿ‘‹
  • void lockResource(ResourceLock *lock) โ€” checks the state of lock. If the lock is already owned by a different thread, the call blocks until the other thread gives it up. If the lock is unowned, it gets handed to the current thread and the lock count is set to 1. If the lock is already owned by this thread, the lock count gets incremented by 1.
  • void unlockResource(ResourceLock *lock) โ€” if it's called by the current owner of lock, decrements lock's lock count by 1; if that hits zero, it relinquishes the lock so other threads can take it.
Care must be taken to make sure that one, and only one, unlockResource() call is issued for each lockResource() call issued on a given resource lock. Not a vibe you want to fumble, fr fr. ๐Ÿ’…

๐ŸŒˆ SHARED MEMORY IPC DEVICES ๐ŸŒˆ

The functions in this section give you platform-independent management of IPC mechanisms for synchronizing the operations of threads, tasks, or processes that may live in different address spaces but share access to a common system (nominally, processor) memory. ๐Ÿฆ‹

NOTE that this is distinct from the VxWorks "VxMP" capability that lets tasks share access to bus memory or dual-ported board memory from multiple processors. The "platform" system will support IPC devices that use that capability at some time in the future, but that support is not yet implemented. ๐Ÿ’€
  • int sm_ipc_init( ) โ€” acquires and initializes shared-memory IPC management resources. Must be called before any other shared-memory IPC function is called. Returns 0 on success, -1 on any failure. โœจ
  • void sm_ipc_stop( ) โ€” releases shared-memory IPC management resources, disabling the shared-memory IPC functions until sm_ipc_init() gets called again.
  • int sm_GetUniqueKey( ) โ€” some of the sm_ (shared memory) functions described below associate new communication objects with key values that uniquely identify them, so different processes can access them independently. Key values are typically defined as constants in application code. But when you need a new communication object and nobody anticipated it in the application, you can call sm_GetUniqueKey() to grab a brand-new, arbitrary key value that's known not to already be in use. ๐ŸŽ€
  • sm_SemId sm_SemCreate(int key, int semType) โ€” creates a shared-memory semaphore you can use to synchronize activity among tasks or processes that live in a common system memory but possibly in multiple address spaces; returns a reference handle for that semaphore, or SM_SEM_NONE on any failure. If key refers to an existing semaphore, it returns the handle of that semaphore. If key is the constant value SM_NO_KEY, it automatically obtains an unused key. On VxWorks platforms, semType decides the order in which the semaphore gets given to multiple tasks that try to take it while it's already taken: if set to SM_SEM_PRIORITY then the semaphore goes to tasks in task priority sequence (i.e., the highest-priority task waiting for it receives it when it's released), while otherwise (SM_SEM_FIFO) the semaphore goes to tasks in the order they attempted to take it. On all other platforms, only SM_SEM_FIFO behavior is supported and semType is ignored. ๐Ÿ’…
  • int sm_SemTake(sm_SemId semId) โ€” blocks until the indicated semaphore is no longer taken by any other task or process, then takes it. Returns 0 on success, -1 on any error. ๐Ÿ™ˆ

void sm_SemGive(sm_SemId semId)

Gives the semaphore you point at, so some other task or process can come along and take it. That's it, that's the whole job โ€” it's the "you may go now bestie" of semaphore calls. ๐Ÿ’–


void sm_SemEnd(sm_SemId semId)

This is how you send a termination signal to whatever task is currently blocked on taking that semaphore (if literally anyone is). It flips the "ended" flag on that semaphore to 1 โ€” so a call to sm_SemEnded() will come back with 1 โ€” and then it gives the semaphore, so the blocked task actually gets a turn and can go test that flag. Like sliding into their DMs with the bad news. ๐Ÿ˜ญ


int sm_SemEnded(sm_SemId semId)

Returns 1 if the "ended" flag on the indicated semaphore has been set to 1, and returns zero if it hasn't. And here's the cute part: when it returns 1, it also gives the semaphore, so any other tasks that might be pended on that same semaphore get a shot at testing it and finding out it's been ended too. Group chat announcement energy. ๐Ÿ“ฃ


void sm_SemUnend(sm_SemId semId)

Resets an ended semaphore, so a restarted subsystem can just reuse it instead of deleting it and allocating a whole new one. Recycling is iconic, actually. โ™ป๏ธโœจ


int sm_SemUnwedge(sm_SemId semId, int timeoutSeconds)

The rescue mission function ๐Ÿ’… โ€” for releasing semaphores that got taken and then never released, probably because the tasks or processes holding them crashed before they could let go. Rude of them.

It attempts to take the semaphore. If that attempt doesn't succeed within timeoutSeconds seconds โ€” and that window is there to give normal processing time to finish, in case the semaphore is legitimately and temporarily locked by some task โ€” then the semaphore is assumed to be wedged. Either way, once all that's decided, the semaphore gets released.

Returns 0 on success, -1 on any error.


void sm_SemDelete(sm_SemId semId)

Destroys the indicated semaphore. Straight up. ๐Ÿ’€


sm_SemId sm_GetTaskSemaphore(int taskId)

Returns the ID of the semaphore that's dedicated to the private use of the task you named, or SM_SEM_NONE on any error.

So here's the whole concept behind it: for every task there can always be one dedicated semaphore, one it can always use for its own purposes, and whose key value you can know ahead of time โ€” because the key of that semaphore is based on the task's ID. ๐ŸŽ€

The design rests on one assumption: that each task's ID โ€” whether it's a VxWorks task ID or a Unix process ID โ€” maps to a number that's out of the range of all possible key values that sm_GetUniqueKey() might arbitrarily produce.

  • For VxWorks, we assume this is true because a task ID is a pointer to task state in memory, and we assume that doesn't exceed 2GB. The unique key counter starts at 2GB. ๐Ÿง 
  • For Unix, we assume it's true because a process ID is an index into a process table whose size is less than 64K. Unique keys are formed by shifting the process ID left 16 bits and adding the value of an incremented counter, which is always greater than zero.

int sm_ShmAttach(int key, int size, char **shmPtr, int *id)

Attaches to a segment of memory that tasks or processes living in a common system memory โ€” but possibly in multiple address spaces โ€” all have access to. Shared memory besties ๐Ÿซถ.

This function registers the invoking task or process as a user of the shared memory segment identified by key.

  • If key is the constant value SM_NO_KEY, it automatically sets key to some unused key value.
  • If a shared memory segment identified by key already exists, then size may be zero and the value of *shmPtr is ignored.
  • Otherwise the size of the shared memory segment must be provided in size, and a new shared memory segment is created in a manner that depends on *shmPtr:
    • if *shmPtr is NULL, then size bytes of shared memory are dynamically acquired, allocated, and assigned to the newly created shared memory segment;
    • otherwise the memory located at shmPtr is assumed to have been pre-allocated and is merely assigned to the newly created shared memory segment.

On success it stores the unique shared memory ID of the segment in *id (for possible future destruction, iykyk), stores a pointer to the segment's assigned memory in *shmPtr, and returns 1 if the segment is newly created or 0 otherwise. Returns -1 on any error. ๐Ÿ”ฅ


void sm_ShmDetach(char *shmPtr)

Unregisters the invoking task or process as a user of the shared memory starting at shmPtr. Not me leaving the group chat ๐Ÿšช๐Ÿ’จ


void sm_ShmDestroy(int id)

Destroys the shared memory segment identified by id, releasing any memory that was allocated when the segment was created. Gone, reduced to atoms, periodt. ๐Ÿ’€โœจ


๐Ÿฆ‹ PORTABLE MULTI-TASKING ๐Ÿฆ‹

int sm_TaskIdSelf( )

Returns the unique identifying number of the invoking task or process. It's giving "who am I, really" but make it a syscall. ๐Ÿชž


int sm_TaskExists(int taskId)

Returns non-zero if a task or process identified by taskId is currently running on the local processor, and zero otherwise. A little vibe check for your task list. โœ…


void *sm_TaskVar(void **arg)

Posts or retrieves the value of the "task variable" belonging to the invoking task.

Every task gets access to a single task variable, initialized to NULL, that lives in the task's private state. This is genuinely convenient for passing task-specific info to a signal handler, for example โ€” lowkey one of the handiest little tricks in here.

If arg is non-NULL, then *arg is posted as the new value of the task's private task variable. And in any case, the value of that task variable is returned. ๐ŸŽ€


void sm_TaskSuspend( )

Indefinitely suspends execution of the invoking task or process. Helpful when you want to freeze an application right at the point where an error is detected, then go in with a debugger and examine its state. It's the "hold still, I'm looking at you" of debugging. ๐Ÿ”


void sm_TaskDelay(int seconds)

Same as snooze(3). ๐Ÿ˜ด


void sm_TaskYield( )

Relinquishes the CPU temporarily so other tasks can use it. Sharing is caring, bestie. ๐Ÿ’–


sm_TaskSpawn

int sm_TaskSpawn(char *name, char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7, char *arg8, char *arg9, char *arg10, int priority, int stackSize)

Spawns/forks a new task/process and hands it up to ten command-line arguments. name is the name of the function (VxWorks) or the executable image (UNIX) to be executed in the new task/process. โœจ

  • For UNIX, name must be the name of some executable program in the $PATH of the invoking process.
  • For VxWorks, name must be the name of some function named in an application-defined private symbol table (if PRIVATE_SYMTAB is defined) or the system symbol table (otherwise). If PRIVATE_SYMTAB is defined, the application must provide a suitable adaptation of the symtab.c source file, which implements the private symbol table. That part is on you, not on us. ๐Ÿ™ˆ

"priority" and "stackSize" are ignored under UNIX. Under VxWorks, if they're zero they default to the values in the application-defined private symbol table if one is provided, or otherwise to ICI_PRIORITY (nominally 100) and 32768 respectively.

Returns the task/process ID of the new task/process on success, or -1 on any error.


void sm_TaskKill(int taskId, int sigNbr)

Sends the indicated signal to the indicated task or process. ๐Ÿ“จ


void sm_TaskDelete(int taskId)

Terminates the indicated task or process. Blocked. ๐Ÿšซ


void sm_Abort()

Terminates the calling task or process. If it's not called while ION is in flight configuration, a stack trace is printed or a core file is written.

๐Ÿ’€ Heads up bestie: this one takes you out, and it might leave a stack trace or a core file behind as evidence.

int pseudoshell(char *script)

Parses script into a command name and up to 10 arguments, then passes the command name and arguments to sm_TaskSpawn() for execution. The sm_TaskSpawn() function is invoked with priority and stack size both set to zero, which causes default values (possibly from an application-defined private symbol table) to be used.

Tokens in script are normally whitespace-delimited, but a token enclosed in single-quote characters (') may contain embedded whitespace and may contain escaped single-quote characters ("\'").

On any parsing failure it returns -1; otherwise it returns the value returned by sm_TaskSpawn(). ๐Ÿš


๐ŸŒˆ USER'S GUIDE ๐ŸŒˆ

Compiling an application that uses "platform":

Just be sure to #include "platform.h" at the top of each source file that includes any platform function calls. That's literally it. ๐ŸŒธ

#include "platform.h"

Linking/loading an application that uses "platform":

  1. In a Solaris environment, link with these libraries:
-lplatform -socket -nsl -posix4 -c
  1. In a Linux environment, simply link with platform:
-lplatform
  1. In a VxWorks environment, use
ld 1, 0, "libplatform.o"

to load platform on the target before loading applications. Load the library before the apps, not after โ€” order matters, no cap. ๐Ÿ‘‘


โญ SEE ALSO โญ

  • gettimeofday(3C)

And that's the whole page, bestie โ€” you made it to the end. ๐ŸŽ‰ This one came from the perl v5.24.1 toolchain, dated 2016-07-07, and it's page ici::doc::pod3::platform(3). Now go forth and slay your semaphores. ๐Ÿ’…๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from