尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Linux进程管道

Linux进程管道 一匿名管道1.创建匿名管道只能用于同祖先进程之间通信将子进程的数据传给父进程#include iostream #include string #include unistd.h #include sys/types.h #include cstdio #include cstdlib #include cstring using namespace std; int main() { //在克隆进程前先创建一个管道 int pipefd[2];//装管道读写两端的fd的数组 int ret pipe(pipefd); if (ret -1) { perror(pipe); exit(0); } //复制进程 pid_t pid fork(); //父进程 if (pid 0) { char buf[1024] { 0 }; read(pipefd[0], buf, sizeof(buf)); printf(parrent recieve %s,pid:%d\n, buf, getpid()); } //子进程 else if (pid 0) { sleep(2); string msg Hello from child process; // .c_str() 获取 const char*适配系统调用write write(pipefd[1], msg.c_str(), msg.size()); } return 0; }0s主线程执行pipe()创建匿名管道 → 执行fork()分裂两个进程。子进程继承父进程全部文件描述符此时父子进程同时持有管道读端、写端两份 fd分裂瞬间父进程立刻进入 if (pid0) 分支执行read(pipefd[0], buf, sizeof(buf));当前管道是空的管道阻塞特性生效 →父进程被操作系统挂起、休眠阻塞不再继续运行等待管道内出现数据分裂瞬间子进程进入 else if (pid0) 分支执行sleep(1)→ 子进程主动睡眠 1 秒。第 2 秒到来子进程 sleep 结束执行write(pipefd[1], str, strlen(str));将字符串写入管道内核缓冲区数据写入管道的一瞬间内核发现管道有数据唤醒被阻塞的父进程 read 函数父进程read读取到缓冲区数据填充到 buf 数组执行 printf 打印内容父子进程各自执行到return 0进程正常退出2.匿名管道双向传输#include iostream #include string #include unistd.h #include sys/types.h #include cstdio #include cstdlib #include cstring using namespace std; int main() { //在克隆进程前先创建一个管道 int pipefd[2];//装管道读写两端的fd的数组 int ret pipe(pipefd); if (ret -1) { perror(pipe); exit(0); } //复制进程 pid_t pid fork(); //父进程 if (pid 0) { char buf[1024] { 0 }; while (1) { read(pipefd[0], buf, sizeof(buf)); printf(parrent recieve %s,pid:%d\n, buf, getpid()); string msg Hello from parent process; // .c_str() 获取 const char*适配系统调用write write(pipefd[1], msg.c_str(), msg.size()); sleep(2); } } //子进程 else if (pid 0) { char buf[1024] { 0 }; while (1) { string msg Hello from child process; // .c_str() 获取 const char*适配系统调用write write(pipefd[1], msg.c_str(), msg.size()); sleep(2); read(pipefd[0], buf, sizeof(buf)); printf(childen recieve %s,pid:%d\n, buf, getpid()); } } return 0; }但是不建议这么写容易混乱匿名管道是单向通信 管道规则pipefd[0] 读端pipefd[1] 写端数据流只能写端 → 读端天然半双工不能直接双向收发。现在父子共用同一条管道双向读写会出现严重冲突 父子都持有pipefd[0] 读pipefd[1] 写双方互相抢管道极易阻塞、消息错乱、死锁。2.实现 ps aux | grep xxx 父子进程间通信子进程 ps aux, 子进程结束后将数据发送给父进程父进程获取到数据过滤pipe()execlp()子进程将标准输出 stdout_fileno 重定向到管道的写端。 dup2#include unistd.h #include sys/types.h #include stdio.h #include stdlib.h #include string.h #include wait.h int main() { //创建管道 int fd[2]; int ret pipe(fd); if (ret -1) { perror(pipe); exit(0); } //复制进程 pid_t pid fork(); if (pid 0) { close(fd[1]);//关闭写端 char buf[1024] { 0 }; while (read(fd[0], buf, sizeof(buf)) 0) { printf(%s, buf); memset(buf, 0, 1024); } } else if (pid 0) { close(fd[0]); dup2(fd[1], STDOUT_FILENO);//重定向将输出到终端重定向到管道输入端 execlp(ps, ps, aux, NULL); perror(execlp); exit(0); } else { exit(0); } }execlp:本质上就是用 C 语言代码100% 还原了在终端Terminal里敲下ps aux并按下回车的整个过程你在终端敲下命令的第一个词ps第 1 个参数ps你敲ps时Shell 会去系统路径PATH里找ps这个软件在哪里。代码里的第一个ps就是让execlp去寻找这个可执行文件。操作系统启动ps进程第 2 个参数ps当程序跑起来时它需要知道“我自己叫什么”。终端会自动把ps传给程序的argv[0]代码里也必须显式传入这个ps。你在ps后面空格跟的选项aux第 3 个参数aux终端里空格后面的内容就是参数。代码里的aux对应ps程序的argv[1]告诉ps“我要查看**所有用户a/u/x**的进程信息”。你在终端按下了回车Enter第 4 个参数NULL回车代表“我的命令敲完了请执行”。代码里的NULL就是那个“回车”告诉内核“参数全部传完了可以开始执行了”。如果你在终端换一个命令代码怎么变理解了这种对应关系后你可以轻松把代码改成执行其他命令例子 1在终端敲grep bash终端grep bash代码execlp(grep, grep, bash, NULL);例子 2在终端敲ls -l -a /home终端ls -l -a /home代码execlp(ls, ls, -l, -a, /home, NULL);总结execlp(ps, ps, aux, NULL)并不是什么神奇的固定咒语它就是在 C 语言层面调用ps aux指令的标准写法。3.管道读写总结1读管道行为有数据时返回值read返回实际读到的字节数无数据时写端全关闭read返回0类似文件结束符EOF写端未全关read会阻塞等待直到有数据可读才返回2写管道行为读端全关闭信号机制进程会收到SIGPIPE信号默认处理通常导致进程异常终止读端未全关管道已满write会阻塞等待可用空间管道未满write返回实际写入的字节数设置管道非阻塞int flags fcntl(fd[0], F_GETFL); // 获取原来的flag flags | O_NONBLOCK; // 修改flag的值 fcntl(fd[0], F_SETFL, flags); // 设置新的flagfcntl(fd[0], F_GETFL);获取fd[0]当前的文件状态标志Flags。flags | O_NONBLOCK;通过按位或运算在保留原有标志的基础上叠加非阻塞标志O_NONBLOCK。fcntl(fd[0], F_SETFL, flags);将修改后的标志写回给fd[0]使其生效。阻塞vs非阻塞管道状态默认阻塞模式设置非阻塞后O_NONBLOCK管道中有数据正常读取数据并返回读取字节数正常读取数据并返回读取字节数管道为空且写端还开着进程卡死/挂起一直等到有数据写入为止立即返回-1同时设置全局变量errno EAGAIN或EWOULDBLOCK管道为空且写端全关闭返回0代表读取到 EOF 文件末尾返回0代表读取到 EOF 文件末尾二.有名管道fifo1 和匿名管道区别文件系统表现:FIFO作为特殊文件存在文件系统中匿名管道无文件系统表现**内容存储机制:FIFO内容实际存储在内核内存缓冲区匿名管道同样使用内核缓冲区**生命周期:FIFO文件在进程退出后仍保留匿名管道随进程结束而销毁**命名与访问:FIFO通过路径名访问匿名管道通过继承的文件描述符访问2 有名管道创建命令函数阻塞只读阻塞一个进程以只读方式打开管道时会阻塞直到另一个进程以只写方式打开该管道只写阻塞一个进程以只写方式打开管道时会阻塞直到另一个进程以只读方式打开该管道有名管道实现跨进程通信mkfifo.c#include sys/types.h #include sys/stat.h #include stdio.h #include unistd.h #include stdlib.h #include fcntl.h #includestring.h int main() { //判断文件是否存在 int ret access(test, F_OK); //不存在就创建一个 if (ret -1) { printf(管道不存在创建一个); ret mkfifo(test, 0664); if (ret -1) { perror(mkfifo); exit(0); } } return 0; }write.c#include sys/types.h #include sys/stat.h #include stdio.h #include unistd.h #include stdlib.h #include fcntl.h #includestring.h int main() { //判断文件是否存在 int ret access(test, F_OK); //不存在就创建一个 if (ret -1) { printf(管道不存在创建一个); ret mkfifo(test, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //以只写的方式打开管道 int fd open(test, O_WRONLY);//只有读端开启才会写入否则阻塞 if (fd -1) { perror(open); exit(0); } //写数据 for (int i 0; i 100; i) { char buf[1024]; sprintf(buf, hello %d\n, i); write(fd, buf, sizeof(buf)); printf(write %s\n, buf); sleep(1); } close(fd); return 0; }read.c#include sys/types.h #include sys/stat.h #include stdio.h #include unistd.h #include stdlib.h #include fcntl.h #includestring.h int main() { //打开管道文件 int fd open(test, O_RDONLY); if (fd -1) { perror(open); exit(0); } while (1) { char buf[1024] {0}; int lenread(fd, buf, sizeof(buf)); if (len 0) { printf(写端断开连接了...\n); break; } printf(read %s\n, buf); } close(fd); return 0; }有名管道互传信息一次只能发送一条a发一条b收一条b发一条a收一条//chatA #include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include stdlib.h #include unistd.h #include fcntl.h #include string.h int main() { //判断管道fifo1存不存在 int ret access(fifo1, F_OK); if (ret -1) { printf(无管道1创建一个\n); ret mkfifo(fifo1, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //判断管道fifo2存不存在 ret access(fifo2, F_OK); if (ret -1) { printf(无管道2创建一个\n); ret mkfifo(fifo2, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //打开两个管道 int fdw open(fifo1, O_WRONLY); int fdr open(fifo2, O_RDONLY); char buf[1024]; //开始循环读写 while (1) { //写管道 memset(buf, 0, sizeof(buf)); printf(chatA 输入\n); fgets(buf, sizeof(buf), stdin); retwrite(fdw, buf, sizeof(buf)); if (ret -1) { perror(write); exit(0); } //读管道 memset(buf, 0, sizeof(buf)); retread(fdr, buf, sizeof(buf)); printf(收到B%s, buf); if (ret -1) { perror(read); exit(0); } } return 0; }//chatB #include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include stdlib.h #include unistd.h #include fcntl.h #include string.h int main() { //判断管道fifo1存不存在 int ret access(fifo1, F_OK); if (ret -1) { printf(无管道1创建一个\n); ret mkfifo(fifo1, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //判断管道fifo2存不存在 ret access(fifo2, F_OK); if (ret -1) { printf(无管道2创建一个\n); ret mkfifo(fifo2, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //打开两个管道 int fdr open(fifo1, O_RDONLY); int fdw open(fifo2, O_WRONLY); char buf[1024]; //开始循环读写 while (1) { //读管道 memset(buf, 0, sizeof(buf)); ret read(fdr, buf, sizeof(buf)); printf(收到A%s, buf); if (ret -1) { perror(read); exit(0); } //写管道 memset(buf, 0, sizeof(buf)); printf(chatB 输入\n); fgets(buf, sizeof(buf), stdin); ret write(fdw, buf, sizeof(buf)); if (ret -1) { perror(write); exit(0); } } return 0; }但是这个只能解决一条一条发如果同时一个进程发送两条消息就会堵塞另一个进程无显示当Aw后Br然后B就进入代码W部分AB都是W阻塞有名管道互传信息改良可以一次发送多条改良将写管道和读管道分为两个进程父子进程每个进程只负责读/写//chata #include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include stdlib.h #include unistd.h #include fcntl.h #include string.h int main() { //判断管道fifo1存不存在 int ret access(fifo1, F_OK); if (ret -1) { printf(no fifo1set one\n); ret mkfifo(fifo1, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //判断管道fifo2存不存在 ret access(fifo2, F_OK); if (ret -1) { printf(no fifo2set one\n); ret mkfifo(fifo2, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //克隆子进程 pid_t pid fork(); char buf[1024]; //父进程写 if (pid 0) { int fdw open(fifo1, O_WRONLY); while (1) { //写管道 memset(buf, 0, sizeof(buf)); printf(chatA input\n); fgets(buf, sizeof(buf), stdin); ret write(fdw, buf, sizeof(buf)); if (ret -1) { perror(write); exit(0); } } } //子进程读 else if (pid 0) { int fdr open(fifo2, O_RDONLY); while (1) { //读管道 memset(buf, 0, sizeof(buf)); ret read(fdr, buf, sizeof(buf)); printf(recieveB%s, buf); if (ret0) { perror(read); exit(0); } } } return 0; }//chatb #include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include stdlib.h #include unistd.h #include fcntl.h #include string.h int main() { //判断管道fifo1存不存在 int ret access(fifo1, F_OK); if (ret -1) { printf(no fifo1set one\n); ret mkfifo(fifo1, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //判断管道fifo2存不存在 ret access(fifo2, F_OK); if (ret -1) { printf(no fifo2set one\n); ret mkfifo(fifo2, 0664); if (ret -1) { perror(mkfifo); exit(0); } } //克隆子进程 pid_t pid fork(); char buf[1024]; //父进程读 if (pid 0) { int fdr open(fifo1, O_RDONLY); while (1) { //读管道 memset(buf, 0, sizeof(buf)); ret read(fdr, buf, sizeof(buf)); printf(recieveA%s, buf); if (ret 0) { perror(read); exit(0); } } } else if (pid 0) { int fdw open(fifo2, O_WRONLY); while (1) { //写管道 memset(buf, 0, sizeof(buf)); printf(chatB input\n); fgets(buf, sizeof(buf), stdin); ret write(fdw, buf, sizeof(buf)); if (ret -1) { perror(write); exit(0); } } } return 0; }
返回列表