进程间通信(IPC)
??即使是有血缘关系的父子进程之间,它们的PID也是独立的,所以它们彼此之间相互独立,当两个进程要进行数据交流时,需要在它们之间架起一个桥梁,数据便可以通过桥梁进行发送和接收,这就叫做进程间通信(InterProcess Communication)。就如上图所示。
??在32位系统上,对于不同进程来说其虚拟内存的用户空间往物理内存上映射将映射到不同区域中。但对于不同的进程来说其内核空间都是指同一个,即映射到相同的物理内存中。这就相当于进程之间有相同的内核空间,所以在不同的进程中搭建桥梁,就相当于在内核空间中的开辟一块缓冲区,这个缓冲区用来存放需要进行收发的数据,当需要进行发数据时就往缓冲区中写数据,当缓冲区中有数据时,需要的进程便可以取数据。
??进行进程间通的办法有如下四种:
???1.管道:默认指无名管道,使用最简单的一种方式,但是只能用于有血缘关系的进程之间;
???2.信号:携带的数据量有限,能都表示的数据也比较单一,开销最小,速度快;
???3.共享队列/共享内存:不需要血缘关系;
???4.套接字:最稳定性最好,复杂度最高。
??以上这些进程间通信方式,之后会慢慢更新,本章只讲解无名管道和有名管道
无名管道
??无名管道是内核使用环形队列机制,借助内核缓冲区(4K)实现。只能作用在有血缘关系的进程之间,也不能够在同一个进程中自己读自己写。队列具有先进先出的性质,数据也只能够读取一次,因为队列的读位置会递增,借助代码可使队列其形成一个"环"。进程通过写端来写数据,通过读端来读数据。
pipe函数
pipe()
头 文 件:
#include <unistd.h>
定义函数:
int pipe(int pipefd[2]);
参数分析:
pipefd[0]:读端描述符
pipefd[1]:写端描述符
返 回 值:
成功 : 0
失败 :-1
无名管道实验程序
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#define MSG_SIZE 512
int main(int argc, char const *argv[])
{
//建立通信管道
int pipefd[2];
if (pipe(pipefd))
{
perror("pipe error");
return -1;
}
//创建子进程,管道的描述符就可以顺利继承子进程
pid_t sp_id = fork();
if (sp_id > 0)
{
char *msg = calloc(1, MSG_SIZE);
printf("Please enter data to pipe:");
fgets(msg, MSG_SIZE, stdin);
int ret_val = write(pipefd[1], msg, strlen(msg)); //向管道写入数据
printf("MY ID :%d The number of bytes written is:%d\n", getpid(), ret_val);
ret_val = read(pipefd[0], msg, MSG_SIZE); //无数据将阻塞,有数据则读出管道数据
printf("MY ID :%d The number of bytes successfully read is :%d \n", getpid(), ret_val);
printf("read msg is: %s\n", msg);
}
if (0 == sp_id)
{
char *msg = calloc(1, MSG_SIZE);
int ret_val = read(pipefd[0], msg, MSG_SIZE); //无数据将阻塞,有数据则读出管道数据
printf("read msg is: %s", msg);
printf("MY ID :%d The number of bytes successfully read is :%d\n", getpid(), ret_val);
ret_val = write(pipefd[1], "OK, let me get ready", strlen("OK, let me get ready")); //向管道写入数据
printf("MY ID :%d The number of bytes writeen is :%d\n", getpid(), ret_val);
}
return 0;
}
注意
??1.管道以伪文件的方式存在,所以是以文件的方式来读写管道;
??2.数据只可以读一次,不可以使用lseek函数来定位读写位置;
??3.由于并发机制,子进程和父进程不一定谁运行的快;
??4.由于无名管道不具有原子性,所以有可能发生数据践踏。
有名管道
??有名管道有一个确切的名字(文件),可以在没有血缘关系的进程间使用(具有写入原子性),不会发生数据之间的践踏。
有名管道API
mkfifo()
头 文 件:
#include <sys/types.h>
#include <sys/stat.h>
定义函数:
int mkfifo(const char *pathname, mode_t mode);
参数分析:
pathname: FIFO的文件名
mode:文件权限
返 回 值:
成功 : 0
失败 :-1
access ( 判断是否具有存取文件的权限)
头文件:
#include <unistd.h>
定义函数:
int access(const char * pathname, int mode);
参数分析:
pathname:路径名
mode:判断文件的权限
R_OK 是否可读
W_OK 是否可写
X_OK 是否可执行
F_OK 是否存在
返回值:
若所有欲查核的权限都通过了检查则返回0值, 表示成功,
只要有一权限被禁止则返回-1.
有名管道实验程序
fifo.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define FIFO_PATH "./my_fifo" //需要先创建管道文件
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid > 0)
{
if (access(FIFO_PATH, F_OK | W_OK))
{
if (mkfifo(FIFO_PATH, 0666))
{
perror("mkfifo error");
exit(0);
}
}
// 打开管道文件
int fd_fifo = open(FIFO_PATH, O_WRONLY);
if (fd_fifo < 0)
{
perror("open fifo error");
exit(0);
}
//发送信息
char *msg = calloc(1, 512);
printf("Please enter data to fifo:");
fgets(msg, 512, stdin);
int ret_val = write(fd_fifo, msg, strlen(msg));
printf("the number of bytes successfully written is:%d\n", ret_val);
}
else if(0 == pid)
{
int ret_val = execl("./fifo_recv", "Even", NULL);
printf("ret_val:%d\n", ret_val);
}
return 0;
}
fifo_recv.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_PATH "./my_fifo"
int main(int argc, char const *argv[])
{
if (access(FIFO_PATH, F_OK | W_OK))
{
if (mkfifo(FIFO_PATH, 0666))
{
perror("mkfifo error");
exit(0);
}
}
sleep(3);
int fd_fifo = open(FIFO_PATH, O_RDONLY);
if (fd_fifo < 0)
{
perror("open fifo error");
exit(0);
}
//接收信息
char *msg = calloc(1, 512);
int ret_val = read(fd_fifo, msg, 512);
printf("The number of bytes read is %d\n", ret_val);
printf("fifo_recv read msg is : %s\n", msg);
return 0;
}
输出结果: