Current version of SAL library provides abstraction of three subsystems: multitasking, file IO and socket IO. Implementations of this interfaces for Windows 95/98/NT and various versions of Unix are currently available. This library was primary developed for GOODS project (multiplatform distributed Generic Object Orient Database management System) and proved to be effective solution for development of portable application.
Depending on requirements to the subsystem, three different approaches are used for for encapsulation of system dependent implementation for these interfaces:
Subsystem | Approach |
---|---|
Multitasking | Conditional inheritance |
File IO | Different method implementations |
Socket IO | Abstract class with several implementation classes |
Abstraction of thread is represented in SAL by class task
(name thread was not used to avoid name conflict).
Except task
class, multitasking subsystem provides set of
synchronization classes, which make possible to synchronize execution of
concurrent tasks.
Multitasking interface requires high effective inline implementations for critical sections (since this operations are very frequently used in multithreaded applications). That is why conditional inheritance was used for the classes representing synchronization primitives. Each such class XXX is derived from correspondent XXX_internals class, which methods are invoked by inline methods of interface class XXX. Implementation of internal classes depends on the system and is taken from one of the available header files. Currently three different implementations of multitasking subsystem are provided:
setjmp()/longjmp()
functions
static task* create(fptr f, void* arg = NULL, priority pri = pri_normal, size_t stack_size = normal_stack);
f
- pointer to function to be executed in new task.
arg
- argument which should be passed to the function
priority
- one of the following task priorities:
pri_background, pri_low, pri_normal, pri_high, pri_realtime.
stack_size
- space reserved for task stack. You can specify
any value not smaller than min_stack. Also the following
symbolic links are provided:
Name | Value |
---|---|
min_stack | 8 Kb |
small_stack | 16 Kb |
normal_stack | 64 Kb |
big_stack | 256 Kb |
huge_stack | 1024 Kb |
task::current()
method
static void initialize(size_t main_stack_size = normal_stack);
main()
procedure. Using multitasking library without initialization can cause
unpredictable behavior.
main_stack_size
- specify stack size reserved for main task.
This parameter is used only by portable multitasking implementation.
static void reschedule();
static void sleep(timeout_t msec);
msec
- value of timeout in milliseconds
static void exit();
exit()
method is called or return from task function is done.
static void current();
void enter();
enter
method several times and mutex
will be released only after correspondent number of leave
invocations.
void leave();
enter()
invocations becomes equal
to the number of leave()
invocations.
critical_section(mutex& guard);
guard
- mutex used to synchronize access to this critical
section
void wait();
boolean wait_with_timeout(timeout_t msec) {
msec
- value of timeout in milliseconds. If zero
timeout parameter is specified the method will return immediately.
True
if value of counter is non-zero, False
if timeout is expired before semaphore is signaled.
void signal();
This class has the same methods as semaphore
class.
Result of executing method of this class with unlocked guard mutex or
with mutex locked more than once (nested locks) is unpredictable.
Portable multitasking library is able to catch such errors by
assert
statement, but the same is not true for implementation based on OS threads.
This class provides the same model of accessing conditional variables
as used in Posix threads.
semaphores(mutex& guard);
guard
- mutex to be used as semaphore guard.
void wait();
boolean wait_with_timeout(timeout_t msec) {
msec
- value of timeout in milliseconds. If zero
timeout parameter is specified the method will return immediately.
True
if event is signaled, False
if timeout is expired before event is set to signaled state.
void signal();
reset()
method will be called.
void reset();
wait()
atomically releases
mutex and blocks calling task until event will be signaled.
After return the mutex has been locked and is owned by the current task.
This class has the same methods as event
class.
Result of executing method of this class with unlocked guard mutex or
with mutex locked more than once (nested locks) is unpredictable.
eventex(mutex& guard);
guard
- mutex to be used as event guard.
fifo_queue
can be used as communication buffer
between two or more tasks. Such models of intertask communications as
producer/consumers and channels can be implemented by means of this class.
This class uses cyclic buffer which size is determined at the moment
of object creation.
fifo_queue(size_t size);
size
- cyclic buffer size. When size
elements are
put into the queue, it become full and any other attempt to add element to the
queue will block the calling task.
boolean is_empty() const;
True
if there are no elements in queue; False
otherwise.
boolean is_full() const;
True
if cyclic buffer is full (no more elements can be placed
in the queue until some elements were taken from it); False
otherwise.
fifo_queue& operator << (T const& elem);
elem
- element to be inserted in queue.
this
object to make it possible to use chain of
<<
operations.
fifo_queue& operator >> (T& elem);
elem
- reference to location where extracted element should be
placed.
this
object to make it possible to use chain of
>>
operations.
barrier
class allows a set of tasks to sync up at some point in
their code. It is initialized to the number of tasks to be using it, then it
blocks all tasks calling it until it reaches zero, at which point it unblocks
them all. The idea is that you can now arrange for a set of tasks to stop when
they get to some predefined point in their computation and wait for all others
to catch up. If you have eight tasks, you initialize the barrier to eight.
Then, as each task reaches that point, it decrements the barrier, and hence
goes to sleep. When the last task arrives, it decrement the barrier to zero,
and they all unblock and proceed.
void reset(int n);
n
- number of tasks using this barrier for synchronization.
void reach();
Most of the systems supporting multithreaded model have debugger which is able to deal with threads (suspend/resume threads, switch between threads, show thread context). As far as cooperative multitasking provided by SAL is not visible for the system and debugger, a number of special functions were implemented to make it possible to debug such applications using standard debugger (debugger should support evaluation of user functions). The following section describes these functions and should be read only if you are going to use portable multitasking implementation. My experiments with this library shows that is more convenient to start debugging of the application with cooperative multitasking and then switch to preemptive multitasking.
To enable debugging of application using cooperative multitasking provided
by SAL, you should compile SAL sources with
CTASK_DEBUGGING_SUPPORT
macro defined.
As far as defining this name adds very small runtime overhead, it is
defined by default. If this macro is defined three functions will be available
in SAL library, which can be used for analyzing state of multitasking program
(unfortunately it is not possible to continue execution after such analysis).
debug_select_task(int)
is used to switch
context to specified task, and breakpoint in
debug_catch_task_activation()
make it possible to programmer
to see this context.
debug_get_number_of_tasks()
function.
Index of task passed as parameter to this function should be positive number
less than value returned by debug_get_number_of_tasks()
.
Task with index 0 refers to the task, which was running before debugger
stops the application, and it can not be activated with this function
(so always investigate current context before switching to other tasks).
After activation of the specified task, control is passed to the function
debug_catch_task_activation()
and then execution of the task
continues.
i
- index of the task, should be in range [1..number-of-tasks)
-1
if specified number is greater or equal to the
number of tasks in the application or less than 1. Otherwise control from this
function will not return (longjmp).
Because of presence of this abstract class hierarchy (all this classes
are derived from abstract class file
), it is not convenient
to use inheritance to provide system dependent implementations.
Instead of this several system dependent implementations of file class
methods are provided and they are placed in two system dependent
modules unifile.cxx
and winfile.cxx
.
This is possible because structure of the file
class is
almost the same for all systems, we need only to describe system
dependent handle type.
os_file
provides standard set of methods
for accessing operating system file.
os_file(const char* name);
name
- name of the file
iop_status read(void* buf, size_t size);
buf
- buffer to hold read data. The buffer size should be
not less than size
.
size
- number of bytes to read
file::ok
if operation successfully completed;
end_of_file
if there are less than size
bytes
between current position and end of file;
iop_status read(fposi_t pos, void* buf, size_t size);
pos
- position in the file
buf
- buffer to hold read data. The buffer size should be
not less than size
.
size
- number of bytes to read
file::ok
if operation successfully completed;
end_of_file
if there are less than size
bytes
between specified position and end of file;
iop_status write(void const* buf, size_t size);
buf
- buffer with data to be written.
size
- number of bytes to write
file::ok
if operation successfully completed;
end_of_file
if number of bytes really written is less than
size
bytes;
iop_status write(fposi_t pos, void const* buf, size_t size);
buf
- buffer with data to be written.
size
- number of bytes to write
file::ok
if operation successfully completed;
end_of_file
if number of bytes really written is less than
size
bytes;
iop_status set_position(fposi_t pos);
pos
- position in the file
file::ok
if operation successfully completed;
iop_status get_position(fposi_t& pos);
pos
- reference to variable to hold current position in the file
file::ok
if operation successfully completed;
iop_status flush();
file::ok
if operation successfully completed;
iop_status open(access_mode mode, int flags);
mode
- one of fa_read, fa_write, fa_readwrite
flags
- combination of 0 or more flags:
Flag | Description |
---|---|
fo_truncate | reset length of file to 0 |
fo_create | create file if not existed |
fo_sync | wait completion of write operations |
fo_random | optimize file for random access |
fo_exclusive | prevent file from opening by another process |
fo_shared | prevent concurrent write access to the file |
file::ok
if operation successfully completed;
file::lock_error
if fo_exclusive
or fo_shared
flags are specified and file was opened by some other process in
incompatible mode;
iop_status close();
file::ok
if operation successfully completed;
char const* get_name();
iop_status set_name(char const* new_name);
new_name
- new name for the file
file::ok
if operation successfully completed;
iop_status get_size(fsize_t& size);
size
- reference of variable to hold file size.
file::ok
if operation successfully completed;
iop_status set_size(fsize_t new_size);
new_size
- new file size
file::ok
if operation successfully completed;
void get_error_text(iop_status code, char* buf, size_t buf_size)
code
- error code returned by some other file operation.
buf
- buffer to receive text of the error message
buf_size
- size of buffer, no more than buf_size
bytes will be placed in the buffer
os_file
class and has
one additional method get_mmap_addr()
.
mmap_file(const char* name, size_t init_size);
name
- name of the file
init_size
- initial size of the file. This method
will reserve at least init_size bytes of virtual memory and
map file on it. If the file size will exceed this value, then file will
be reallocated. If the file size is greater than init_size, then this
parameter is ignored.
char* get_mmap_addr() const;
multifile
can be used to overcome system limitation
for maximal file size or to distribute file space between several disk
partitiones. This class implements all the methods described in
os_file section.
multifile(int n_segments, segment* segments);
segment
structure declared
locally in multifile
class and containing name
and size
fields. Only file described in the last segment can be
extended.
n_segments
- number of segments in multifile
segments
- pointer to the array of segment descriptions.
This array should have n_segments elements.
socket_t
. Concrete object implementing socket
is create by static create, accept
or connect
methods. Access to sockets should be synchronized by mutexes or some
other synchronization primitive. Concurrent execution of two read or two
write operations can cause unpredictable behavior. But concurrent execution
of read and write operation is possible.Implementations of sockets are mostly based on socket library provided by operating system, but as far as local domain sockets are supported only in Unix, SAL provides very efficient implementation of local sockets for Win32. These local sockets are implemented by Win32 shared memory and semaphore objects and can be used to perform fast communication between processes within one computer. My experiments shows that this implementation of local sockets is about 10 times faster than original socket library provided by Microsoft.
boolean read(void* buf, size_t size);
buf
- buffer to hold received data
buf_size
- number of bytes to receive
True
if operation successfully completed, False
otherwise.
boolean write(void const* buf, size_t size);
buf
- buffer containing data to send
buf_size
- number of bytes to send
True
if operation successfully completed, False
otherwise.
socket_t* accept();
connect
method and access
server's accept port, accept method will create new socket, which can be used
for communication with the client. Accept method will block current task until
some connection will be established.
NULL
if operation failed.
boolean cancel_accept();
True
if socket was successfully closed, False
otherwise.
boolean shutdown();
True
if operation successfully completed, False
otherwise.
boolean close();
True
if operation successfully completed, False
otherwise.
static socket_t* connect(char const* address, socket_domain domain = sock_any_domain, int max_attempts = DEFAULT_CONNECT_MAX_ATTEMPTS, time_t timeout = DEFAULT_RECONNECT_TIMEOUT);
max_attempts
attempts to connect server, with timeout
interval between attempts.
address
- address of server socket in format "hostname:port"
domain
- type of connection. The following values of this
parameter are recognized:
Domain | Description |
---|---|
sock_any_domain | domain is chosen automatically |
sock_local_domain | local domain (connection with one host) |
sock_global_domain | internet domain |
If sock_any_domain is specified, local connection is chosen when either port was omitted in specification of the address or hostname is "localhost", and global connection is used in all other cases.
max_attempts
- maximal number of attempts to connect to server
timeout
- timeout in seconds between attempts to connect
the server
static socket_t* create_local(char const* address, int listen_queue_size = DEFAULT_LISTEN_QUEUE_SIZE);
address
- address to be assigned to the socket
listen_queue_size
- size of listen queue
static socket_t* create_global(char const* address, int listen_queue_size = DEFAULT_LISTEN_QUEUE_SIZE);
address
- address to be assigned to the socket
listen_queue_size
- size of listen queue
boolean is_ok();
True
if the last operation completed successfully,
False
otherwise
void get_error_text(char* buf, size_t buf_size)
buf
- buffer to receive text of the error message
buf_size
- size of buffer, no more than buf_size
bytes will be placed in the buffer
Look for new version at my homepage | E-Mail me about bugs and problems