前提条件:pipe()系统调用
当pipe()中的I / O块发生时?
考虑两个过程,一个过程实时收集数据(读取数据),另一个过程绘制数据(写入数据)。这两个过程通过管道连接,数据采集过程为数据绘图过程提供动力。两个过程的数据采集速度是不同的。
管道中的默认行为是,如果伙伴进程较慢,则管道的写入和读取端将表现出阻塞行为。这很不好,因为数据获取过程可能要等待绘图过程(写数据)。因此,在数据获取过程中,管道中的块读取调用和程序挂起。如果我们不希望这种情况发生,则必须在读取结束调用之前关闭写入中的进程。
用简单的语言,
- 读取调用可获取与请求一样多的数据,或者与管道一样多的数据(以较少者为准)
- 如果管道是空的
- 如果没有进程的写端打开,则对管道的读取将返回EOF(返回值0)
- 如果某个进程打开了用于写入的管道,则读取将阻止新数据的进入
管道的非阻塞I / O
有时,不阻塞I / O会很方便,例如,我们不希望在另一个输入的情况下对一个读取调用进行阻塞。解决方案是给定的函数:
To specify non-blocking option:
#include
int fd;
fcntl(fd, F_SETFL, O_NONBLOCK);
- 范围:
- fd:文件描述符
- F_SETFL:将文件状态标志设置为arg指定的值。我们此处的文件访问模式仅用于O_NONBLOCK标志。
- O_NONBLOCK:用于非阻塞选项。
- 返回值:
- 0:成功返回
- -1:返回错误,设置errorno
此函数成功运行后,如果管道成功,则对读/写的调用返回-1
为空/已满,并将errno设置为EAGAIN
示例:孩子每隔3秒钟向父母写一次“ hello”,而父母每秒进行一次非阻塞读取。
// C program to illustrate
// non I/O blocking calls
#include
#include
#include // 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);
}
输出:
(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
原子写入管道
原子意味着没有其他过程观察到它部分完成了。如果写入的数据大小不大于PIPE_BUF(4096 bytes),则读取或写入管道数据是原子的。这意味着数据传输似乎是一个瞬时单位,这意味着系统中的其他任何部分都无法观察到其部分完成的状态。原子I / O可能不会立即开始(它可能需要等待缓冲区空间或数据),但是一旦开始,它将立即完成。
最多可写PIPE_BUF(4096字节)的数据是原子的。读取或写入大量数据可能不是原子操作;例如,可以散布来自共享描述符的其他进程的输出数据。同样,一旦写入了PIPE_BUF字符,将阻止进一步的写入,直到读取了某些字符
示例:进程1同时发送100字节的消息,进程2发送100字节的消息不保证顺序,但是管道将接收所有一条消息,然后再接收其他所有消息。
在非原子写入中,对于较大的写入没有这样的保证,数据可能会像这样混乱地混合在一起:
管道容量
- 管道可以容纳有限数量的字节。
- 写满管道并在管道已满时阻塞
- 它们阻塞,直到另一个进程在管道的另一端读取了足够的数据,并在传输完所有要写入的数据后返回
- 管道的容量至少为512字节,通常更多(取决于系统)
// C program to illustrate
// finding capacity of pipe
#include
#include
#include
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);
}
}
输出:
Write blocked after 65536 characters
//output depend on the system so output may change in different system
在这里,在while循环中,在write()调用在管道中写入一个字符“ x”后,设置了第一个5秒警报。而count变量用于在管道中写入count字符。 strong>警报(0)表示取消设置的5秒警报。一段时间后,当管道容量已满时,将阻塞write()调用,并且在设置警报铃声5秒钟并发送信号SIGALRM后,程序将不执行下一条指令。之后, alram_action处理程序执行并打印管道可以写入的最大字符。