每个进程都有自己的虚拟地址空间,访问的都是虚拟地址,因此进程之间具有独立性,无法直接通信!
进程间通信原理:操作系统为进程建提供一个公共的传输媒介,实现公共访问从而实现通信!
根据通信需求不同提供了不同方式:管道,共享内存,消息队列,信号量。
管道
a、特性:半双工通信(可以选择方向的单向通信)
b、本质:系统内核中的一块缓冲区(内核空间中开辟的一块内存)
c、通信原理:多个进程只要能访问同一块内核中的缓冲区(管道)就能实现通信
d、分类:匿名管道,命名管道
匿名管道:只能用于具有亲缘关系的进程间通信
命名管道:可以用于同一主机上任意进程间通信。需要传递具体的标识符,利用该标识符进行通信。
e、实质:管道数据的传输实质是字节流方式,有进有出,按顺序。
1、匿名管道
管道缓冲区没有标识符,无法被其他进程找到,因此只能通过子进程复制父进程的方式获取管道的管道的操作句柄,进行通信。
相关函数:
int pipe(int pipefd[2]);
参数:pipefd[0]:用于从管道读数据
pipefd[1]:用于向管道写数据
返回值: 成功返回 0;失败返回 -1
通过IO操作完成对管道的操作
关闭写端:close(pipefd[1])
关闭读端:close(pipefd[0])
读写特性:
a、若管道中没有数据,则read
会阻塞;若管道中数据满了,则write
会阻塞,即不会继续写入数据
b、若管道的所有读端被关闭,则继续write
就会触发异常 – 导致进程退出!!!!!!!!!!!
c、若管道的所有写端被关闭,read
读完所有数据后,则不会再阻塞,返回0
例程
管道符:|
如:ps -ef | grep pipe
ps -ef
:将所有进程信息写入标准输出,即将这些信息打印显示;
grep pipe
:不断循环从标准输入读取数据进行字符串匹配过滤,此处要匹配的字符串是 pipe
,无主动退出的时候,可添加程序使其退出。
上述程序是将 管道符前面的输出 作为 其后的输入 继续操作。或者说,将前边命令进程要输出到标准输出的结果,不再输出到标准输出,而是将其传输到后边的命令进程进行继续处理。
将上述程序利用 管道 进行实现:
问题分析:
因为代码中有两个功能要实现,所以在 shell 进程中需创建两个进程,一个进程运行 ps,一个进程运行 grep;
同时题目中要求将 ps 进程的数据,交给 grep 进程进行处理。此处只能通过进程间通信完成 ,为满足要求,遂决定利用 匿名管道 进行实现!
实现步骤:
1、创建管道;
2、创建两个进程,在各自进程中进行程序替换(标准输出重定向);
3、在程序替换之前,ps进程应该标准输出重定向到管道写入端,
grep进程应该标准输入重定向到管道读取端;
4、父进程应等待子进程退出,防止僵尸进程的出现。
实现代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
int pipefd[2];
int ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe error");
return -1;
}
//两个子进程
//检查是否是子进程
if(fork() == 0)
{
dup2(pipefd[1],1);//将标准输出的内容重定向或写入至管道写入端,将数据写入管道
//程序替换
execlp("ps","ps","-ef",NULL);
exit(-1);
}
if(fork() == 0)
{
//关闭写入端,当返回0时,说明没有写入数据了,如此当检索出所有结果后,会直接退出!!!!!!!!1
close(pipefd[1]);
//将标准输入内容重定向至管道读取端,即若要从标准输入文件读取数据时,会从管道
//的读取端读取数据,此时读取端的数据时从写入端读取而来的
dup2(pipefd[0],0); // 从管道读取数据!!!!!!!
execlp("grep","grep","pipe",NULL);
exit(-1);
}
//父进程等待时的读取与写入端都进行关闭
close(pipefd[0]);
close(pipefd[1]);
//防止出现僵尸进程
wait(NULL);
wait(NULL);
return 0;
}
运行结果:
上图中的结果显示,上面的实现程序可以实现从运行完 ps -ef
后所获得的进程信息中检索出与字符串 pipe
相关的内容!
2、命名管道
可以用于同一主机上任意进程间通信 。其中,管道缓冲区具有标识符 ,简单说,命名管道是一块缓冲区!!
创建管道文件:
a、shell
中建立管道文件
代码:
mkfifo filename
参数:filename`:管道文件名称
注:使用 mkfifo
创建的管道文件,其利用 ls -l
查看时,其权限前会显示 p
,如下图
b、程序中建立管道文件
代码:
int mkfifo(char *path,int mode) //实现对管道文件的读写操作
参数:mode :权限
path :目标路径
返回值: 成功返回 0;失败返回 -1
通过IO操作完成对管道的操作
本质:命名管道依然是内核中的一块缓冲区,但命名管道有名字和标识符。这个标识符就是一个可见于文件系统的管道类型文件,多个进程可以通过打开同一个管道文件访问同一块内核中的缓冲区实现通信!!
创建的命名管道文件只是一个缓冲区的名称,不管写入内容多大,该文件的大小不变,其相当于起了一个索引作用,因为其是通过缓冲区进行通信的,即是通过这个文件指向的缓冲区通信。且若要写入内容,必须有读取操作,否则写入操作也无法正常实现!
读写特性:
a、若以 只读方式 打开命名管道文件,则会阻塞,直到这个管道文件被以写的方式打开;
b、若以 只写方式 打开命名管道文件,则会阻塞,直到这个管道文件被以读的方式打开。
例程
(1)管道读取
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
int main()
{
umask(0);//掩码设为0
char *fifo_name = "./test.fifo";
int ret = mkfifo(fifo_name,0664);//创建命名管道
if(ret < 0 && errno != EEXIST)
{
//不是因为文件存在而报错
perror("mkfifo error");
return -1;
}
//open(文件名,打开方式,权限),不要利用open创建管道文件
//int fd = open(fifo_name, O_WRONLY);//以只写的方式打开也会阻塞
//int fd = open(fifo_name, O_RDWR);//以可读可写打开,不会阻塞
int fd = open(fifo_name, O_RDONLY);//以只读的方式打开会阻塞
if(fd < 0)
{
perror("open error");
return -1;
}
printf("read open fifo success!\n");
while(1)
{
char buf[1024] = {0};
int ret = read(fd,buf,1023);
if(ret < 0)
{
perror("read error");
return -1;
}
else if(ret == 0) //所有写端关闭
{
printf("all write close\n");
return -1;
}
printf("命名管道内容:%s\n",buf);
}
return 0;
}
(2)管道写入
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
int main()
{
umask(0);
char *fifo_name = "./test.fifo";
int ret = mkfifo(fifo_name,0664);
if(ret < 0 && errno != EEXIST)
{
perror("mkfifo error");
return -1;
}
int fd = open(fifo_name,O_WRONLY);
if(fd < 0)
{
perror("open error");
return -1;
}
printf("write open success!\n");
while(1)
{
char buf[1024] = {0};
scanf("%s",buf);
int ret = write(fd,buf,strlen(buf));
if(ret < 0)
{
perror("write error");
return -1;
}
}
close(fd);
return 0;
}
运行结果:
上图中,fread
与 fwrite
分别为 管道读取 与 管道写入 两个实现程序的执行文件。此处实现的是,一边输入内容,一边将输入的内容打印,上图中实现了目标功能!
同时,因为上面的程序是在程序中建立管道,并非是在shell
中建立管道,所以当程序运行后,其会自行建立对应管道,如图所示
最后,再附上一张总结图
以上为管道相关内容,内容较多,其它如共享内存、消息队列、信号量等内容,可见笔者其他博客!
(侵权删~)