为什么需要进程间通信?
由于进程都拥有独立的虚拟地址空间,从而导致了进程之间的独立性,通过进程间通信可以使各个进程之间很好的交互或进程控制。
网络是目前最大的进程间通信。
匿名管道
本质上就是在内核中开辟的一块空间,也被称为缓冲区,但是这块空间是没有标识符的。
匿名管道的创建
int pipe(int fd[2])
fd[2]
是文件描述符数组,fd[0]
代表从管道中读取数据,fd[1]
代码往管道中写入数据。成功返回0,失败返回-1。
即:
数据的流向是从fd[1]->管道->fd[0],且数据只能从中读一次,数据就流走了。
匿名管道的几个特性
- 管道是单双工通信。数据流向只能是从写端流到读端。
- 管道提供流式服务,好处就是可以决定读端每次读多少个字节。
- 管道的生命周期跟随进程
- 匿名管道只支持有亲缘关系的进程进行进程间通信。即先创建管道,再fork子进程。
获取一个文件的权限:
int fcntl(int fd, int cmd, … /* arg */ );
参数解释:
- fd:文件描述符
- 获取权限:int flag = fcntl(fd,F_GETFL),F_GETEL是宏
- 再设置权限:fcntl(fd,F_SETFL,flag | 权限),权限为八进制数字
- 当是F_GETFL的时候,返回的是文件描述符的权限信息
管道到底能容纳多大的数据呢?
输出结果:
这里涉及到一个点:原子性。
进程在操作匿名管道的时候,如果写入的字节数量小于4K,则保证当前写入操作的原子性。
上述程序会在write程序处阻塞下来,将文件描述符的属性设置为为阻塞态:O_NONBLOCK
在这块涉及到了两种情况:
1.若是写端文件描述符设置非阻塞状态
读端文件描述符都关闭 | 读端文件文件描述符不关、也不读 | |
---|---|---|
现象 | 管道破裂,会导致当前的进程退出 | 写端一直写,直到将管道写满 |
父进程调用write | 父进程会退出掉,子进程被1号进程领养 | 再继续写,会返回-1,并报错 |
子进程调用write | 子进程会因管道破裂成为僵尸进程 | 和父进程一样 |
2.若是读端文件描述符设置非阻塞状态
写端文件描述符都关闭 | 写端文件文件描述符不关、也不写 | |
---|---|---|
现象 | 读端一直读,直到将管道中的内容读完 | read 返回-1 ,表示资源不可用 |
父进程调用read | 再调用read,会返回0,并报错,没有读到任何内容 | 再继续写,会返回-1,表示资源不可用 |
子进程调用read | 和父进程一样 | 和父进程一样 |
所以在调用read函数的时候,要循环读取,直到有读到内容。
命名管道
命名管道也是在内核中开辟的一段缓冲区,这段缓冲区是有标识符的。则不同的进程,不需要亲缘关系,只需要通过标识符就能找到缓冲区,进行进程间通信。
命名管道的创建
可使用命令行创建:mkfifo filename
可在程序中进行创建:int mkfifo(const char *filename, mode_t mode)
mode:为权限
成功返回0,失败返回-1
命令行创建:
p:为管道文件。
程序创建:
关注一下mkfifo的返回值。
在使用的时候
FIFO为阻塞态 | FIFO为非阻塞态 | |
---|---|---|
读打开FIFO | 阻塞到有进程为写而打开FIFO | 立即返回成功 |
写打开FIFO | 阻塞到有进程为读而打开FIFO | 立即返回失败 |
命名管道的特性
- 命名管道的生命周期也是跟随进程的。
- 命名管道具有标识符。
- 其余特性和匿名管道一样。
数据同样,只读一次,就会被读走。
测试程序:
结果为:读取成功。
在使用命名管道的时候,使用的是一定存在的或已经创建好了的。