目录
为什么需要进程间通信
进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。
但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信
进程间通信的目的
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
- 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
管道通信方式
什么是管道?
- 管道是Unix中最古⽼的进程间通信的形式。
- 我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个“管道”
通常通过符号“|"来使用管道.管道实际上是内核申请的一段内存.
匿名管道
匿名管道只能用于具有亲缘关系的进程之间的通信.例如父子进程和兄弟进程.
它是一种半双工的通信方式,具有固定的读端和写端.
管道可以说是特殊的文件,也可以使用read( )和write( )函数来进行读写,但他不属于文件系统,只能存在于内核的内存空间中.
管道的创建
pipe函数用于创建管道
包含头文件 | #include<unistd.h> |
函数原型 | Int pipe( int fd[2]) ) |
函数传入值 | fd[2]:一个整形数组只有两个元素表示管道的两个文件描述符,之后就可以直接操作这两个文件描述符. |
函数返回值 | 成功返回0,失败返回-1. |
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1].其中fds[0]固定用于读管道,fds[1]固定用于写管道.
管道关闭只需将这两个文件描述符关闭即可,可使用普通的close( )逐个关闭文件描述符.
如图所示,用pipe()函数创建的管道两端处于同一个进程中,管道主要用于不同进程通信,因此实际意义不大.
实际中,通常是创建一个管道,再通过fork( )函数创建一个子进程,该子进程会继承父进程所创建的管道,此时父子进程分别拥有自己的读写管道.
为了实现父子进程之间的读写,只需要把无关的读端或写端的文件描述符关闭即可.此时,父子进程之间就建立了一条”子进程读取父进程写入”的管道.
父进程调用pipe开辟管道,得到两个文件描述符指向管道两端.
父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道.
父进程关闭管道读端,子进程关闭管道写端.父进程可以网管道里写,子进程可以从管道里读,管道是用环队列实现的,数据从写端流入从读端流出.这就实现了进程间通信.
管道的读写规则
如果所指向的管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样.
如果有指向管道写端的文件描述符没关闭,而持有写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了 才读取数据并返回.
如果所指向管道写端的文件描述符都关闭了,这时有进程向管道的写端write,那么该进程会受到信号SIGPIPE,通常会导致进程异常终止.
如果指向管道读端的文件描述符没有关闭,而持有管道读端的进程也没有从管道中读数据,这时进程向管道写端写数据,那么管道被写满时再次write会阻塞,直到管道中有空位置才写入数据并返回.
当要写的数据不大于pipe_buf,要保证写入的原子性
当要写的数据大于pipe_buf,linux不会保护写入的原子性
管道的特点
①管道是半双工的,也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西,那么进程2 就只能读取文件的内容。
②只能用于具有血缘关系的进程间通信,通常用于父子进程建通信
③管道是基于字节流来通信的
④依赖于文件系统,它的生命周期随进程的结束结束,多个进程打开,只有所有进程结束才释放
⑤其本身自带同步互斥效果
管道的实现
从标准输入读取数据,写入管道,再从管道读数据,写入标准输出
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd[2]);
if (ret < 0)
{
perror("pipe");
return 1;
}
while (1)
{
//从标准输入读数据
char buf[1024] = { 0 };
ssize_t read_size = read(0, buf, sizeof(buf)-1);
if (read_size < 0)
{
perror("read");
return 1;
}
if (read_size == 0)//读到EOF标志结束
{
printf("read done\n");
return 0;
}
buf[read_size] = '\0';
//把数据写到管道中
write(fd[1], buf, strlen(buf));
//从管道中读数据
char buf_output[1024] = { 0 };
read_size=read(fd[0], buf_output, sizeof(buf_output)-1);
if (read_size < 0)
{
perror("read");
return 1;
}
if (read_size == 0)
{
//如果管道的所有写端关闭,那么从读端读数据将会read返回0
printf("pipe close!");
return 0;
}
buf_output[read_size] = '\0';
//把数据写到标准输出中
write(1, buf_output, strlen(buf_output));
}
return 0;
}
命名管道(FIFO)
以FIFO文件的形式存储于文件系统中,能够实现任何两个进程之间通信。
命名管道是一种特殊类型的文件.
创建一个命名管道
| Mkfifo函数
| |
包含头文件 | #include<sys/types.h> #include<sys/types.h> | |
函数原型 | Int mkfifo(const char* filename,mode_t mode ) | |
函数传入值 | Filename:要创建的管道 | |
| Mode | O_RDONLY:读管道 |
|
| O_WRONLY:写管道 |
|
| O_RDWR:读写管道 |
|
| O_NONBLOCK:非阻塞 |
|
| O_CREAT:如果该文件不存在,就创建一个新文件,并用第三个参数为其设置权限 |
|
| O_EXCL如果使用O_CREAT时文件存在,那么可返回错误消息,这一参数可测试文件是否存在 |
函数返回值 | 成功:0 失败:1 |
匿名管道和命名管道的区别
命名管道创建完成后就可以使用,其使用方法与管道一样
命名管道使用之前需要使用open()打开。命名管道是设备文件,它是存储在硬盘上的,而管道是存在内存中的特殊文件。
命名管道读写规则
命名管道调用open()打开有可能会阻塞,但是如果以读写方式(O_RDWR)打开则一定不会阻塞;
以只读(O_RDONLY)方式打开时,调用open()的函数会被阻塞直到有数据可读;
如果以只写方式(O_WRONLY)打开时同样也会被阻塞,知道有以读方式打开该管道。
同类阅读推荐:https://blog.csdn.net/skyroben/article/details/71513385