IPC via named pipe
Named pipe, or FIFO, is a way of providing inter-process communications (IPC). To demonstrate how it works, create two pipes, say, "in" and "out" (choose suitable names for your system), and write a program that works the two pipes such that:
- Data written to the "in" FIFO will be discarded except the byte count, which will be added to a total tally kept by the program;
- Whenever another process reads the "out" FIFO, it should receive the total count so far.
Possible issues:
- Chances are you don't already have "in" and "out" pipes lying around. Create them within your program or without, at your discretion. You may assume they are already created for you.
- Your program may assume it's the sole reader on "in" and the sole writer on "out".
- Read/write operations on pipes are generally blocking. Make your program responsive to both pipes, so that it won't block trying to read the "in" pipe while leaving another process hanging on the other end of "out" pipe indefinitely -- or vice versa. You probably need to either poll the pipes or use multi-threading.
- You may assume other processes using the pipes behave; specificially, your program may assume the process at the other end of a pipe will not unexpectedly break away before you finish reading or writing.
Ruby
With OpenBSD, we observe that open(2) a named pipe blocks all threads in a process. (This must be bug in thread library.) So, we fork(2) other process to call open(2), and apply UNIXSocket to send IO object.
<lang ruby>require 'socket'
- Ruby has no direct access to mkfifo(2). We use a shell script.
system 'sh', '-c', <<EOF or abort test -p in || mkfifo in || exit test -p out || mkfifo out || exit EOF
- Forks a process to open _path_. Returns a _socket_ to receive the open
- IO object (by UNIXSocket#recv_io), and the _pid_ of the process.
def open_sesame(path, mode)
reader, writer = UNIXSocket.pair pid = fork do begin reader.close file = File.open(path, mode) writer.send_io file ensure exit! end end writer.close return reader, pid
end
insock, inpid = open_sesame("in", "rb") outsock, outpid = open_sesame("out", "w") Process.detach(inpid) Process.detach(outpid) inpipe, outpipe = nil count = 0 readers = [insock, outsock] writers = [] loop do
selection = select(readers, writers) selection[0].each do |reader| case reader when insock inpipe = reader.recv_io when outsock outpipe = reader.recv_io when inpipe count += (inpipe.read_nonblock.size rescue 0) end end selection[1].each do |writer| case writer when outpipe outpipe.puts count exit end end
end</lang>
$ ruby count.rb count.rb:39:in `recv_io': file descriptor was not passed (msg_controllen=0 smaller than sizeof(struct cmsghdr)=12) (SocketError) from count.rb:39:in `block (2 levels) in <main>' from count.rb:36:in `each' from count.rb:36:in `block in <main>' from count.rb:34:in `loop' from count.rb:34:in `<main>'
Tcl
<lang tcl># Make the pipes by calling a subprocess... exec sh -c {test -p in || mkfifo in || exit 1;test -p out || exec mkfifo out}
- How many bytes have we seen so far?
set count 0
- Read side; uses standard fileevent mechanism (select() under the covers)
set in [open in {RDONLY NONBLOCK}] fconfigure $in -translation binary fileevent $in readable consume proc consume {} {
global count in # Reads only 4kB at a time set data [read $in 4096] incr count [string length $data]
}
- Writer side; relies on open() throwing ENXIO on non-blocking open of write side
proc reportEveryHalfSecond {} {
global count catch {
set out [open out {WRONLY NONBLOCK}] puts $out $count close $out
} # Polling nastiness! after 500 reportEveryHalfSecond
} reportEveryHalfSecond
- Run the event loop until done
vwait forever</lang>