How are Unix pipes implemented?
toroid.org
toroid.org
https://www.slideshare.net/divyekapoor/linux-kernel-implemen...
Long story short: pipes and FIFOs are implemented on a virtual pipeFS and an internal 64K buffer is used for holding data in memory when transferring between processes. Locks on VFS inodes on pipefs are used for synchronization across threads / processes.
(Full Disclosure: I'm the author; this work was done as part of my Master's degree and discusses Pipes and FIFOs as implemented on pipefs for a kernel around 2011).
Hope this is interesting to the people on the thread.
Can you elaborate on how to identify the filename for a pipe in procfs or sysfs in a simple "echo hello | wc -c" example?
Re: your second question, I guess you’re referring to identifying a FIFO on the filesystem - that’s a simple ls -la - FIFOs show up as regular files with the p attribute. For procfs or sysfs - just ls /proc or ls /sys
Man page: http://nersp.nerdc.ufl.edu/~dicke3/nerspcs/ls.html
You should see the p attribute for FIFOs on the filesystem. Sysfs and procfs map onto internal (in-memory) kernel data structures, so those might just show up as regular files in the “virtual” filesystem. So cat, grep etc. on these files will be just reads/writes from/to the appropriate memory space in the kernel or for read only files, they may be “code generated output” that allows for inspection of some internal state in the kernel.
You can see it under /proc/$pid/fd. For example, if you run "sleep 600|wc -c" (just to give yourself some time to poke around), you can see:
$ ls -l /proc/2760791/fd
total 0
lrwx------ 1 ams ams 64 Mar 28 13:35 0 -> /dev/pts/10
l-wx------ 1 ams ams 64 Mar 28 13:35 1 -> pipe:[54074122]
lrwx------ 1 ams ams 64 Mar 28 13:35 2 -> /dev/pts/10
$ ls -l /proc/2760792/fd
total 0
lr-x------ 1 ams ams 64 Mar 28 13:35 0 -> pipe:[54074122]
lrwx------ 1 ams ams 64 Mar 28 13:35 1 -> /dev/pts/10
lrwx------ 1 ams ams 64 Mar 28 13:35 2 -> /dev/pts/10
Here 2760791 is the pid of the "sleep 600", and 2760792 is the pid of "wc". You can see they're both connected to "pipe:[54074122]".54074122 is the (virtual, i.e., not disk-based) inode of the pipe. You can get substantially the same information from lsof:
$ lsof -E -u ams|egrep 'sleep|wc'|grep pipe
sleep 2760791 ams 1w FIFO 0,13 0t0 54074122 pipe 2760792,wc,0r
wc 2760792 ams 0r FIFO 0,13 0t0 54074122 pipe 2760791,sleep,1w
But the name "pipe:[54074122]" is actually the "filename" of the pipe, and it comes from here in fs/pipe.c: static char *pipefs_dname(struct dentry *dentry, char *buffer, int buflen)
{
return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",
d_inode(dentry)->i_ino);
}
(There's an interesting note in Documentation/filesystems/vfs.txt about how pseudo-filesystems like pipefs can generate these names only when someone asks for them, since they're not used for anything otherwise. The only way I know of to ask for the name in this case is to call readlink() on the pipe fd under /proc/$pid/fd, as ls does.)"To put my strongest concerns in a nutshell: 1. we should have some way of coupling programs like garden hose..."
from a Bell Labs internal memo.
Kernighan's book is a great read, BTW. Highly recommend to all UNIX nerds.
As far as I can tell the implementation of each might look like the other in some ways (a queue of some kind with MutEx guaranteed somehow).
Does anyone have the knowledge to write a little on this? Or maybe point to a similar article for that style of messaging?
Pipes exercise backpressure on the writer - if the pipe is full the writer is blocked. Actor systems mostly use seemingly unbounded queues. The sender will not get blocked.
Erlang does suspend (block) processes that send to ports or nodes when the buffers for that get full; but not when sending to local processes. There used to be an optional reduction count punishment for senders when sending to messages with larger mailboxes, but it seems that may have been removed. I don't think it would be too hard to add a feature where sending to a local mailbox over a specified size caused the sender to be suspended, but tracking it might be a little difficult.
TL;DR: McIlroy was applying the concept of coroutines, which was described by Melvin Conway in 1963. Two processes communicating over a pipe are basically coroutines, except instead of passing structured data they're just passing bytes.
A Unix pipeline is a set of multiple coroutines. In fact, Tony Hoare's 1978 Communicating Sequential Processes paper cites the UNIX shell[1] for the concept of coroutines, and discussion of coroutines figures prominently in that paper. See https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf CSP basically models the behavior of a large set of coroutines.
AFAIU, Erlang was partly inspired by CSP. You can draw a straight line from coroutines, through Unix pipelines and CSP, to Erlang's processes.
[1] Specifically, Ken Thompson's 1976 paper, "The UNIX command language." See https://archive.org/details/theunixcommandlanguage
http://www.dabeaz.com/coroutines/index.htmlhttps://www.oreilly.com/library/view/making-sense-of/9781492...
Sorry for the "accusation" then.
[0] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
It has some unusual notation you might need to get used to, but it's a real goldmine of ideas.
... which is still how roff tools do it today. Manual formatters still send, even today, TTY Model 37 style input from 1969 to the manual pager: underlining with BS and the underscore character, boldface with BS and overprinting, and bullet points formed by printing a plus sign over the letter "o"; all of which less/more/pg/most have to recognize (but ironically actually do not).
The relatively modern (1976!) capabilities of GNU groff were deliberately turned off at the turn of the 21st century.
By the way: File-based pipes were later created on a specific "pipe device", whose device number was configured by the /etc/config program and was not necessarily the root.
Thanks, you are perfectly right. What I found charming was that the 3E pipe.2 manual page contains «word^H^H^H^H____» written out by hand in the roff _source_. The 4E one switched to using ".it word" instead.
https://minnie.tuhs.org/cgi-bin/utree.pl?file=V3/man/man2/pi...
Another charming little quirk: the 6E kernel's falloc() does «printf("no files\n")» if it fails to find a free file structure.
No, they do not (nor on the modern BSD kernels, as far as I can tell). The Linux pipe(7) manpage says (under "Portability notes"):
«On some systems (but not Linux), pipes are bidirectional:
data can be transmitted in both directions between the
pipe ends. POSIX.1 requires only unidirectional pipes.
Portable applications should avoid reliance on
bidirectional pipe semantics.»
I believe the systems that supported bidirectional pipes were SysV kernels that implemented pipes using STREAMS and 4(?)BSD kernels that implemented it using socketpair.That said, it is not based on socketpair any more. sys/kern/sys_pipe.c says:
/*
* This file contains a high-performance replacement for the socket-based
* pipes scheme originally used in FreeBSD/4.4Lite. It does not support
* all features of sockets, but does do everything that pipes normally
* do.
*/No, not modern Linux, nor Unix historically. The GNU HURD was supposed to, IIRC, have bidirectional pipes as one of its features.
It's in the spirit of UNIX v6, but it's written for multicore Intel x86 machines in ANSI C.
If you're interested in studying UNIX for teaching or learning OS design, xv6 is a great starting point.
I also looked at the pipe implementation in Minix, which is a (non-trivial) variant of John S. Dyson's implementation that the BSDs share. It is implemented as a server (in the microkernel sense), so there's quite some added complexity there in handling "vmount"s and locking, but there are still some familiar elements of the code too, such as the "put it all together with flags" code in create_pipe().
https://github.com/Stichting-MINIX-Research-Foundation/minix...
For something even further along these lines, there's also the pipe implementation from Plan9, which at first glance felt so unfamiliar that I wasn't sure I was looking in the right place:
It's a kernel level file server.
foo = qux(bar(foo(x)))
see 18.2.1 and 18.2.4 in https://r4ds.had.co.nz/pipes.html
much sugar
pipes are cool until they aren't
foo = qux @ bar @ foo(x)
which resembles the function composition operator in mathematics (https://en.wikipedia.org/wiki/Function_composition). Piping is basically the reverse notation, i.e. foo = foo(x) | bar | qux.