我们之前一直用的是vim来编写代码,现在有了vscode这样强大的编辑器,我们可以把我们的vim放一边了,如果还有小伙伴还没有配置好vscode的远端,可以点击这里:
我们今天进入管道的学习:
在计算机领域,管道(Pipeline)是一种将多个命令连接在一起以形成数据流的机制。它允许一个命令的输出成为另一个命令的输入,从而实现命令之间的数据传递和处理。
在 Unix/Linux 系统中,管道通常用竖线符号 | 表示。通过管道,可以将一个命令的输出传递给另一个命令进行处理,从而构建复杂的数据处理流程。
例如,假设我们有两个命令 command1 和 command2,我们可以使用管道将它们连接起来:
command1 | command2
这将会把 command1 的输出作为 command2 的输入,command2 将处理 command1 的输出并生成最终的结果。
管道的优势包括:
简化复杂任务: 管道可以将多个简单的命令组合成一个复杂的任务,使得任务的实现更加简单和高效。
模块化和可重用性: 通过将命令连接在一起,可以更好地组织代码并提高代码的可重用性。每个命令都可以专注于完成一个特定的任务。
减少临时文件: 管道可以避免将数据存储到临时文件中,从而减少了文件 I/O 的开销和磁盘空间的占用。
实时处理: 管道允许命令之间的实时数据传递,这对于需要连续处理数据的任务非常有用,比如日志处理、数据流分析等。
简单来说,管道就是连接多个指令。我们之前也在频繁使用管道:比如我们想统计当前登录到系统的用户数量。
who指令的结果作为wc -l的输入。
我们这里讲的简单一点,现在我们有一个进程,它自身会被以读和写的方式分别打开一次:
然后这个读和写都会往一个缓冲区输入输出数据:
这个时候父进程创建子进程,子进程发生浅拷贝,指向没有发生变化:
这里注意一下,管道一般是单向的,所以我们现在想让父进程读,让子进程写:
这样形成了一个单向通道,这个就是一个基本的匿名管道。
匿名管道(Anonymous Pipe)是一种用于进程间通信的机制,特别是在 Unix 和类 Unix 系统中。它允许一个进程将输出发送到另一个进程的输入,从而实现进程间的数据传输。
以下是匿名管道的一些关键特点:
单向通信:匿名管道是单向的,只能支持单向数据流。它只能用于单一方向的通信,通常是父进程到子进程或者相反。
创建:匿名管道通过调用系统调用 pipe() 来创建。这个系统调用创建了一个管道,返回两个文件描述符,其中一个用于读取管道,另一个用于写入管道。
父子进程通信:通常,匿名管道用于父子进程之间的通信。在创建子进程后,父进程可以将数据写入管道,而子进程则可以从管道中读取这些数据。
半双工:匿名管道是半双工的,意味着数据只能在一个方向上流动。如果需要双向通信,则需要创建两个管道,或者使用其他的进程间通信机制,比如命名管道或套接字。
进程同步:匿名管道通常用于进程间的同步和协作。一个进程可能会阻塞在读取管道上,直到另一个进程写入数据到管道中为止。
匿名管道在 Unix 系统中被广泛应用,特别是在 shell 编程和进程间通信方面。它提供了一种简单而有效的方式,允许不同进程之间进行数据交换和协作
我也有专门创建管道的函数pipe:
我们可以来试一下:
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;
int main()
{
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
cout<<"pipefd[0]"<<"--->"<<pipefd[0]<<"pipefd[1]"<<"--->"<<pipefd[1]<<endl;
return 0;
}
运行:
这里我们发现pipefd[0]指代的是3,而我们的pipefd[1]指代的是4。其实也很好理解,因为0,1,2被标准输入,标准输出,标准错误占了。所以从3开始。
同时,如果我么查手册会看到这样一段话:
这段话的主要意思是pipefd[0]是读端,而pipefd[1]是写端。这为我们以后哪个开哪个关提供了依据。
我们先搭建架子来观察我们匿名管道的现象:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
}
if(id ==0)
{
//子进程要做的事
exit(0);
}
//父进程要做的事
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
现在我们想让子进程写,父进程读,我们把相应用不到的管道关闭:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们让子进程写入一些东西,然后让父进程来读,看看行不行:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(1);
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们看到父进程真的拿到了子进程写的东西,这就是一个最基本的管道的应用。
我们模拟一下,写端慢,读端快的情况
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(100); //模拟写端慢
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们发现父进程处于一个休眠的状态,很明显,它是在等待我们的子进程进行写入。
这里我们可以得出匿名管道具有同步机制,读端和写端是协同工作的。
我们调换一下,让写端快,读端快:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10000;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
cout<<"writing......"<<endl;
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
执行:
过了2秒之后:
数据一瞬间出来了。
这里我们可以得出匿名管道是面向字节流的,它没有硬性规定我写一条你必须马上读一条,而是以字节流的形式读或写。
我们可以写一段代码来测试我们管道的大小:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(1)
{
// //缓冲区
// char message[MAX];
// //向缓冲区里写
// snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
// cnt--;
// //向管道写
// write(pipefd[1],message,strlen(message));
// cout<<"writing......"<<endl;
char c = 'a';
write(pipefd[1], &c, 1);
cnt++;
cout << "write ....: " << cnt << endl;
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
// sleep(2); //睡眠2秒
// ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
// if(n > 0)
// {
// cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
// }
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们发现最后结果是65536,折合下来也就是64kb左右的大小。
我们也可以用指令来查看管道大小:ulimit -a:
我们查看的管道大小为512 * 8 = 4kb,好像比我们看到的小。这个其实不是真正的大小。
我们现在让写段写一段时间后直接关闭,但是读端没有关闭:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(1)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt++;
//向管道写
write(pipefd[1],message,strlen(message));
//跳出
if(cnt > 3) break;
// char c = 'a';
// write(pipefd[1], &c, 1);
// cnt++;
// cout << "write ....: " << cnt << endl;
}
//关闭写端
close(pipefd[1]);
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
//sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
cout<<"father return value:"<< n << endl;
sleep(1);
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
这样表示:写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾。
同时注意,进程退出,管道自动关闭。
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(true)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt++;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(1);
//跳出
//if(cnt > 3) break;
// char c = 'a';
// write(pipefd[1], &c, 1);
// cnt++;
// cout << "write ....: " << cnt << endl;
sleep(1);
}
//关闭写端
//close(pipefd[1]);
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
//sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
cout<<"father return value:"<< n << endl;
sleep(1);
//直接跳出
break;
}
//关闭读端
close(pipefd[0]);
sleep(5);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid == id)
{
cout << "wait success, child exit sig: " << (status&0x7F) << endl;
}
// //回收子进程
// pid_t rid = waitpid(id,nullptr,0);
// if(rid == id)
// {
// cout<<"wait success"<<endl;
// }
return 0;
}
我们得到一下它的信号:
我们查一下13号信号:
13号信号是:SIGPIPE:
SIGPIPE 是在进程向一个已经被关闭的管道(或者其他的类似的通信方式)写入数据时,内核向该进程发送的信号。这个信号的默认行为是终止进程。
常见的场景是,一个进程向另一个进程通过管道发送数据,但接收数据的进程提前退出,导致写入数据的进程尝试往已关闭的管道写入数据。在这种情况下,内核会发送 SIGPIPE 信号给写入数据的进程,通知它目标进程已经退出,不再接收数据。
所以我们才有上述现象。
总结一下管道有4种情况:
管道的4种情况
- 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
- 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
- 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
- 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程
5种特性:
管道的5种特性
- 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
- 匿名管道,默认给读写端要提供同步机制 — 了解现象就行
- 面向字节流的 — 了解现象就行
- 管道的生命周期是随进程的
- 管道是单向通信的,半双工通信的一种特殊情况
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法