linux系统下,进程空间地址相互独立,每个进程各自有不同的用户地址。任何一个进程的全局变量在另有一个进程中都看不到,所以进程和进程间不能互相访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1的数据从用户空间拷贝至内核缓冲区,进程2再从缓冲区把数据读取,内核提供的这种机制称为进程间通信(IPC)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。
主要方式有:
① 管道 (使用最简单)
② 信号 (开销最小)
③ 共享映射区 (无血缘关系)
④ 本地套接字 (最稳定)
本文以下内容主要针对的是管道进行展开,其它进程间通信方式将在以后的文章中讲解。
管道是一种最基本的 IPC 机制,作用于有血缘关系进程之间,完成数据传递。调用 pipe 系统函数(或者用mkfifo命令)即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道实为内核使用环形队列机制(FIFO),借助内核缓冲区(4k)实现。
1. 数据不能进程自己写,自己读。
2. 管道中数据不可反复读取。一旦读走,管道中不再存在。
3. 采用半双工通信方式,数据只能在单方向上流动。
4.只能在有公共祖先的进程间使用管道。
优点:
简单,相比信号,套接字实现进程间通信,简单很多。
缺点:
1. 只能单向通信,双向通信需建立两个管道。
2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用 fifo 有名管道解决。
函数原型:
int pipe(int pipefd[2]);
参数:
fd[0] : r 读端
fd[1] : w 写端
返回值:
成功:0;失败:-1,设置 errno
注:函数调用成功返回 r/w 两个文件描述符。无需 open,但需手动 close。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。实现父子进程间通信通常可以采用如下步骤:
int main (int argc,char *argv[])
{
int ret;
int fd[2];
char buf [1024];
pid_t pid;
ret =pipe(fd);
if(ret==-1)
{
sys_err("pipe error");
}
pid =fork();
if(pid>0)
{ //父进程写 父进程关闭管道读端
close(fd[0]);
write((fd[1]),"hello pipe",strlen("hello pipe"));
sleep(1);
close(fd[1]);
}
else if (pid ==0)
{
//子进程读 子进程关闭管道写端
close(fd[1]);
ret=read(fd[0],buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
close(fd[0]);
}
return 0;
}
管道中有数据,read 返回实际读到的字节数。
管道中无数据:
(1) 管道写端被全部关闭,read 返回 0 (好像读到 文件结尾
(2) 写端没有全部被关闭,read 阻塞等待(不久的 将来可能有数据递达,此时会让出 cpu)
管道读端全部被关闭, 进程异常终止(也可使用捕捉 SIGPIPE 信号,使进程不终止)
管道读端没有全部关闭:
(1) 管道已满,write 阻塞。
(2) 管道未满,write 将数据写入,并返回实际写入的字节数。
使用管道实现父子进程间通信,完成:ls | wc –l。假定父进程实现 ls,子进程实现 wc。ls 命令正常会将结果集写出到 stdout,但现在会写入管道的写端;wc –l 正常应该从 stdin 读取数据,但此时会从管道的读端读。
代码:
#include
#include
#include
#include
#include
using namespace std;
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main (int argc,char *argv[])
{
int ret;
int fd[2];
char buf [1024];
pid_t pid;
//创建管道
ret =pipe(fd);
//判断是否创建成功
if(ret==-1)
{
sys_err("pipe error");
}
//创建子进程
pid=fork();
if(pid==-1)//创建子进程失败
{
perror("fork error");
exit(1);
}
//实现从父进程向子进程通信,父进程写管道打开,读管道关闭; 子进程读管道打开,写管道关闭
else if(pid>0)//父进程
{
//父进程关闭读端
close(fd[0]);
//将标准输出文件描述符指向父进程管道写端
dup2(fd[1],STDOUT_FILENO);
//利用execlp函数执行指定程序命令
execlp("ls","ls",NULL) ;
}
else if (pid==0)//子进程
{
//子进程关闭写端
close(fd[1]);
//将标准输入文件描述符指向子进程管道读端
dup2(fd[0],STDIN_FILENO);
//利用execlp函数执行指定程序命令
execlp("wc","wc","-l",NULL);
}
return 0;
}
使用管道实现兄弟进程间通信。 兄:ls 弟: wc -l 父:等待回收子进程。
要求:使用“循环创建 N 个子进程”模型创建兄弟进程,使用循环因子 i 标示。注意管道读写行为。
代码:
#include
#include
#include
#include
#include
using namespace std;
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main (int argc,char *argv[])
{
int i,ret;
int fd[2];
char buf [1024];
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)//创建子进程失败
{
perror("fork error");
exit(1);
}
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);
//利用execlp函数执行指定程序命令
execlp("ls","ls",NULL) ;
}
else if (i==1)//弟进程
{
close(fd[1]);
//将标准输入文件描述符指向弟进程管道读端
dup2(fd[0],STDIN_FILENO);
//利用execlp函数执行指定程序命令
execlp("wc","wc","-l",NULL);
}
return 0;
}
运行结果:
可以使用 ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:
pipe size (512 bytes, -p) 8
也可以使用 fpathconf 函数,借助参数 选项来查看。使用该宏应引入头文件
<unistd.h>long fpathconf(int fd, int name);
成功:返回管道的大小
失败:-1,设置 errno
FIFO 常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过 FIFO,不相关的进程也能交换数据。
FIFO 是 Linux 基础文件类型中的一种。但FIFO 文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行 read/write,实际上是在读写内核通道,这样就实现了进程间通信。
创建:
命令行:mkfifo 管道名
库函数:int mkfifo(const char *pathname, mode_t mode); 成功:0; 失败:-1
https://blog.csdn.net/qq_33951180/article/details/68959819