为什么进程间要通信
- 数据传输 进程需要把数据传递给其他进程
- 资源共享 多个进程之间有时需要共享一份资源
- 通知事件 比如子进程要把自己的退出信息交给父进程
- 进程控制 比如Debug进程需要控制待调试进程的所有陷入和异常 ,
并且知道他现在的状态。
因为每个进程本来是拥有自己的虚拟地址空间 用页表映射到每个进程自己的物理内存上的 每个进程自己视为独占系统资源 各个进程的地址空间 为了安全期间并不关联 相对封闭 所以需要一些机制来保证进程间能够通信
学习进程间通信必须了解临界资源的概念
1. 临界资源:把多个进程(执行流)能够看到访问的共同资源叫临界资源,管道是一种临界资源。
2 . 临界区:把访问临界资源的代码叫临界区。
进程间通讯的本质是二进程共享资源(不同进程看到公共资源),一进程以读方式一进程以写方式打开同一份文件
linux 下一切接文件,管道也是一种特殊的文件。
3. 互斥:在任意一个时刻只能有一个“人”访问临界资源,采用原子性原则访问。
4. 原子性操作:狭义的原子性操作指该操作的汇编代码只有一句(该操作不可分),广义的原子操作指一个操作正在执行 时决不会被切出。原子操作要么做了,要么没做,不存在正在做。
System V 的进程间通信机制有三种
1. 消息队列
2. 信号量
3. 共享内存
这四个机制的共同点是 让两个或几个要通信的进程 能够按照一定的规则访问同一内核内存空间 该资源也叫临界资源。 临界资源架起了两个进程之间的桥梁 。
1. 管道
管道是最古老的unix进程间通信方式 大概的思想是 让两个进程打开并访问到同一个文件 看到同一份内存 一个进程往文件里面写数据 另一个进程从文件里面读数据
这个文件于是就想链接两个进程的管道一样。
1.1匿名管道
匿名管道的创建
#include <unistd.h>
int pipe(int pipefd[2]);
int pipe2(int pipefd[2], int flags);
系统调用pipe 的参数是一个文件描述符数组 有两个数组元素
f[0] 表示文件的读端 f[1]表示文件的写端 返回值 成功返回0 失败返回 -1 并置errno 错误代码。
flag 表示 读写方式
pipe()系统调用默认读写方式阻塞
具体怎么实现通信呢?
这张图做了说明 父进程调用pipe() 创建了一个匿名管道(在内核空间)
并且默认打开这个管道文件的读端写端 f[0] f[1]文件描述符
之后 fork() 创建子进程 子进程继承了父进程的文件描述符表 故也默认打开f[0] f[1] 两个文件描述符
之后父进程关闭读端文件描述符 子进程关闭写端文件描述符 父进程往文件里写 子进程从文件里读 。 这样父子进程实现了通信
这是对于想要父写 子读的 也可以子写父读 父关f[1] 子关f[0]
代码实现 :
#include<stdio.h>
#include<sys/types.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(-1 == ret){
printf(" Create pipe error!, error code id %d",ret);
}
pid_t pid = fork();
if(pid < 0){
perror("fork");
}else if(0 == pid ){
//Child
close(_pipe[0]);
char* masg = NULL;
int i = 0;
while( i < 10){
masg = "I am Child!";
write(_pipe[1],masg,strlen(masg) + 1);
sleep(1);
}
}
else if( 0 < pid){
//Father
close(_pipe[1]);
char masg_buf[20];
int i = 0;
while( i < 10){
memset(masg_buf, '\0',sizeof(masg_buf));
read(_pipe[0],masg_buf,sizeof(masg_buf));
printf("Child said :%s\n",masg_buf);
+`````
i;
}
}
return 0;
}
从内核角度看进程间通信
两个进程的进程控制块 task_struct——-> file_struct———–>file 结构体——–>dentry 结构体——–> inode结构体
同时指向同一个inode结构体 一个file结构体的f_op ——>file_operators 为读 一个为写
这一段看不懂请看:http://blog.csdn.net/x__016meliorem/article/details/78825904
管道的读写规则:
和 pipe2() 函数的flag设置有关
没有数据可读时:
1. O_NONBLOCK diabale read()调用阻塞 进程暂停执行 直到有数据写进去才继续开始读
2. O_NONBLOCK enable read调用返回-1, errno值设置为 EAGAIN
当管道被写满的时候:
1. O_NONBLOCK disanble write()调用阻塞 进程暂停执行 直到有数据被读走才开始继续写。
2. O_NONBLOCK enable write()调用返回-1, errno至设置为 EAGAIN。
如果所有管道的对应写端文件描述符被关闭 ,read()返回0。
如果所有管道的对应读端文件描述符被关闭 , write()操作会产生信号SIGPIPE 该信号可能导致write()进程退出。(因为没有人读 写是没有意义的)
当写入数据量不大的 PIPE_BUF时 linux保证写入的原子操作。
当写入数据量很大的 PIPE_BUF时 linux保不证写入的原子操作。
管道的特点:
1. 生命周期随进程 当进程退出时 管道的内存空间被释放 。
2. 只用于有亲源关系的进程之间通信 , 因为有相同亲源的进程从共同祖先那里继承了一份文件描述符表 表中有同一管道的读写 端文件描述符
这样才能让不同进程看到同一管道
3. 半双工 数据只朝一个方向流动 要实现双向通信 就必须建立两个管道
4 管道通信是面向字节流的 一次写的数据不一定要一次都出来 ,一次读的数据不一定要一次写进去。
5. 内核对管道的操作提供同步与互斥保证
什么是同步与互斥
同步:多人多进程要完成最终目标就一定要按照一定次序协同完成 , 否则无法完成或效率底下。
互斥:任意时刻只能有一个“人” 访问临界资源 采用原子操作访问。
管道的同步与互斥是指:
同步:写满了就不再写 读完了就不再读
互斥:不可以两个进程同时读 或者写管道
命名管道fifo
如果想在两个并不相关的进程之间实现通信 就得使用FIFO命名管道
FIFO的意思是先进先出 mkfifo创建的文件 内核提供机制让 先写入的数据被先读到。
命名管道是一种特殊类型的文件
创建命名管道的函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
该函数的第一个参数是 要创建的命名管道文件的路径名 第二个参数mode是拥有者,所属组 , 和其他三种“人” 的权限。
命名管道的实质是创建一个文件, 两个进程以open方式打开这个文件 一个写一个读
命名管道与之前的匿名管道的区别
只在于 创建方式不同 可以在非亲元关系的进程
根本都是内核提供一块内存让 两个进程通过文件描述符 读写公共资源
在内核角度都是让两个进程的file结构体指向同一个inode节点
命名管道实现 client —–server 通信
client.c
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>
int main()
{
int ret = mkfifo("./myfifo", 0666);
if(ret < 0){
perror("mkfifo");
return 1;
}
int fd = open("./myfifo", O_RDONLY);
if(fd < 0){
perror("open");
return 2;
}
char buf[1024] = {0};
while(1){
ssize_t read_size = read(fd, buf, sizeof(buf) - 1);
if(read_size > 0){
buf[read_size - 1] = 0;
printf("client say : %s\n", buf);
}
else if(read_size == 0){
printf("client quit!, quit now\n");
return 4;
}else if(read_size < 0){
perror("read");
return 3;
}
}
close(fd);
return 0;
}
server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int ret = mkfifo("./myfifo", 0666);
if(ret == 0){
perror("mkfifo");
return 1;
}
int fd = open("./myfifo", O_WRONLY);
if(fd < 0){
perror("open");
return 2;
}
while(1){
char buf[1024] = {0};
printf("plase enter!:");
fflush(stdout);
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if(read_size > 0){
buf[read_size] = 0;
write(fd, buf, strlen(buf));
}
if(read_size <= 0){
perror("read");
return 3;
}
}
close(fd);
return 0;
}