一、IPC简介
Linux 环境下,进程地址控件相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程 1 把数据从用户空间拷贝到内核缓冲区,进程 2 在从内核缓冲区把数据读走,内核提供的这种机制称为 进程件通信(IPC)。
在进程空间完成数据的传送需要借助操作系统提供的特殊方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信的方式有:
(1)、管道(使用最简单)
(2)、信号(开销最小)
(3)、共享映射区(无血缘关系)
(4)、本地套接字 (最稳定)
二、管道(pipe)
1、管道概念:
管道是一种最基本的 IPC 机制,作用于血缘关系的进程之间,完成数据传递。调用 pipe 系统函数即可创建一个管道。有如以下特质:
(1)、本质上是一个伪文件 (实为内核缓冲区)
(2)、有两个文件描述符引用,一个表示读端,一个表示写端。
(3)、规定数据从管道的写端流入管道,从读端流出
2、管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
3、管道的局限性:
(1)、数据自己读不能写。
(2)、数据一旦被读走,便不在管道中存在,不可反复读取 。
(3)、由于管道采用半双双工通信方式。因此,数据只能在一个方向上流动。
(4)、只能在共有祖先的进程间使用管道。
常见的通信方式有:单工通信,半双工通信,全双工通信。
4、使用管道在父子进程间进行通信(数据传输):
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
int main(void)
{
int fd[2];
pid_t pid;
int ret = pipe(fd);
if( ret == -1 )
{
perror("pipe error");
exit(1);
}
pid = fork();
if( pid == 0 ) // 子进程读数据
{
// sleep(3);
close(fd[1]); // 关闭写管道的写文件描述符
char buf[1024]="";
ret = read(fd[0],buf,sizeof(buf));
if( ret == 0 )
{
printf("管道中的数据已经读完\n");
}
// 把缓存 buf 中的数据显示到屏幕上
write(STDOUT_FILENO,buf,ret);
}
else // 父进程 写数据
{
close(fd[0]); // 关闭管道的读文件描述符
char buf[1024]="asdada";
printf("Please input data:\n");
scanf("%s",buf);
buf[strlen(buf)]=‘\n‘;
// 把从标准输入中读取的数据写入管道中
write(fd[1],buf,strlen(buf));
}
wait(NULL);
}
三、有名管道(FIFO)
无名管道应用有一个重大的限制是它没有名字,因此,只能用于具有亲缘关系的父进程(父子进程)通信。为此 Linux 引入了 有名管道(FIFO)。FIFO 不同与无名管道之处自在于它提供了一个路径名与之关联,以管道类型的文件形式存放于文件系统中,这样,即使通信双方不存在亲缘关系,也可以用有名管道文件进行通信。
有名管道通过 mkfifo 来创建,其函数原型如下:
-
-
int mkfifo(const char *pathname, mode_t mode);
该函数的第一个参数是普通的路径名,也就是创建后的 FIFO 的名字,第二个参数类似有系统调用 open 中的 mode 参数。如果调用 mkfifo 时的第一个参数是一个已经存在的路径名,会返回一个 EXIST 错误,所以典型的调用代码会首先检查检查返回该错误,如果确实返回该错误,那么只要打开FIFO的函数就可以了。一般文件的 I/O 函数都可用于 FIFO,如 close 、read、write等。
打开 FIFO 文件时需要注意一个问题,程序不能以 O_RDWR 模式打开 FIFO,因为管道是半双工通信的,在同一时段之内数据流向是单向的,如果在程序间需要双向数据传递的话,可以同时使用一对 FIFO管道,一个方向配一个。ca
对于 FIFO 文件而言,读写是阻塞的,也就是说如果打卡了一个文件 空白的 FIFO 文件并进行 read 操作,只有在文件上有数据的时候(另一个程序才能执行 write)才能执行,而 没有数据流入的话,进程会阻塞在 read 上。执行 write 操作也是一样的。FIFO 文件内不存放任何数据,它只是一个用于传输的媒介。
以下两个程序,一个是 mkfifo_w.c,它会创建FIFO,并尽快的写入数据。另一个是 mkfifo_r.c ,它是从 FIFO 文件中读取数据。
/*
mkfifo_w.c 这个程序主要是从输入中写数据到往有名管道中
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(void)
{
int ret ;
char *fifoname="myfifo";
int fd;
int open_mode = O_WRONLY;
char buf[1024]="";
umask(0);
// 创建有名管道
ret = mkfifo(fifoname,0777);
if( ret == -1 )
{
perror("mkfifo error");
exit(1);
}
printf("mkfifo creat success : ret = %d\n",ret);
// 打开有名管道,并且只能只读或者只写的方式打开管道,因为有名管道是半双工通信方式
// 所以只能以只读方式打开或者以只写方式打开
fd = open(fifoname,open_mode);
if( fd == -1 )
{
perror("open error");
exit(1);
}
printf("open %s success\n",fifoname);
// 往管道中写数据
printf("Please input data:\n");
// 网缓冲中写数据
scanf("%s",buf);
write(fifoname,buf,strlen(buf));
printf("buf = [%s]\n",buf);
printf(" processs %d finish\n",getpid());
close(fd);
return 0;
}
————————————————
/*
mkfifo_r.c 这个程序主要是从有名管道中读取数据并且打印到屏幕上
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(void)
{
int ret;
char fifoname="myfifo";
int fd;
int open_mode = O_RDONLY;
char buf[1024]="";
// 打开有名管道,并且只能只读或者只写的方式打开管道,因为有名管道是半双工通信方式
// 所以只能以只读方式打开或者以只写方式打开
fd = open(fifoname,open_mode);
if( fd == -1 )
{
perror("open error");
exit(1);
}
// 网缓冲区中读取数据,然后打印到屏幕上
while( (read(fd,buf,sizeof(buf) ) > 0 ))
{
printf("Read data is [ %s ]\n",buf);
}
printf(" processs %d finish\n",getpid());
close(fd);
return 0;
}
————————————————
两个程序使用的都是阻塞模式的FIFO文件,先启动 mkfifo_w.c,,它将阻塞并等待有进程打开这个 FIFO文件,当 mkfifo_r.c程序打开的时候,写进程解除阻塞状态,并开始向管道中写,同时,读进程也从管道中读取。
共享内存:https://blog.csdn.net/t_x_l_/article/details/72903253
信号量:https://blog.csdn.net/t_x_l_/article/details/72876995