Non-blocking I/O with pipes in C
Last Updated :
15 Sep, 2023
Prerequisite: pipe() System call
When I/O block in pipe() happens?
Consider two processes, one process that's gathering data(read data) in real time and another process that's plotting it(write data). The two processes are connected by a pipe, with the data acquisition process feeding the data plotting process. Speed of the data acquisition of two process is different.
The default behavior in a pipe is for the writing and reading ends of a pipe is to exhibit blocking behavior, if the partner process is slower. This is bad because the data acquisition process can wait on the plotting process(write data). So, during the data acquisition process, block read call in pipe and program hangs. If we want this not to happen, we must close the write end in process before read end call.
In simple language,
- A read call gets as much data as it requests or as much data as the pipe has, whichever is less
- If the pipe is empty
- Reads on the pipe will return EOF (return value 0) if no process has the write end open
- If some process has the pipe open for writing, read will block in anticipation of new data
Non-blocking I/O with pipes
Sometimes it's convenient to have I/O that doesn't block i.e we don't want a read call to block on one in case of input from the other. Solution for this is the given function:
To specify non-blocking option:
#include<fcntl.h>
int fd;
fcntl(fd, F_SETFL, O_NONBLOCK);
- fd: file descriptor
- F_SETFL: Set the file status flags to the value specified by arg. File access mode here in our purpose use only for O_NONBLOCK flag.
- O_NONBLOCK: use for non-blocking option.
- 0: return on successful
- -1: return on error, set errorno
After this function runs successfully, a call to read/write returns -1 if pipe
is empty/full and sets errno to EAGAIN
Example: Child writes “hello” to parent every 3 seconds and Parent does a non-blocking read each second.
C
// C program to illustrate
// non I/O blocking calls
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h> // library for fcntl function
#define MSGSIZE 6
char* msg1 =“hello”;
char* msg2 =“bye !!”;
int main()
{
int p[2], i;
// error checking for pipe
if (pipe(p) < 0)
exit(1);
// error checking for fcntl
if (fcntl(p[0], F_SETFL, O_NONBLOCK) < 0)
exit(2);
// continued
switch (fork()) {
// error
case -1:
exit(3);
// 0 for child process
case 0:
child_write(p);
break;
default:
parent_read(p);
break;
}
return 0;
}
void parent_read(int p[])
{
int nread;
char buf[MSGSIZE];
// write link
close(p[1]);
while (1) {
// read call if return -1 then pipe is
// empty because of fcntl
nread = read(p[0], buf, MSGSIZE);
switch (nread) {
case -1:
// case -1 means pipe is empty and errono
// set EAGAIN
if (errno == EAGAIN) {
printf(“(pipe empty)\n”);
sleep(1);
break;
}
else {
perror(“read”);
exit(4);
}
// case 0 means all bytes are read and EOF(end of conv.)
case 0:
printf(“End of conversation\n”);
// read link
close(p[0]);
exit(0);
default:
// text read
// by default return no. of bytes
// which read call read at that time
printf(“MSG = % s\n”, buf);
}
}
}
void child_write(int p[])
{
int i;
// read link
close(p[0]);
// write 3 times "hello" in 3 second interval
for (i = 0; i < 3; i++) {
write(p[1], msg1, MSGSIZE);
sleep(3);
}
// write "bye" one times
write(p[1], msg2, MSGSIZE);
// here after write all bytes then write end
// doesn't close so read end block but
// because of fcntl block doesn't happen..
exit(0);
}
Output:
(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=hello
(pipe empty)
(pipe empty)
(pipe empty)
MSG=bye!!
End of conversation
Atomic writes in pipe
Atomic means no other process ever observes that its partially done. Reading or writing pipe data is atomic if the size of data written is not greater than PIPE_BUF(4096 bytes). This means that the data transfer seems to be an instantaneous unit means nothing else in the system can observe a state in which it is partially complete. Atomic I/O may not begin right away (it may need to wait for buffer space or for data), but once it does begin it finishes immediately.
Data Writes of up to PIPE_BUF (4096 Bytes) are atomic. Reading or writing a larger amount of data may not be atomic; For example, output data from other processes sharing the descriptor may be interspersed. Also, once PIPE_BUF characters have been written, further writes will block until some characters are read
Example: Process 1 sends a 100-byte message at the same time, process 2 sends a 100-byte message No guarantee about the order, but pipe will receive all of one message followed by all of the other.
In non atomic writes for larger writes there is no such guarantee, data could get confusingly intermingled like, this:
Pipe Capacity
- A pipe can hold a limited number of bytes.
- Writes fill the pipe and block when the pipe is full
- They block until another process reads enough data at the other end of the pipe and return when all the data given to write have been transmitted
- Capacity of a pipe is at least 512 bytes, usually more (system dependent)
C
// C program to illustrate
// finding capacity of pipe
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int count = 0;
// SIGALRM signal handler
void alrm_action(int signo)
{
printf("Write blocked after %d characters\n", count);
exit(0);
}
int main()
{
int p[2];
char c = 'x';
// SIGALRM signal
signal(SIGALRM, alrm_action);
// pipe error check
if (pipe(p) == -1)
exit(1);
while (1) {
alarm(5);
// write 'x' at one time when capacity full
// write() block and after 5 second alarm
write(p[1], &c, 1);
// send signal and alrm_action handler execute.
++count;
alarm(0);
}
}
Output:
Write blocked after 65536 characters
//output depend on the system so output may change in different system
Here, in while loop first 5 second alarm is set after write() call writes one character 'x' in the pipe. And count variable is used for count character write in the pipe. strong>alarm(0) means cancel the set alarm of 5 second. After some time when pipe capacity is full then write() call is block and program does not execute next instruction, after 5 second set alarm ringing and send signal SIGALRM. After that alram_action handler executes and prints how many maximum character can be written by pipe.
Similar Reads
C Programming Language Tutorial C is a general-purpose mid-level programming language developed by Dennis M. Ritchie at Bell Laboratories in 1972. It was initially used for the development of UNIX operating system, but it later became popular for a wide range of applications. Today, C remains one of the top three most widely used
5 min read
Dynamic Memory Allocation in C using malloc(), calloc(), free() and realloc() In C, a variable defined in a function is stored in the stack memory. The requirement of this memory is that it needs to know the size of the data to memory at compile time (before the program runs). Also, once defined, we can neither change the size nor completely delete the memory.To resolve this,
9 min read
Data Types in C Each variable in C has an associated data type. It specifies the type of data that the variable can store like integer, character, floating, double, etc.Example:C++int number;The above statement declares a variable with name number that can store integer values.C is a statically type language where
5 min read
C Language Introduction C is a general-purpose procedural programming language initially developed by Dennis Ritchie in 1972 at Bell Laboratories of AT&T Labs. It was mainly created as a system programming language to write the UNIX operating system.Main features of CWhy Learn C?C is considered mother of all programmin
6 min read
C Arrays An array in C is a fixed-size collection of similar data items stored in contiguous memory locations. It can be used to store the collection of primitive data types such as int, char, float, etc., as well as derived and user-defined data types such as pointers, structures, etc. Creating an Array in
7 min read
C Pointers A pointer is a variable that stores the memory address of another variable. Instead of holding a direct value, it has the address where the value is stored in memory. This allows us to manipulate the data stored at a specific memory location without actually using its variable. It is the backbone of
9 min read
C Programs To learn anything effectively, practicing and solving problems is essential. To help you master C programming, we have compiled over 100 C programming examples across various categories, including basic C programs, Fibonacci series, strings, arrays, base conversions, pattern printing, pointers, and
8 min read
Basics of File Handling in C File handling in C is the process in which we create, open, read, write, and close operations on a file. C language provides different functions such as fopen(), fwrite(), fread(), fseek(), fprintf(), etc. to perform input, output, and many different C file operations in our program.Need of File Han
13 min read
Operators in C In C language, operators are symbols that represent some kind of operations to be performed. They are the basic components of the C programming. In this article, we will learn about all the operators in C with examples.What is an Operator in C?A C operator can be defined as the symbol that helps us
11 min read
Bitwise Operators in C In C, bitwise operators are used to perform operations directly on the binary representations of numbers. These operators work by manipulating individual bits (0s and 1s) in a number.The following 6 operators are bitwise operators (also known as bit operators as they work at the bit-level). They are
6 min read