进程间通信:IPC概念

IPC:Interprocess Communication,通过内核提供的缓冲区进行数据交换的机制。

IPC通信的方式:

通信种类:

  • 单工(广播)
  • 单双工(对讲机)
  • 全双工(电话)

一,管道PIPE

pipe通信是单双工的。

pipe通信,只能在有血缘关系的进程间通信。父子进程,兄弟进程,爷孙进程等。

#include <unistd.h>
int pipe(int pipefd[2]);
  • pipefd:【0】是读端,【1】是写端。
  • 返回值:成功返回0;失败返回-1。

例子:

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork(); if(pid == 0){
write(fds[1], "hello\n", 6);
char buf[10] = {0};
int ret = read(fds[0], buf, sizeof buf);
if(ret > 0){
printf("%s", buf);
}
}
if(pid > 0){
char buf[10] = {0};
int ret = read(fds[0], buf, sizeof buf);
if(ret > 0){
printf("%s", buf);
}
write(fds[1], "world\n", 6);
sleep(1);
}
}

例子1:子进程写,父进程读。

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork(); if(pid == 0){
write(fds[1], "hello\n", 6);
/*
char buf[10] = {0};
int ret = read(fds[0], buf, sizeof buf);
if(ret > 0){
printf("%s", buf);
}
*/
}
if(pid > 0){
char buf[10] = {0};
int ret = read(fds[0], buf, sizeof buf);
if(ret > 0){
printf("%s", buf);
}
//write(fds[1], "world\n", 6);
//sleep(1);
}
}

例子2:用管道实现【ps aux | grep bash】命令。

实现办法,用dup2函数把标准输出,重定向到写端;再把标准输入重定向到读端。

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork();
int stdoutfd = dup(STDOUT_FILENO);
if(pid == 0){
//close(fds[0]);//------------①
dup2(fds[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
}
if(pid > 0){
//close(fds[1]);//----------②
dup2(fds[0], STDIN_FILENO);
execlp("grep", "grep", "bash", NULL);
dup2(stdoutfd, STDOUT_FILENO);
}
}

运行结果:发现程序没有结束,阻塞住了,必须按ctol-c才能结束。

ys@ys:~/test$ ./pi2
ys 1551 0.0 0.2 29692 5548 pts/0 Ss 10:05 0:00 bash
ys 2316 0.0 0.2 29560 5328 pts/1 Ss+ 11:33 0:00 bash
ys 2486 0.0 0.0 21536 1060 pts/0 S+ 11:56 0:00 grep bash

用【ps aux】调查一下,发现,由于父进程【grep bash】没有结束还没有回收子进程,导致【ps】变成了僵尸进程。

ys        2437  0.0  0.0  21536  1088 pts/0    S+   11:50   0:00 grep bash
ys 2438 0.1 0.0 0 0 pts/0 Z+ 11:50 0:00 [ps] <defunct>
ys 2439 0.0 0.1 44472 3800 pts/1 R+ 11:50 0:00 ps aux

为什么父进程【grep bash】没有结束呢?确实在子进程里给父进程【ps aux】的输出结果了啊!

这是grep命令本身的缘故,在终端执行【grep bash】的话,就变成了阻塞状态,grep在等待标准输入,如果输入了【bash】grep就会给出结果,但是还是在继续等待标准输入,所以这就是父进程没有结束,阻塞在【grep bash】那里的原因。

解决办法:告诉【grep】,管道的写端不会再写入数据了后,grep就不会再继续等待,所以grep就会结束。grep的结束了,父进程也就结束了,所以僵尸进程也就自动消失了。

需要改代码的地方是②处,加上【close(fds[1]);】,就告诉了grep,已经没有写入了,所以grep就不会阻塞,父进程就能够结束掉。

注意:其实应该在子进程里也应该加上【close(fds[1]);】,才能达到写端全部关闭了,为什么没写也没错误呢,因为子进程先执行结束了,进程结束后,系统会自动把进程中打开的文件描述符全部关闭,所以没在子进程里写关闭写端的代码,也没出问题。

管道有如下的规则:

  • 读管道时:

    • 写端全部关闭:read函数返回0,相当于没有再能读取到的了。
    • 写端未全部关闭:
      • 管道里有数据:read函数能够读到数据。
      • 管道里没有数据:read 阻塞。(可以用fcnlt设置成非阻塞)
  • 写管道时:
    • 读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。
    • 读端未全部关闭:
      • 管道已满:write函数阻塞等待。
      • 管道未满:write函数正常写入。

例子1:写端全部关闭:read函数返回0。

在①和②两处必须都关闭写端,read函数才能返回0.

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork(); if(pid == 0){
char buf[10] = {0};
int ret = read(fds[0], buf, sizeof buf);
if(ret > 0){
printf("%s", buf);
}
close(fds[1]);//----①
sleep(1);
if(read(fds[0],buf, sizeof buf) == 0){
printf("all closed\n");
} }
if(pid > 0){
int ret = write(fds[1], "hello\n", 6);
close(fds[1]);//------②
wait(NULL);
}
}

例子2:读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。

在①和②两处必须都关闭读端,write函数会产生SIGPIPE信号。

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork(); if(pid == 0){
close(fds[0]);//------②
int ret = write(fds[1], "hello\n", 6);
}
if(pid > 0){
close(fds[0]);//----①
//close(fds[1]);
int status;
wait(&status);
if(WIFSIGNALED(status)){
printf("killed by %d\n", WTERMSIG(status));
}
}
}

执行结果:【killed by 13】。13是SIGPIPE

查看系统默认的管道缓冲区的大小:ulimit -a

pipe size            (512 bytes, -p) 8

查看系统默认的管道缓冲区的大小的函数:fpathconf

#include <unistd.h>
long fpathconf(int fd, int name);
  • fd:文件描述符
  • name:可以选择很多宏
    • _PC_PIPE_BUF:代表管道。

例子:

#include <unistd.h>
#include <stdio.h> int main(){
int fds[2];
pipe(fds);
long ret = fpathconf(fds[0], _PC_PIPE_BUF);
printf("size:%ld\n", ret);
}

执行结果:size:4096

上面的【例子:用管道实现【ps aux | grep bash】命令】有个问题,父进程直接调用了exec函数,导致无法在父进程中回收子进程的资源。下面的例子就去解决这个问题,方法是,不在父进程里调用exec函数,在2个兄弟子进程里分别调用exec函数,然后在父进程里回收资源。

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h> int main(){
int fds[2];
pipe(fds);
pid_t pid = fork();
if(pid == 0){
pid_t pid1 = fork();
if(pid1 == 0){
dup2(fds[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL); }
else if(pid1 > 0){
close(fds[1]);//----①
dup2(fds[0], STDIN_FILENO);
execlp("grep", "grep", "bash", NULL);
//dup2(stdoutfd, STDOUT_FILENO);
}
}
else if(pid > 0){
close(fds[1]);//----②
wait(NULL);
}
}

注意在①和②处的关闭代码。

到此为止,可以看出来管道的

  • 优点:使用起来简单。
  • 缺点:只能在有血缘关系的进程间使用。

二,FIFO通信

创建FIFO伪文件的命令:【mkfifo】

prw-r--r-- 1 ys ys     0 4月  29 15:59 myfifo

文件类型为P,大小为0。

也可以用函数:mkfifo创建

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
  • pathname:文件名
  • mode:文件权限
  • 返回值:0成功;-1失败

FIFO通信原理:内核对fifo文件开辟一个缓冲区,操作fifo伪文件,就相当于操作缓冲区,实现里进程间的通信。实际上就是文件读写。

FIFO例子:传进一个事先用mkfifo 创建好的FIFO文件。可以同时打开多个读端和写端。

  • 写端:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h> int main(int argc, char* argv[]){ printf("begin write\n");
    int fd = open(argv[1], O_WRONLY);
    printf("end write\n"); int num = 0;
    char buf[20] = {0};
    while(1){
    sprintf(buf, "num=%04d\n", num++);
    write(fd, buf, strlen(buf));
    sleep(1);
    } close(fd);
    }
  • 读端:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h> int main(int argc, char* argv[]){ printf("begin read\n");
    int fd = open(argv[1], O_RDONLY);
    printf("end read\n"); int num = 0;
    char buf[20] = {0};
    while(1){
    memset(buf, 0x00, sizeof buf);
    int ret = read(fd, buf, sizeof buf);
    if(ret > 0){
    printf("%s\n", buf);
    }
    else if(ret == 0){
    break;
    }
    sleep(1);
    } close(fd);
    }

例子里有两个注意点:

  • open的时候是阻塞的,只有当读端和写端都打开后,open函数才会返回。非FIFO文件的open函数不是阻塞的。

    FIFOs
    Opening the read or write end of a FIFO blocks until the other end is
    also opened (by another process or thread). See fifo(7) for further
    details.
  • 强制终止读端进程后,写端会自动终止。理由是读端已经关闭了,再往里写就会收到SIGFIFO信号,这个和管道的原理是一样的。

  • 非常重要的一点:从fifo里读出数据后,这个被读出来的数据在fifo里就消失了。后面讲的mmap进程间通信就不一样,读完了,再读还有,因为是映射到内存了。

A进程发送一个mp3文件,B进程接收这个mp3文件,并存储到磁盘上,代码如下:

发送端:先取得mp3文件的大小,把文件的大小先发给接收端,然后在把文件的内容发过去。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> int main(int argc, char* argv[]){
struct stat sbuf;
int ret = stat("02.mp3", &sbuf);
if(ret < 0){
perror("stat");
return -1;
}
//get file size
int sz = sbuf.st_size;
printf("size:%d\n", sz);
char buf[20] = {0};
sprintf(buf, "%d", sz);
//open fifo file
int fd = open(argv[1], O_RDWR);
//send file size
write(fd, buf, sizeof(buf)); //open src mp3 file
int src = open("02.mp3", O_RDONLY);
char srcBuf[1024] = {0};
//send file content to dec file
int sent = 0;
while((sent = read(src, srcBuf, sizeof(srcBuf))) > 0){
write(fd, srcBuf, sent);
memset(srcBuf, 0x00, sizeof(srcBuf));
}
close(fd);
close(src);
}

接收端:先从发送端取得要发过来的MP3文件的大小,然后根据这个大小,先创建一个空的文件,然后再向这个空的文件里写内容。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char* argv[]){
//open fifo file
int fd = open(argv[1], O_RDONLY);
//send file size
char buf[20] = {0};
//get file size
read(fd, buf, sizeof(buf));
int sz = atoi(buf);
printf("sz:%d\n", sz); int dsc = open("des.mp3", O_RDWR|O_CREAT|O_TRUNC, 0666);
int ret = ftruncate(dsc, sz);
if(ret < 0){
perror("ftruncate");
return -1;
} char srcBuf[1024] = {0};
//recv file content from src file
int sent = 0;
while((sent = read(fd, srcBuf, sizeof(srcBuf))) > 0){
write(dsc, srcBuf, sent);
memset(srcBuf, 0x00, sizeof(srcBuf));
} close(fd);
close(dsc);
}

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

linux 进程通信之 管道和FIFO的更多相关文章

  1. Linux进程通信----匿名管道

    Linux进程通信中最为简单的方式是匿名管道 匿名管道的创建需要用到pipe函数,pipe函数参数为一个数组表示的文件描述字.这个数组有两个文件描 述字,第一个是用于读数据的文件描述符第二个是用于写数 ...

  2. linux下的进程通信之管道与FIFO

    概念:管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条.管道的一端连接一个进程的输出.这个进程会向管道中放入信息.管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息. 优点:不需 ...

  3. linux进程通信之管道

    1.介绍: 1)同一主机: unix进程通信方式:无名管道,有名管道,信号 system v方式:信号量,消息队列,共享内存 2)网络通信:Socket,RPC 2.管道: 无名管道(PIPE):使用 ...

  4. Linux 进程通信之管道

    管道是单向的.先进先出的,它把一个进程的输出和还有一个进程的输入连接在一起.一个进程(写进程)在管道的尾部写入数据,还有一个进程(读进程)从管道的头部读出数据.数据被一个进程读出后,将被从管道中删除, ...

  5. Linux学习笔记(13)-进程通信|命名管道

    匿名管道只能在具有亲属关系的进程间通信,那么如果想要在不具有亲戚关系,想在陌生人之间通信,那又该怎么办呢? 别慌,Linux身为世界上*强大的操作系统,当然提供了这种机制,那便是命名管道-- 所谓命名 ...

  6. linux 进程通信 管道

    1. 管道概述及相关API应用 1.1 管道相关的关键概念 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管 ...

  7. Linux下进程通信之管道

    每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把 ...

  8. Linux 进程通信(有名管道)

    有名管道(FIFO) 有名管道是持久稳定的. 它们存在于文件系统中. FIFO比无名管道作用更大,因为他们能让无关联的进程之间交换数据. 管道文件一般用于交换数据. shell命令创建管道 一个she ...

  9. Linux进程通信之匿名管道

    进程间的通信方式 进程间的通信方式包括,管道.共享内存.信号.信号量.消息队列.套接字. 进程间通信的目的 进程间通信的主要目的是:数据传输.数据共享.事件通知.资源共享.进程控制等. 进程间通信之管 ...

随机推荐

  1. VCS双机原理

    VCS双机的配置目录:/etc/VRTSvcs/conf/config/main.cf VCS双机的监控脚本目录:/opt/VRTSvcs/bin/mdcc/ VCS双机日志目录:/var/VRTSv ...

  2. 12步轻松搞定Python装饰器

    译者:寒寻 译文:http://www.cnblogs.com/imshome/p/8327438.html 原文:https://dzone.com/articles/understanding-p ...

  3. 超简单的制作win7 U盘启动

    我感觉真的太简单,操作so简单 第一个下载这个工具,这是微软官方提供的,用这个工具可以把win7的iso文件刻录到u盘中,u盘就可以作为系统启动盘来使用了 Windows 7 USB DVD Down ...

  4. 《OpenCL异构并行编程实战》第十二至十四章

    ▶ 第十二章,在其他语言中使用 OpenCL ● JOCL(Java Building for OpenCL),PyOpenCL ● 一个 PyOpenCL 的例子代码,需要 pyopencl 包 i ...

  5. Vue项目中将table组件导出Excel表格以及打印页面内容

    体验更优排版请移步原文:http://blog.kwin.wang/programming/vue-table-export-excel-and-print.html 页面中显示的table表格,经常 ...

  6. 利用Nginx rewrite规则实现域名显性转发

    体验更优排版请移步原文:http://blog.kwin.wang/website/nginx-rewrite-realize-domain-forward.html 自己的blog域名最开始用的vb ...

  7. LeetCode之二叉树作题java

    100. Same Tree Total Accepted: 127501 Total Submissions: 294584 Difficulty: Easy Given two binary tr ...

  8. MVC4中压缩和合并js文件和样式文件

    1.在App_Start文件夹中BundleConfig.cs类中添加相应的文件 1.1bundles.Add(new ScriptBundle("~/bundles/adminJs&quo ...

  9. 若p是与10互质的质数,则p-1个9能被p整除

    [若p是与10互质的质数,则k(p-1)个9能被p整除] 因为(p,10)=1,所以(p,10^k)=1.根据费马定理,10^(k*(p-1))-1|p. 而10^k*(p-1)-1是一个位数为(p- ...

  10. C# \/date(1498820611133+0800)\/ 转DateTime

    开发中经常遇到日期转换问题,特别是做接口的时候,现在整理了下时间戳转为C#格式时间的方法: /// <summary> /// 时间戳转为C#格式时间 /// </summary&g ...