每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程 A 把数据从用户空间拷到内核缓冲区,进程 B 再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
1、管道
1.1 匿名管道
1)创建管道
匿名管道只能用于有亲缘关系的父子进程之间,且只能单向通讯。
管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。
#include <unistd.h>
int pipe (int fd[2]);
//例子
int fd[2];
int ret = pipe(fd);
**fd:**参数返回两个文件描述符,fd[0] 指向管道的读端,fd[1] 指向管道的写端。
**返回值:**0:创建成功;-1:创建失败。
2)管道实现进程间通讯流程
- 父进程创建管道,得到两个⽂件描述符指向管道的两端;
- 父进程 fork 出子进程,子进程继承父进程的管道描述符;
- 由于父子进程都有一套管道文件描述符,因此使用管道读端的需要关闭写端文件描述符,使用管道写端的需要关闭读文件描述符;
- 管道通过环形队列实现,数据从写端流入从读端流出。
3)使用示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int fd[2];
int ret = 0;
ret = pipe(fd);
if(ret < 0){
perror("creat pipe fail");
exit(-1);
}
pid_t pid = fork();
if(pid == 0){
//child
close(fd[0]); //减少一个引用计数
char *msg = "I am child\n";
for(int i = 0; i < 5; i++){
write(fd[1],msg,strlen(msg));
sleep(2);
}
close(fd[1]);
_exit(0);
}else{
//parent
close(fd[1]); //减少一个引用计数
char buf[100];
while(1){
ssize_t count = read(fd[0], buf, sizeof(buf));
if(count > 0){
fputs(buf, stdout);
}
if(count == 0) //关闭写描述符后,继续读会返回0
break;
}
close(fd[0]);
wait(NULL);
exit(0);
}
}
4)管道读取数据的四种情况
-
读端不读,写端一直写:
会导致管道里写满数据,满时将导致 write 阻塞。
-
写端不写,读端一直读:
会导致 read 阻塞。
-
读端一直读,写端不写且关闭了文件描述符:
当管道的剩余数据都被读取后,再次调用 read 会返回 0,而不是阻塞。
-
写端一直写,读端不读且关闭了文件描述符:
负责写的进程会收到 SIGPIPE 信号,通常会导致进程异常终止。
1.2 有名管道 FIFO
不同于匿名管道,FIFO 有一个路劲名与之关联,允许无亲缘关系的进程访问同一个 FIFO,进行通信。
1)创建 FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
**pathname:**FIFO 文件路径名。
**mode:**FIFO 文件权限,可由以下权限进行或运算组合:
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
**返回值:**0:创建成功;-1:创建失败。
注意:
mkfifo() 函数只用来创建 FIFO 文件,如果文件路径名已存在,则会返回 EEXIST。如果想要打开 FIFO 文件则用 open() 函数,如果不确定 FIFO 文件是否存在则先调用 mkfifo() 来检查,再调用 open 函数。
2)打开 FIFO
如果以某种方式(读/写)打开有名管道,系统将阻塞进程,直到有另一个进程(包括自己)以另一种方式(写/读)打开有名管道。一个进程可以以可读可写的方式打开有名管道,进程不会阻塞。
3)读操作
- 如果管道中没有数据,读操作默认阻塞;
- 如果管道中数据小于要读取数量,读出所有数据返回;
- 如果管道中数据大于要读取数量,读出期望大小数据返回。
4)写操作
- 如果管道中没有空间,写操作阻塞;
- 如果管道中剩余空间小于要写入数据,写满空间后阻塞;
- 如果管道中剩余空间大于要写入数据,写入数据后返回。
5)中途其中一个进程退出
- 未退出一端是写操作,返回 SIGPIPE 信号;
- 未退出一端是读操作,读操作不再阻塞返回 0。
6)使用示例
//写数据进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
char PWD[100] = {
0};
char writeData[] = "fifo write\n";
getcwd(PWD, sizeof(PWD)); //获取当前目录
strcat(PWD,"/FIFO"); //在当前目录添加FIFO命名
int ret = mkfifo(PWD, 0666);
if(ret != 0){
printf("已创建有名管道\n");
}
printf("%s\n",PWD);
int fd = open(PWD, O_WRONLY); //只写方式打开管道
for(int i = 0; i < 5; ++i){
write(fd, writeData, sizeof(writeData));
printf("写入第%d次\n",i);
sleep(2);
}
close(fd);
printf("写进程结束\n");
exit(0);
}
//读数据进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
char PWD[100] = {
0};
char buf[100];
int i = 1;
getcwd(PWD,