一,管道读写规则

当没有数据可读时

  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道满的时候

  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

二,验证示例

示例一:O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> int main(void)
{
int fds[2];
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
close(fds[0]);//子进程关闭读端
sleep(10);
write(fds[1],"hello",5);
exit(EXIT_SUCCESS);
} close(fds[1]);//父进程关闭写端
char buf[10] = {0};
read(fds[0],buf,10);
printf("receive datas = %s\n",buf);
return 0;
}

结果:

说明:管道创建时默认打开了文件描述符,且默认是阻塞(block)模式打开

所以这里,我们让子进程先睡眠10s,父进程因为没有数据从管道中读出,被阻塞了,直到子进程睡眠结束,向管道中写入数据后,父进程才读到数据

示例二:O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> int main(void)
{
int fds[2];
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
close(fds[0]);//子进程关闭读端
sleep(10);
write(fds[1],"hello",5);
exit(EXIT_SUCCESS);
} close(fds[1]);//父进程关闭写端
char buf[10] = {0};
int flags = fcntl(fds[0], F_GETFL);//先获取原先的flags
fcntl(fds[0],F_SETFL,flags | O_NONBLOCK);//设置fd为阻塞模式
int ret;
ret = read(fds[0],buf,10);
if(ret == -1){ perror("read error");
exit(EXIT_FAILURE);
} printf("receive datas = %s\n",buf);
return 0;
}

结果:

示例三:如果所有管道写端对应的文件描述符被关闭,则read返回0

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> int main(void)
{
int fds[2];
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
close(fds[1]);//子进程关闭写端
exit(EXIT_SUCCESS);
} close(fds[1]);//父进程关闭写端
char buf[10] = {0}; int ret;
ret = read(fds[0],buf,10);
printf("ret = %d\n", ret); return 0;
}

结果:

可知确实返回0,表示读到了文件末尾,并不表示出错

示例四:如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h> void sighandler(int signo);
int main(void)
{
int fds[2];
if(signal(SIGPIPE,sighandler) == SIG_ERR)
{
perror("signal error");
exit(EXIT_FAILURE);
}
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
close(fds[0]);//子进程关闭读端
exit(EXIT_SUCCESS);
} close(fds[0]);//父进程关闭读端
sleep(1);//确保子进程也将读端关闭
int ret;
ret = write(fds[1],"hello",5);
if(ret == -1){
printf("write error\n");
}
return 0;
} void sighandler(int signo)
{
printf("catch a SIGPIPE signal and signum = %d\n",signo);
}

结果:

可知当所有读端都关闭时,write时确实产生SIGPIPE信号

示例五:O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> int main(void)
{
int fds[2];
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
int ret;
int count = 0;
while(1){
ret = write(fds[1],"A",1);//fds[1]默认是阻塞模式
if(ret == -1){
perror("write error");
break;
}
count++;
} return 0;
}

结果:

说明:fd打开时默认是阻塞模式,当pipe缓冲区满时,write操作确实阻塞了,等待其他进程将数据从管道中取走

示例六:O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> int main(void)
{
int fds[2];
if(pipe(fds) == -1){
perror("pipe error");
exit(EXIT_FAILURE);
}
int ret;
int count = 0;
int flags = fcntl(fds[1],F_GETFL);
fcntl(fds[1],F_SETFL,flags|O_NONBLOCK);
while(1){
ret = write(fds[1],"A",1);//fds[1]默认是阻塞模式
if(ret == -1){
perror("write error");
break;
}
count++;
}
printf("the pipe capcity is = %d\n",count); return 0;
}

结果:

可知也出现EGIN错误,管道容量是65536字节

man 7 pipe说明:

Pipe capacity
       A pipe has a limited capacity.  If the pipe is full, then a write(2)
will block or fail, depending on whether the O_NONBLOCK flag is set
(see below). Different implementations have different limits for the
pipe capacity. Applications should not rely on a particular
capacity: an application should be designed so that a reading process
consumes data as soon as it is available, so that a writing process
does not remain blocked. In Linux versions before 2.6.11, the capacity of a pipe was the same
as the system page size (e.g., 4096 bytes on i386). Since Linux
2.6.11, the pipe capacity is 65536 bytes.

三,管道写与PIPE_BUF关系

man帮助说明:
PIPE_BUF
       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: O_NONBLOCK disabled, n <= PIPE_BUF
All n bytes are written atomically; write(2) may block if
there is not room for n bytes to be written immediately
阻塞模式时且n<PIPE_BUF:写入具有原子性,如果没有足够的空间供n个字节全部写入,则阻塞直到有足够空间将n个字节全部写入管道
O_NONBLOCK enabled, n <= PIPE_BUF
If there is room to write n bytes to the pipe, then write(2)
succeeds immediately, writing all n bytes; otherwise write(2)
fails, with errno set to EAGAIN.
非阻塞模式时且n<PIPE_BUF:写入具有原子性,立即全部成功写入,否则一个都不写入,返回错误
O_NONBLOCK disabled, n > PIPE_BUF
The write is nonatomic: the data given to write(2) may be
interleaved with write(2)s by other process; the write(2)
blocks until n bytes have been written.
阻塞模式时且n>PIPE_BUF:不具有原子性,可能中间有其他进程穿插写入,直到将n字节全部写入才返回,否则阻塞等待写入
O_NONBLOCK enabled, n > PIPE_BUF
If the pipe is full, then write(2) fails, with errno set to
EAGAIN. Otherwise, from 1 to n bytes may be written (i.e., a
"partial write" may occur; the caller should check the return
value from write(2) to see how many bytes were actually
written), and these bytes may be interleaved with writes by
other processes.
   非阻塞模式时且N>PIPE_BUF:如果管道满的,则立即失败,一个都不写入,返回错误,如果不满,则返回写入的字节数为1~n,即部分写入,写入时可能有其他进程穿插写入
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

注:管道容量不一定等于PIPE_BUF

示例:当写入数据大于PIPE_BUF时

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) #define TEST_SIZE 68*1024 int main(void)
{
char a[TEST_SIZE];
char b[TEST_SIZE];
char c[TEST_SIZE]; memset(a, 'A', sizeof(a));
memset(b, 'B', sizeof(b));
memset(c, 'C', sizeof(c)); int pipefd[2]; int ret = pipe(pipefd);
if (ret == -1)
ERR_EXIT("pipe error"); pid_t pid;
pid = fork();
if (pid == 0)//第一个子进程
{
close(pipefd[0]);
ret = write(pipefd[1], a, sizeof(a));
printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
} pid = fork(); if (pid == 0)//第二个子进程
{
close(pipefd[0]);
ret = write(pipefd[1], b, sizeof(b));
printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
} pid = fork(); if (pid == 0)//第三个子进程
{
close(pipefd[0]);
ret = write(pipefd[1], c, sizeof(c));
printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
} close(pipefd[1]); sleep(1);
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
char buf[1024*4] = {0};
int n = 1;
while (1)
{
ret = read(pipefd[0], buf, sizeof(buf));
if (ret == 0)
break;
printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]);
write(fd, buf, ret); }
return 0;
}

结果:

可见各子进程间出现穿插写入,并没保证原子性写入,且父进程在子进程编写时边读。

linux系统编程之管道(二):管道读写规则的更多相关文章

  1. Linux系统编程——进程间通信:命名管道(FIFO)

    命名管道的概述 无名管道,因为没有名字,仅仅能用于亲缘关系的进程间通信(很多其它详情.请看<无名管道>).为了克服这个缺点.提出了命名管道(FIFO).也叫有名管道.FIFO 文件. 命名 ...

  2. linux系统编程之信号(二)

    经过了漫长的间歇,对于c语言的学习也被中断了很久,现实确实有很多的无耐,计划中的事情总会被打乱,但不管怎样,学习的道路是不能休止的,所以经过了一断温习后现在继续学习C语言,话不多说,进入正题: 信号分 ...

  3. linux系统编程之进程(二)

    今天继续学习进程相关的东东,上节提到了,当fork()之后,子进程复制了父进程当中的大部分数据,其中对于打开的文件,如果父进程打开了,子进程则不需要打开了,是共享的,所以首先先来研究下共享文件这一块的 ...

  4. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  5. linux系统编程之管道(三)

    今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...

  6. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  7. linux系统编程(一)概述

    glibc库封装了linux系统调用,并提供c语言接口 所以学习linux系统编程,主要参考glibc库系统调用相关api 一.进程控制: fork 创建一个新进程 clone 按指定条件创建子进程 ...

  8. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  9. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  10. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

随机推荐

  1. ubuntu 下当前网速查看

    ubuntu下用ethstatus可以监控实时的网卡带宽占用.这个软件能显示当前网卡的 RX 和 TX 速率,单位是Byte 一.安装 ethstatus 软件 #sudo apt-get insta ...

  2. iOS 各种方法

    tableViewCell分割线左对齐: - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)c ...

  3. [leetcode]318. Maximum Product of Word Lengths单词长度最大乘积

    Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the tw ...

  4. 如何进入/home/user/.wine

    命令行输入 :cd /home/user/.wine/drive_c/windows/fonts /home是linux的用户目录,/user是用户名/.wine是隐藏目录,凡是以.开头的都是隐藏目录 ...

  5. win下安装composer

    1.在官网上下载composer的安装程序. https://getcomposer.org/ 2.双击运行Composer-Setup.exe程序,选择你自已的php安装目录打到php.exe.   ...

  6. dede数据库内容替换,去掉文章内容中的img标签

    1.织梦已经给我们准备好了数据库内容替换工具,在采集->批量维护->数据库内容替换 2.织梦的文章内容一般在放在dede_addonarticle表body字段中. (1).选择好数据表和 ...

  7. 2018.10.19 NOIP模拟 加密(模拟)

    传送门 直接按hashhashhash函数反着算回去就行了. 加法用exgcdexgcdexgcd,异或直接枚举二进制位. 代码

  8. 2018.08.31 bzoj1426 收集邮票(期望dp)

    描述 有n种不同的邮票,皮皮想收集所有种类的邮票.唯一的收集方法是到同学凡凡那里购买,每次只能买一张,并且 买到的邮票究竟是n种邮票中的哪一种是等概率的,概率均为1/n.但是由于凡凡也很喜欢邮票,所以 ...

  9. phalapi框架where条件查询

    // WHERE name = 'dogstar' AND age = 18 $user->where(array('name' => 'dogstar', 'age' => 18) ...

  10. 树莓派安装mono

    http://www.mono-project.com/download/ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --r ...