This page documents an IPC facility for Unicon, based on three Unix IPC facilities, namely shared memory, semaphores and message queues. This facility enables communication between different processes, either within the same or a different programs.
$ export UNICON_HOME=/opt/unicon
$ make
gcc -fPIC -O2 -I/usr/include -I/opt/unicon/src/h -I/opt/unicon/src/gdbm -I/opt/unicon/src/libtp -c ipc.c
gcc -shared -o uniipclib.so -fPIC ipc.o
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`
One other thing may need to be changed depending on your operating system. On Linux I found that the iconx executable needed to be linked with the -E flag, to enable the shared library to access symbols in the runtime executable. To do this edit the file src/runtime/Makefile so that the target iconx looks something like this :-
iconx: $(OBJS)
$(CC) $(LDFLAGS) -Xlinker -E -o iconx $(OBJS) $(XL) $(RLIBS)
cp iconx ../../bin
strip ../../bin/iconx
It is the "-Xlinker -E" bit that passes the "-E" flag to the linker.
Then just cd to src/runtime, rm iconx and run make.
Creating a sub-process is easy, and can be done with the fork() function, or alternatively using the Process class, which is just a wrapper around fork() and some other common commands. Here is a simple use of the Process class.
import lang
class Child : Process()
method run()
write("In child")
end
end
procedure main()
local c
write("In parent")
c := Child()
c.start()
c.join()
write("Finished")
end
This program when compiled prints
In parent
In child
Finished
The child process is defined by subclassing Process and implementing the run() method. In the parent process in the main method, invoking the start() method of the child begins the process, whilst the join() method waits for the child to finish.
This is very straightforward, but for things become more complicated when the child and parent process wish to communicate with one another. Consider the following expanded version of the above program.
import lang
global var
class Child : Process()
method run()
write("In child, setting var")
var := 2
end
end
procedure main()
local c
var := 1
write("In parent var=", var)
c := Child()
c.start()
c.join()
write("Finished, var=", var)
end
The output of this program is :-
In parent var=1
In child, setting var
Finished, var=1
This is not what you would expect at first glance; the child's attempt to set the variable has had no effect on the parent. This is because at the point the child process is created, it is given a complete copy of the memory space of the parent process, and so all assignments affect the copy, not the original.
import ipc, lang
global var
class Child : Process()
method run()
write("In child, setting var")
var.set_value(2)
end
end
procedure main()
# Create a Shm instance with initial value 1
var := create_private_shm(1)
write("In parent var=", var.get_value())
c := Child()
c.start()
c.join()
write("Finished, var=", var.get_value())
var.remove()
end
Now the output is :-
In parent var=1
In child, setting var
Finished, var=2
Here, var is an instance of the Shm class, and is created with the "factory" procedure create_private_shm. The parameter 1 is just the initial value. Then get_value() and set_value() are used to get/set the value of the object, and finally remove() cleans up the operating system resources used by the instance (if this is not done, it will be done automatically when the program ends, and a warning message output). These various calls delegate their work to the C functions in the shared library.
More or less anything can be used as the parameter to set_value(), but note that the value is copied into and out of the shared variable, ie reference semantics do not apply. So, for example :-
var := create_private_shm([1,2,3])
put(var.get_value(), 4) # No effect; just adds 4 to the copy returned by get_value
every write(!var.get_value()) # 1,2,3
var.set_value(put(var.get_value(), 4)) # OK, sets the value again with the 4 added
every write(!var.get_value()) # 1,2,3,4
There is one restriction regarding setting class instances as the value of the shared variable, and that is that they must subclass ClassCoding. This is best achieved by actually subclassing one of ClassCoding subclasses, BasicClassCoding or SelectiveClassCoding. Please see the documentation of those classes for details, but here is an example.
import ipc, lang
class MyClass : BasicClassCoding(x)
method thing()
write("In thing, x=", x)
end
end
procedure main()
local var, c, d
var := create_private_shm()
c := MyClass(10)
var.set_value(c)
d := var.get_value()
# Note that now d ~=== c
d.thing()
var.remove()
end
Which outputs
In thing, x=10
If MyClass does not extend BasicEncode as shown, then an unfriendly runtime
error will result!
import ipc, lang
global var, sem1, sem2
class Child : Process()
method run()
every i := 1 to 10 do {
# Wait till okay to set value
sem1.wait()
var.set_value(i)
# Signal value set
sem2.signal()
# Sleep for 1/2 second
sleep(500)
}
end
end
procedure main()
var := create_private_shm()
# Create two private semaphores with initial value 0.
sem1 := create_private_sem(0)
sem2 := create_private_sem(0)
c := Child()
c.start()
repeat {
# Signal okay to set value
sem1.signal()
# Wait till value set
sem2.wait()
i := var.get_value()
write(i)
if i = 10 then
break
}
c.join()
write("Finished")
var.remove()
sem1.remove()
sem2.remove()
end
Note that the semaphores are created and destroyed in a similar way to shared memory.
Other than the standard wait and signal methods, the Sem class also has methods to get and set the semaphore value, and to poll it for a value without suspending.
import ipc, lang
global mq
class Child : Process()
method run()
every i := 1 to 10 do {
# Send the message
mq.send(i)
# Sleep for 1/2 second
sleep(500)
}
end
end
procedure main()
mq := create_private_msg()
c := Child()
c.start()
repeat {
i := mq.receive()
write(i)
if i = 10 then
break
}
c.join()
write("Finished")
mq.remove()
end
As you can see, this particular task is much simpler using message queues.
mq := create_private_msg()
But it is possible to provide a pre-arranged key in order to create a "public" resource instead. For example
mq := create_public_msg(999) | stop("Couldn't create resource (already exists)")
After this succeeds a message queue will exist with key 999. If
the call fails, then this means a queue with id 999 already exists.
This message queue may then be opened by another process, possibly in another program.
mq := open_public_msg(999) | stop("Couldn't open resource (doesn't exists)")
Similar factory procedures exist for semaphores and shared memory.
There is one difference between "private" and "public" resources: the "public" variety aren't destroyed automatically at the end of the creating process; they have to be destroyed explicitly either by calling the remove() method, or ultimately using the Unix commands ipcs and ipcrm.