进程间通信
进程之间无法直接通信,因为每个进程都有自己的独立的虚拟地址空间,访问的都是自己的虚拟地址。如果将一号进程的虚拟地址传给二号进程,通过二号进程的页表来映射,并不能映射到正确的物理地址空间。所以两个进程的映射关系并不同,具有独立性,因此无法直接通信。
进程间通信的原理:操作系统为进程间提供一个公共的传输媒介,实现公共访问,进而实现进程间通信。
操作系统根据需求不同提供了不同的进程间通信的方式:
- 管道
- 共享内存
- 消息队列
- 信号量
管道
特性:半双工通信,就是可以选择方向的单向通信。
本质:系统内核中的一块儿缓冲区(内核空间中开辟的一块儿内存)。
通信原理:多个进程只要能够访问同一块儿内核中的缓冲区(管道)就能实现通信。
分类:
- 匿名管道:只能用于具有亲缘关系的进程间通信。
- 命名管道:可以用于同一主机上任意进程间通信。
匿名管道
管道缓冲区没有标识符,无法被其他的进程找到,因此只能通过子进程复制父进程的方式获得到管道的操作句柄,进行通信。
int pipe(int pipefd[2]);
- pipefd[0]:用于从管道里读取数据。
- pipefd[1]:用于将数据写入管道中。
- 返回值:成功返回0,失败返回-1。
通过IO操作完成对管道的操作。
代码操作:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main ()
{
int pipefd[2];
int ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork error");
exit(-1);
}
else if (pid == 0)
{
//子进程
char buf[1024] = {0};
int ret = read(pipefd[0], buf, 1023);
if (ret < 0)
{
perror("read error");
return -1;
}
prinff("buf:%s",buf);
}
else
{
//父进程
char* ptr = "hello world\n";
int ret = write(pipefd[1], ptr, strlen(ptr));
if (ret < 0)
{
perror("write error");
return -1;
}
porintf("写入成功\n");
}
return 0;
}
我们知道,父子进程先运行哪个并不一定,如果先运行了子进程,也就是先从管道读取了数据,但是此时父进程并没有向管道中写入数据,那我们能读取到数据吗?
管道的读写特性:
- 若管道中没有数据,read会阻塞; 若管道中数据写满了,write回阻塞。
- 若管道的所有读端被关闭,则继续write就会触发异常,导致进程退出。
- 若管道的所有写端被关闭,read读完所有数据后,则不在阻塞,返回0.
命名管道
管道缓冲区具有标识符,所以可以用于同一主机上任意进程间通信。
命名操作:mkfifo+文件名;创建一个管道文件。
命名管道的本质依然是内核中的一块儿缓冲区,但是命名管道具有名字,具有标识符,而这个标识符就是一个可见于文件系统的管道类型文件。多个进程可以打开同一个管道文件,访问同一块内核中的缓冲区,实现通信。
int mkfifo(const char *pathname, mode_t mode);
代码操作:
1.fifo_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcnlt.h>
#include <sys/stat.h>
int main ()
{
umask(0);
char* fifo_name = "./test.fifo";
int ret = mkfifo(fifo_name, 0664);
if (ret < 0)
{
perror("mkfifo error");
return -1;
}
int fd = open(fifo_name,O_RDONLY);
if (fd < 0)
{
perror("open error");
return -1;
}
printf("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 closed\n");
return -1;
}
printf("buf:%s",buf);
}
close(fd);
return 0;
}
2.fifo_write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcnlt.h>
#include <sys/stat.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;
}
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;
}
总结管道的特性:
1.半双工通信:可以选择方向的单向通信。
2.管道提供字节流传输服务:有序的、基于连接的、可靠的传输方式。
- 基于连接的:所有读端被关闭,则write触发异常;所有写端被关闭,则read读完数据后返回0,不再阻塞。
3.管道自带同步与互斥:
- 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
- 互斥:通过保证同一时间对临界资源的唯一访问保证操作安全性。
4.管道的生命周期随进程。