进程间通信: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. postman-1版本区别、选择

    postman基于乙醇在腾讯课堂的postman教程 postman特点: 1.便于开发:开发接口的时候需要快速的调用接口,以便调试 2.便于测试:测试的时候需要非常方便的调用接口,通过不同的参数去测 ...

  2. C++11新特性介绍 01

    阅读目录 1. 概述 2. long long 类型 3. 列表初始化 4. nullptr 空指针 5. constexpr变量 6. constexpr函数 7. using类型别名 8. aut ...

  3. centos7开发环境配置总结

    1.win10下SecureCRT SSH连接慢 2.CentOS 7下Samba服务器的安装与配置 3.

  4. python一些不错的东西

    1 cmd命令行写代码的加强版  ipython  直接用pip安装就可以   php install Ipython 2 不错的数据分析 机器语言的 Python(x,y)是一个基于python的科 ...

  5. QT error LNK2019: 无法解析的外部符号

    一个见到那的错误,困扰了好几天了,今天才解决,记录下. 使用QT Creator建立项目,添加一个QT设计界面widget,命名为TestWidget.有ui,头文件(.h),源码文件(.cpp).在 ...

  6. easyui制作进度条案例demo

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. python中字典的比较

    今天碰到一个字典比较的问题,就是比较两个字典的大小,其实这个用的不多,用处也没多少,但是还是记录一下. 字典的比较顺序如下: 1.先比较字典的元素的个数,那个多,就哪个大: 2.比较字典的键,在比较字 ...

  8. jeesite快速开发平台(六)----代码生成模块介绍及使用

    转自:https://blog.csdn.net/u011781521/article/details/79309861

  9. C# JSON 序列化

    1.JavaScriptSerializer System.Web.Extensions.dll System.Web.Script.Serialization命名空间 Serialize Deser ...

  10. ASP.NET web 应用程序项目

    ASP.NET web  应用程序项目 .ashx .ashx.cs aspx包括前台一些代码要处理,ashx可以看作是没有aspx页面中前台代码的后台.cs文件. 没有了前台代码,服务器负担少一点, ...