进程间的通信方式主要分为三类:管道、共享内存、消息队列;但实际上进程的通信方式可以分为六类:管道、共享内存、消息队列、信号量、信号、socket套接字编程。
进程间通信的必要性:
1.进程在操作系统内核中是独立的进程控制块(PCB),即一个一个struct task_struct{....}结构体对象。
2.每一个进程有自己独立的进程地址空间,即进程间的数据是独立的,也就造就了进程间的独立性。
3.进程和进程间交换数据较困难,所以有了进程间通信来完成数据交换过程。
一、管道
匿名管道:
只能在单个进程内部进程通信,即父子进程。
pipe函数的认识:
NAME
pipe, pipe2 - create pipe
SYNOPSIS
#include <unistd.h>
int pipe(int pipefd[2]);
DESCRIPTION
pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe.
RETURN VALUEOn success, zero is returned. On error, -1 is returned, and errno is set appropriately.
A. 管道函数成功调用会创建出来一个匿名管道,对应在内核中会有一个内核缓冲区
B. Pipefd是一个数组,数组有两个元素,分别对应管道的读写两端.
- fd[0]:是管道的读端
- fd[1]:是管道的写端
- 都是文件描述符
C. Pipefd这个参数是输出性参数,不需要程序员赋值.由Pipe函数在调用时赋值.
D.创建成功返回0,创建失败返回-1.
pipe函数使用:
#include <iostream>
#include <unistd.h>
int main(){
int fd[2];
int ret = pipe(fd);
if(ret < 0){
perror("pipe error");
exit(-1);
}
// success
printf("read fd[0]: %d\n", fd[0]);
printf("write fd[1]: %d\n", fd[1]);
return 0;
}
结果显示读端fd[0] 是3,写端fd[1]是4,对应也是的文件描述符,因为0,1,2分别是标准输入、输出、错误所占用了。
验证:
在原代码的基础上,加一个while死循环,查看该进程的pid,然后进行在查看该进程下的文件描述符,可以看到存在3、4号fd:
$ ll /proc/[pid]/fd
读写验证:
int main(){
int fd[2];
int ret = pipe(fd);
if(ret < 0){
perror("pipe error");
exit(-1);
}
// success
printf("read fd[0]: %d\n", fd[0]);
printf("write fd[1]: %d\n", fd[1]);
write(fd[1], "hello", 5);
char buf[1024] = {0};
read(fd[0], buf, sizeof(buf) - 1);
printf("buf: %s\n", buf);
return 0;
}
通过write往fd[1]写入了“hello”,然后使用read从fd[0]读出内容。
父子进程匿名管道
父子进程间是共享的同一个文件描述符,所以使用匿名管道,可以实现父子进程间的通信。
#include <iostream>
#include <unistd.h>
int main(){
int fd[2];
int ret = pipe(fd);
if(ret < 0){
perror("pipe error");
exit(-1);
}
// success
printf("read fd[0]: %d\n", fd[0]);
printf("write fd[1]: %d\n", fd[1]);
ret = fork();
if(ret < 0){
perror("fork fail");
exit(-1);
}
else if(ret == 0){
// 父进程写
write(fd[1], "hello", 5);
}
else{
// 子进程读
char buf[1024] = {0};
read(fd[0], buf, sizeof(buf) - 1);
printf("buf : %s\n", buf);
}
return 0;
}
命名管道:
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
使用指令创建:
$ mkfifo filename
使用程序代码创建:
函数: int mkfifo(const char *filename,mode_t mode);
例如:int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
命名管道打开方式:
使用open函数
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
验证:
使用mkfifo指令创建一个管道mypipo
使用cat < mypipo
读取管道中的数据
使用如下代码往管道内写如数据:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
// ./mypipo表示打开这个管道文件
int outfd = open("./mypipo", O_WRONLY);
write(outfd, "hello\n", 6);
close(outfd);
return 0;
}
一个进程往mkfifo创建的管道写入数据,另一个进程可用从这个管道读出数据。