3

I want to use pipes on Linux as a synchronization primitive between a master process and a slave process. The classic way is to create two pipes, but I believe there's a way to use a single fd instead. Consider:

  • The slave creates r-w pipe.
  • Read end r is passed to the master.
  • When the slave is ready, it writes to w N bytes, then N bytes again, then 1 byte, where N is the pipe buffer size. The first write(2) returns immediately, the second blocks because the buffer is full.
  • Master blocks and reads from r. The second write(2) returns, the third write(2) blocks.
  • After the master has read data, it does whatever stuff it has to.
  • When the slave is to be resumed, master reads once more from r. The third write(2) returns and the slave proceeds.

However, the man page for fcntl says this:

   Changing the capacity of a pipe
       F_SETPIPE_SZ (int; since Linux 2.6.35)
              ...

              Note that because of the way the pages of the pipe buffer are
              employed when data is written to the pipe, the number of bytes
              that can be written may be less than the nominal size, depend‐
              ing on the size of the writes.

The man page seems to say that if the pipe buffer size is N bytes and I write M<=N bytes to the pipe, it is possible that the write will block. In what cases can that happen (except the simple case when there is already much data in the pipe)?

Additionally, "depending on the size of the writes" sounds odd. Can I get this strange behavior if I write exactly N bytes?

1
  • I meant to say that I know that the pipe will block if, say, the buffer size is 16 bytes, I'm writing 8 bytes to it but there are already 12 bytes pending. What happens if the pipe is not empty is not important to me, I'm only interested in the case when the pipe is empty. I'll rephrase the question. Commented Aug 14, 2020 at 11:39

2 Answers 2

2

I would not rely on this element of behaviour. Pipes are intended to be a continuous stream of data. Reads and writes cannot be matched against each other easily, the only real guarantee you should rely on is that the first bytes in will be the first bytes out.

The reason for the manual comment regarding buffer paging is that pipes rely on a ring buffer. From the manual I would infer that the "ring" is a ring of pages not a ring of bytes. IE: pages fill up, when the page is full, the next page is used. Pages are not re-used until they have been fully read. This means a half-read page will not be available at all for writing. That's just an inference from the manual, I've not checked the source code.

The biggest problem with relying on this behaviour is it's an implementation detail rather than an intended effect of the pipe. Kernel developers may change this at any time and your code will suddenly have race conditions.

1
  • 1
    The inference is right, with the addition that the write merger code will leave unused space at the ends of pages in order to align the final characters of writes equal to or larger than the page size, with the ends of pages. Potentially as little as PAGE_SIZE+1 byte of every two pages could end up being used, coming close to using only 50% of buffer capacity. Ironically, the write pattern that triggers this is similar to what is in the question.
    – JdeBP
    Commented Aug 14, 2020 at 16:46
0

Being said that, if the only intention is synchronizing, the "classic way",as you call it, has never ever been using pipes. Use semaphores or signals instead.

Because the pipe capacity changes depending on implementations and has actually changed several times since the first POSIX implementation... the documentation you refer to explicitly writes :

Applications should not rely on a particular capacity

If I understand it correctly then you should find the answer to your question as part of the man page for pipe :

POSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence. Writes of more than PIPE_BUF bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written

4
  • 1
    The processes are owned by different users and are started/managed by a bash script, so I can use neither semaphores nor signals. Commented Aug 14, 2020 at 10:40
  • I believe 'Applications should not rely on a particular capacity' means that I cannot assume the capacity is always 65536 bytes. I'd use F_GETPIPE_SZ to retrieve pipe buffer size. Also, I don't see how atomicity is related to my question, there's a single process writing to pipe and a single process reading from it. Commented Aug 14, 2020 at 10:42
  • @Ivanq : bash handles signals quite well : tldp.org/LDP/Bash-Beginners-Guide/html/chap_12.html
    – MC68020
    Commented Aug 14, 2020 at 11:08
  • 1
    One cannot (safely) send signals between processes owned by different users. Commented Aug 14, 2020 at 11:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .