前言:
前面我们已经对进程已经有了一个简单的了解与认识,那么进程间的通信是什么样的呢,什么是父子进程,什么是兄弟进程,没有血缘关系间的进程是如何实现进程通信的,下面让我们一起学习一下什么是进程间的通信吧。
目录
一、进程间通信常用方式
IPC方式:
二、管道
1.概念:
*2.pipe函数:
*3.管道的读写行为:
4.父子间进程 :
5.兄弟间进程通信:
6.多个读写端操作管道
7.管道缓冲区大小:
.8.管道的优劣
三、FIFO:
1.命名管道fifo的创建和原理:
2.非血缘关系进程间通信avi:
一、进程间通信常用方式
IPC方式:
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:
1.管道(使用最简单)
2.FIFO(无血缘关系,单次读取)
3.共享映射区(无血缘关系,反复读取)
4.信号(开销最小)
5.本地套接字(最稳定)
二、管道
1.概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
(1).其本质是一个伪文件(实为内核缓冲区)
(2).由两个文件描述符引用,一个表示读端,一个表示写端,只能一次读取。
(3).规定数据从管道的写端流入管道,从读端流出,单向流动。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
1)数据不能进程自己写,自己读。·
2)管道中数据不可反复读取。一旦读走,管道中不再存在。
3)采用半双工通信方式,数据只能在单方向上流动。
4)只能在有公共祖先的进程间使用管道
常用的通信方式: 单工通信、半双工通信、全双工通信
创建管道文件:
(不占用磁盘空间)
*2.pipe函数:
pipe函数:用于有血缘关系的进程间通信
函数功能:创建,并打开管道。
int pipe(int fd[2]);
参数:
fd[0]: 读端。
fd[1]: 写端。
返回值:
成功: 0
失败: -1 errno
管道通信:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int ret,re;int fd[2];pid_t pid;char *str = "hello pipe\n";char buf[1024];ret = pipe(fd); //父进程先创建一个管道,持有管道的读端和写端if(ret == -1)sys_err("pipe error");pid = fork(); //子进程同样持有管道的读和写端if(pid > 0){ //父进程close(fd[0]); //关闭读段write(fd[1],str,strlen(str));//写入数据sleep(1);close(fd[1]); //关闭写段}else if(pid == 0){ //子进程close(fd[1]); //关闭写段re = read(fd[0],buf,sizeof(buf)); //读取数据write(STDOUT_FILENO,buf,re); //写到屏幕上close(fd[0]); //关闭读段}return 0;}
*3.管道的读写行为:
读管道:
1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据:
1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE导致的)
2. 有读端:
1) 管道已满, 阻塞等待(少见)
2) 管道未满, 返回写出的字节个数。
1)读管道,管道无数据(无写端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int ret,re;int fd[2];pid_t pid;char *str = "hello pipe\n";char buf[1024];ret = pipe(fd); //父进程先创建一个管道,持有管道的读端和写端if(ret == -1)sys_err("pipe error");pid = fork(); //子进程同样持有管道的读和写端if(pid > 0){ //父进程close(fd[0]); //关闭读段// write(fd[1],str,strlen(str));//写入数据close(fd[1]); //关闭写段}else if(pid == 0){ //子进程close(fd[1]); //关闭写段re = read(fd[0],buf,sizeof(buf)); //读取数据printf("child read ret =%d\n",ret);write(STDOUT_FILENO,buf,re); //写到屏幕上close(fd[0]); //关闭读段}return 0;}
read返回0
*4.父子间进程通信 :
使用管道实现父子进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc
ls | wc -l命令:
实现流程:
(1)父进程创建管道 pipe()
(2)父进程创建子进程 fork()
(3)设置父进程执行ls命令,子进程执行wc命令 execlp()
(4)设置父子进程通过管道的单项流动(设置指向标准输出的指向管道) dup2()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();****************/int fd[2]; int ret;pid_t pid;//父进程创建管道ret = pipe(fd); //父进程先创建一个管道,持有管道的读端和写端if(ret == -1){sys_err("pipe error");}//父进程创建子进程 pid = fork(); //子进程同样持有管道的读和写端if(pid == -1){sys_err("fork error");}else if(pid > 0){ //父进程 读,关闭写端close(fd[1]); //关闭写,设置单项流动dup2(fd[0],STDIN_FILENO); //设置读管道信息,重定向stdin 到管道读端execlp("wc","wc","-l",NULL); //设置父进程wc命令sys_err("execlp wc error");}else if(pid == 0){close(fd[0]); //关闭读,设置单项流动dup2(fd[1],STDOUT_FILENO); //设置写操作指向管道,重定向stdout 到管道写端execlp("ls","ls",NULL); //设置子进程执行ls命令 sys_err("execlp ls error");}return 0;
}
5.兄弟间进程通信:
使用管道实现兄弟进程间通信,完成:ls | wc -l。假定父进程实现ls,子进程实现wc
实现流程:
(1)父进程创建管道 pipe()
(2)父进程创建俩个(兄弟)子进程 fork()
(3)设置兄进程执行ls命令,第进程执行wc命令 execlp()
(4)设置兄弟进程通过管道的单项流动(设置指向标准输出的指向管道) dup2()
(5)回收父进程残余文件 wait()
刚创建出的兄弟进程:
设置兄弟进程通过管道的单项流动后
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();wait();****************/int fd[2]; int ret;int i;pid_t pid;//父进程创建管道ret = pipe(fd);if(ret == -1){sys_err("pipe error");}for(i = 0;i < 2;i++){ //表达式2 出口,仅限父进程使用pid = fork();if(pid == -1){sys_err("fork error");}if(pid == 0) //子进程出口break;}if(i == 2){ //父进程 不参与管道使用//不需要父进程所以需要关闭他管道的读写端close(fd[0]); close(fd[1]);//回收子进程wait(NULL);wait(NULL);}else if(i == 0){ //兄进程close(fd[0]);dup2(fd[1],STDOUT_FILENO); //重定向stdoutexeclp("ls","ls",NULL); //兄进程执行ls命令sys_err("ececlp ls error");}else if(i == 1){ //弟进程close(fd[1]);dup2(fd[0],STDIN_FILENO); //重定向stdinexeclp("wc","wc","-l",NULL); //弟进程执行wc命令sys_err("ececlp wc error");}return 0;
}
6.多个读写端操作管道
实现一个pipe有一个写端,多个读端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>void sys_err(const char *str)
{perror(str);exit(1);
}int main(int argc,char *argv[])
{/***************dup2();fork();pipe();execlp();****************/int fd[2],i,n; int ret;char buf[1024];pid_t pid;//父进程创建管道ret = pipe(fd);if(ret == -1){sys_err("pipe error");exit(1);}for(i = 0;i < 2;i++){pid = fork();if(pid == -1){sys_err("fork error");exit(1);}if(pid == 0)break;}if(i == 2){ //父进程close(fd[1]); //父进程关闭写端,留读端读取数据sleep(1);n = read(fd[0],buf,1024); //从管道中读取数据write(STDOUT_FILENO,buf,n); for(i == 0;i < 2;i++) //两个儿子wait两次wait(NULL);}else if(i == 0){ //兄进程close(fd[0]);write(fd[1],"1.hello\n",strlen("1.hello\n"));}else if(i == 1){ //弟进程close(fd[0]);write(fd[1],"2.world\n",strlen("2.world\n"));}return 0;
}
7.管道缓冲区大小:
可以使用 ulimIt -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:
pipe size ......(512 bytes,-p) 8
也可以使用fpathconf函数,借助参数―选项来查看。使用该宏应引入头文件<unistd.h>
long fpathconf(int fd, int name);成功:返回管道的大小―失败:-1,设置errno
.8.管道的优劣
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:
1.只能单向通信,双向通信需建立两个管道。
2.只能用父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决)
三、FIFO:
fifo管道:可以用于无血缘关系的进程间通信。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_RDONLY
写端,open fifo O_WRONLY
1.命名管道fifo的创建和原理:
使用命令:myfifo myfifo
使用myfifo创建
#include<stdio.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>void sys_err(const char *str){perror(str);exit(1);
}int main(int argc,char *str)
{int ret = mkfifo("mytestfifo",0664);if(ret == -1)sys_err("mkfifo error");return 0;}
2.非血缘关系进程间通信avi:
向管道写数据:fifo_w.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>void sys_err(char *str)
{perror(str);exit(-1);
}int main(int argc, char *argv[])
{int fd, i;char buf[4096];if (argc < 2) {printf("Enter like this: ./a.out fifoname\n");return -1;}fd = open(argv[1], O_WRONLY); //打开管道文件if (fd < 0) sys_err("open");i = 0;while (1) {sprintf(buf, "hello itcast %d\n", i++);write(fd, buf, strlen(buf)); // 向管道写数据sleep(1);}close(fd);return 0;
}
向管道读数据:fifo_r.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>void sys_err(char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int fd, len;char buf[4096];if (argc < 2) {printf("./a.out fifoname\n");return -1;}fd = open(argv[1], O_RDONLY); // 打开管道文件if (fd < 0) sys_err("open");while (1) {len = read(fd, buf, sizeof(buf)); // 从管道的读端获取数据write(STDOUT_FILENO, buf, len);sleep(3); //多個读端时应增加睡眠秒数,放大效果.}close(fd);return 0;
}
一方写,一方读:
一个写,多个读:管道特性,不能反复读取