今天继续研究管道,话不多说,言归正传:

对于管道,有一定的读写规则,所以这里主要是对它的规则进行探讨,具体规则如下:

规则一:

下面用程序来验证下,还是用上节学的子进程写数据,父进程读取数据的例子,只是基于这个程序进行修改来解释上面的理论,先看一下这个原程序:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int pipefd[];
if (pipe(pipefd) == -)
ERR_EXIT("pipe error"); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork error"); if (pid == )
{
close(pipefd[]);
write(pipefd[], "hello", );
close(pipefd[]);
exit(EXIT_SUCCESS);
} close(pipefd[]);
char buf[] = {};
int ret = read(pipefd[], buf, );
if (ret == -)
ERR_EXIT("read error");
printf("buf=%s\n", buf); return ; }

编译运行:

先来验证第一条理论,"O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止",为了看到效果,我们在子进程里写数据之前休眠3秒中,这样父进程在读取数据时就是一个没有数据状态,如下:

编译运行:

从运行效果来看,在没有发送数据之前的3秒,父进程读取状态是阻塞的,直到子进程写了数据,这是默认的行为。

下面,再来验证“O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN”这条理论,所以我们将read的文件描述符设置为非阻塞模式(O_NONBLOCK),如下:

编译运行:

可见,在非阻塞模式下,read时会错误提示。

规则二:

具体代码如下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int pipefd[];
if (pipe(pipefd) == -)
ERR_EXIT("pipe error"); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork error"); if (pid == )
{
close(pipefd[]);//子进程关闭了管道对应的写端
exit(EXIT_SUCCESS);
} close(pipefd[]);//父进程关闭了管道对应的写端
sleep();
char buf[] = {};
int ret = read(pipefd[], buf, );//这时看一下读端返回值
printf("ret = %d\n", ret); return ; }

编译运行:

从结果中可以看出,确实是这样。

规则三:

具体代码如下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int pipefd[];
if (pipe(pipefd) == -)
ERR_EXIT("pipe error"); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork error"); if (pid == )
{
close(pipefd[]);//子进程关闭读端
exit(EXIT_SUCCESS);
} close(pipefd[]);//父进程也关闭读端
sleep();//休眠一秒也就是为了让子进程代码执行了close操作
int ret = write(pipefd[], "hello", );//看下是否会产生SIGPIPE,而默认它的行为就是终止当前进程
if (ret == -)
printf("write error\n"); return ; }

编译运行:

从运行结果来看,确实wirte error没有执行到,是因为write操作产生了SIGPIPE信号,从这个运行结果可能不是很确实就是产生了这个信号,那我们改变一下SIGPIPE的默认行为,就能知道了,修改代码如下:

编译运行:

规则四:

先来验证第一条理论,"O_NONBLOCK disable: write调用阻塞,直到有进程读走数据",实验原理很简单,就是不断往管道里面写东西,因为默认就是阻塞模式,所以看一下当管道满的时候,是否阻塞了,具体代码如下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int pipefd[];
if (pipe(pipefd) == -)
ERR_EXIT("pipe error"); int ret;
int count = ;//计划一下管道的大小,每写一个字符进行累加
while ()
{//不断往管道里面写东西
ret = write(pipefd[], "A", );
if (ret == -)
{
printf("err=%s\n", strerror(errno));
break;
}
count++;
}
printf("count=%d\n", count);
return ;
}

编译运行:

从运行结果来看,确实是阻塞了,我们打印管道大小的语句也没打印出来,论证了第一条观点,接下来来论证第二个观点:"O_NONBLOCK enable:调用返回-1,errno值为EAGAIN",实验原理就是将管道的写端描述符改成非阻塞模式,看下这次还会阻塞么?如果不阻塞了,那管道的最大容量是多少呢?具体代码如下:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int pipefd[];
if (pipe(pipefd) == -)
ERR_EXIT("pipe error"); int ret;
int count = ;//计划一下管道的大小,每写一个字符进行累加
int flags = fcntl(pipefd[], F_GETFL);
fcntl(pipefd[], F_SETFL, flags | O_NONBLOCK);//将写端改为非阻塞模式
while ()
{//不断往管道里面写东西
ret = write(pipefd[], "A", );
if (ret == -)
{
printf("err=%s\n", strerror(errno));
break;
}
count++;
}
printf("count=%d\n", count);
return ;
}

编译运行:

这次,可以清晰地看到,当管道写满时是不会阻塞的,且返回了错误码为EAGAIN,而且可以看到管道的最大容量为65536个字符,也就是64K的容量,实际上,关于这个,可以在man帮助中查找到:

最后一个规则:

【说明】:上图中提示的"写入原子性"是指:如果写入的数据量不大于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() #define TEST_SIZE 68*1024//定义一个68K大小的缓冲区,以便撑爆管道的PIP_BUF大小的容量来看其是否保证原子性 int main(void)
{
char a[TEST_SIZE];
char b[TEST_SIZE];//定义了两个缓冲区,都是68K的大小 memset(a, 'A', sizeof(a));//将a缓冲区内容都初使化为A
memset(b, 'B', sizeof(b));//将b缓冲区内容都初使化为A int pipefd[]; int ret = pipe(pipefd);//创建一个管道
if (ret == -)
ERR_EXIT("pipe error"); pid_t pid;
pid = fork();
if (pid == )
{//第一个子进程
close(pipefd[]);
ret = write(pipefd[], a, sizeof(a));//往管道中写入a缓冲区,看一下这个数据是连续的么?还是被下面第二个进程的数据给穿插了
printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
exit();
} pid = fork(); if (pid == )
{
//第二个子进程式,代码跟第一个子进程的类似
close(pipefd[]);
ret = write(pipefd[], b, sizeof(b));
printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
exit();
} close(pipefd[]);//父进程,关闭写端 sleep();//休眠一秒的作用是为了让子进程都write数据至管道了
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, );//将子进程写入管道的数据读到test.txt文件中进行查看
char buf[*] = {};
int n = ;
while ()
{
ret = read(pipefd[], buf, sizeof(buf));//每次从管道中读取4个字节,以便进行观察原子性
if (ret == )
break;
printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[]);
write(fd, buf, ret);//然后再写入到文件中 }
return ;
}

编译运行:

再次运行:

关于小于PIPE_BUF这时就不演示了,这种情况肯定是能保证原子性的,关于这些规则,实际上在man帮助里面都可以看到:

【注意】:管道的容量之前我们已经验证过了是65536,而PIPE_BUF的大小为4096,也就是说这两者是不划等号的,需要注意。

好了,今天学到这,下回下见!!

linux系统编程之管道(二)的更多相关文章

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

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

  2. linux系统编程之管道(二):管道读写规则

    一,管道读写规则 当没有数据可读时 O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止. O_NONBLOCK enable:read调用返回-1,errn ...

  3. linux系统编程之管道(三):命令管道(FIFO)

    一,匿名管道PIPE局限性 管道的主要局限性正体现在它的特点上: 只支持单向数据流: 只能用于具有亲缘关系的进程之间: 没有名字: 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配 ...

  4. linux系统编程之管道(一):匿名管道(pipe)

    一,什么是管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道: 只能用于父子进程或者兄弟进程之间(具有 ...

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

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

  6. Linux系统编程—有名管道

    ▋****1. 管道的概念 管道,又名「无名管理」,或「匿名管道」,管道是一种非常基本,也是使用非常频繁的IPC方式. 1.1 管道本质 管道的本质也是一种文件,不过是伪文件,实际上是一块内核缓冲区, ...

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

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

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

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

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

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

随机推荐

  1. 【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)

    我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时 ...

  2. java:网络编程(UDP (DatagramSocket和DatagramPacket)正则表达式)

    java:网络编程(UDP (DatagramSocket和DatagramPacket)正则表达式) * TCP* 特点:面向连接,点对点的通信,效率较低,但安全可靠* UDP:用户数据报协议,类似 ...

  3. Postman系列三:Postman中post接口实战(上传文件、json请求)

    一:接口测试过程中GET请求与POST请求的主要区别 从开发角度我们看get与post的主要区别是:1.Get是用来从服务器上获得数据,而Post是用来向服务器上传递数据:2.Get安全性比Post低 ...

  4. spring security实现记住我下次自动登录功能

    目录 spring security实现记住我下次自动登录功能 一.原理分析 二.实现方式 2.1 简单实现方式 2.2 数据库实现方式 三.区分是密码登录还是rememberme登录 spring ...

  5. Linux命令xargs的使用

    ls | xargs catls | xargs -I {} cat {}  大写I,指定参数的替换符号为{} 自定义

  6. Linux 总结篇

    1. sudo -i update upgrade install 包名 (openjdk-8-jdk) autoremove 自动删除不需要的包(remove卸载) sudo apt-get 2. ...

  7. if的条件表达式

    常用的: [ -a FILE ] 如果 FILE 存在则为真. [ -d FILE ] 如果 FILE 存在且是一个目录则返回为真. [ -e FILE ] 如果 指定的文件或目录存在时返回为真. [ ...

  8. 判断密码是否可见/判断登录的状态/判断在form表单中 定义rules规则验证(iview)

    一: 判断密码是否可见判断:type="visiblePassword ? 'text' : 'password'" 是否为false 或者 true 密码为输入框或者文本框点击眼 ...

  9. 终于明白六大类UML类图关系了

    UML,全称Unified Modeling Language,统一建模语言.而UML图分为用例图.类图.对象图.状态图.活动图.时序图.协作图.构件图.部署图等9种图. 在面向对象语言中,我们经常看 ...

  10. java8之lambda表达式(默认方法)

    [推荐]2019 Java 开发者跳槽指南.pdf(吐血整理)>>> 许多开发语言都将函数表达式集成到了其集合库中.这样比循环方式所需的代码更少,并且更加容易理解.以下面的循环为例: ...