目录
1.简单进程介绍
1.概念前提
1.进程之间由于虚拟地址空间的设计,使得不同进程之间相互独立
2.通信就是将进程之间能够相互关联
2.通信定义
1.数据传输:数据由一个进程传给另一个进程
2.资源共享:进程之间有共享的资源
3.通知事件:进程能够通知另一个进程反映的信息
4.进程控制:一个进程控制另一个进程
3.通信的意义
因为有些时候需要多进程协同
本质:
1.OS要给进程提供“公共资源”
2.进程通信需要一份都能看到的“公共资源”
2.进程间通信的方案
两套常用标准:
方案1 POSIX -- 让通信过程可以跨主机
方案2 Systen V -- 聚焦本地通信
1.管道
1.管道基于文件系统,并非上面的两套标准之一
2.管道分为:命名管道和匿名管道
3.一个线程连接另一个进程的一个数据流称为“管道”
1.匿名管道
管道的由来
1.对于子进程而言,一切拷贝自父进程,所以该进程的文件描述符表也一样拷贝,不过在这一层而言,都是独立的,因为系统的虚拟内存空间的设计
2.在内核中,父子进程的文件描述符表由于复制固然相等,以至于,他们指向内存的文件是一样的,所以在这一层数据是共享的
3.打破了数据独立,由于内核中有了公共部分,那么就能设计出完成通信的方法
4.不必需要刷新到磁盘后再提取内容,其实只要一个进程往另一个进程输入即可
5.因为不用磁盘,那么其实我们不需要特地创造一个文件专门来通信,由此管道是一种内存级文件,不需要磁盘的操作
6.内存级文件无法在磁盘路径中找到,那么我们也无法知道其名字,那么我们称这个管道叫匿名管道
真正的管道原理如图:
步骤分析:
父进程创建管道,分别以读写方式打开;如果只打开一端,那么子进程创建会出现跟父进程一样的操作,但是因为管道匿名,则不能再改变了
父进程fork()出子进程,子进程与父进程同样拥有读写操作
父进程去掉读端,子进程去掉写端 / 父进程去掉写端,子进程去掉读端
由图可知一般而言,管道只能单项数据通信。
创造一个pipe的代码
[0] -- 读取,[1] -- 写入
#include <iostream> #include <unistd.h> #include <cassert> #include <sys/types.h> #include <sys/wait.h> #include <cstdio> #include <string.h> using namespace std; int main() { // 第一步:创建管道文件,打开读写端 int fds[2]; int n = pipe(fds); assert(n == 0); // 0,1,2 -> 3,4 // cout << "fds[0]: " << fds[0] << endl; 读 // cout << "fds[1]: " << fds[1] << endl; 写 // 第二步:fork pid_t id = fork(); assert(id >= 0); if (id == 0) { // 子进程写,关闭读端 close(fds[0]); // 子进程的通信代码 const char *s = "我是子进程,我正在给你发消息"; int cnt = 0; while (true) { cnt++; char buffer[1024]; // 只有子进程能看到buffer的内容 snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]", s, cnt, getpid()); write(fds[1], buffer, strlen(buffer)); // 输入到文件里的,不需要考虑+1,因为文件不管"\0" sleep(1); } // 子进程 exit(0); } // 父进程读,关闭写端 close(fds[1]); // 父进程的通信代码 while (true) { char buffer[1024]; // 只有父进程能看到buffer的内容 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); // 文件中拿出来,需要空出一位 if (s > 0) buffer[s] = 0; // 最后一位变\0 cout << "Get Massage# " << buffer << " | my pid: " << getpid() << endl; // 父进程可以不用sleep } // 父进程 int m = waitpid(id, nullptr, 0); assert(m == id); return 0; }
原理图
两个进程是独立的,但是操作系统提供了共同访问的公共资源,以让进程通过调用系统函数从而完成操作。管道中读取信息,之后管道里该段信息会被无效化,不会再次呈现。
1.在读取时,写端暂停写入,读端会读完要求的数据后阻塞等待
2.读端阻塞,写端会写满要求的,随后也阻塞
3.关闭写端,读端接收到0证明读到末尾,之后的操作读段可以选择断开
4.关闭读段,写端会被操作系统传递信号终止,信号为13号,可以通过waitpid调用信号
管道的特征:
1.管道的生命周期随进程
2.管道可在有血缘关系的进程里通信
3.管道面向字节流,字节流就是不管管道输入的是什么类型的信息
4.半双工 -- 单向通信
5.互斥与同步 -- 用于保护公共资源的一种方案
Linux的管道" | "运行机理
bush会创建两个进程,随后由于血缘关系进程可以通信
2.命名管道
p为管道文件,有自己的inode,说明是一个独立文件
两个非血缘关系的文件打开同一份文件,得到同一块公共资源在内核中,而命名管道因为其有自己的名字,通过路径和名字,可以标定唯一的文件,也就是说进程可以找到同一个资源。
1.创建函数的调用
管道文件名和文件的权限
bool creatFifo(const std::string &path) { umask(0); int n = mkfifo(path.c_str(), 0600); if (n == 0) return true; else { std::cout << "error: " << errno << " err string: " << strerror(errno) << std::endl; return false; } }
2.销毁管道文件
void removeFifo(const std::string &path) { int n = unlink(path.c_str()); assert(n); (void)n; }
一个进程创建管道文件,会阻塞在创建管道文件这里;需要另一个进程连接管道文件完毕才让阻塞的进程进一步执行。
//comm.hpp
#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#define NAMED_PIPE "/tmp/mypipe"
bool creatFifo(const std::string &path)
{
umask(0);
int n = mkfifo(path.c_str(), 0600);
if (n == 0)
return true;
else
{
std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
return false;
}
}
void removeFifo(const std::string &path)
{
int n = unlink(path.c_str());
assert(n);
(void)n;
}
//client.cc
#include "comm.hpp"
int main()
{
std::cout << "client begin" << std::endl;
int wfd = open(NAMED_PIPE, O_WRONLY);
std::cout << "client end" << std::endl;
if (wfd < 0)
exit(1);
// write
char buffer[1024];
while (true)
{
std::cout << "Please Say# ";
fgets(buffer, sizeof(buffer), stdin);
if (strlen(buffer) > 0)
buffer[strlen(buffer) - 1] = 0; //消掉在shell上输入时的换行
ssize_t n = write(wfd, buffer, strlen(buffer));
assert(n == strlen(buffer));
(void)n;
}
close(wfd);
return 0;
}
//server.cc
#include "comm.hpp"
int main()
{
bool r = creatFifo(NAMED_PIPE);
assert(r);
(void)r;
std::cout << "server begin" << std::endl; // 打开即打印
int rfd = open(NAMED_PIPE, O_RDONLY); // 打开后检查client,没有打开则阻塞
std::cout << "server begin" << std::endl; // client准备好再打印这个
if (rfd < 0)
exit(1);
// read
char buffer[1024];
while (true)
{
ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << "clien->serve# " << buffer << std::endl;
}
else if (s == 0)
{
std::cout << "clien quit,me too!" << std::endl;
break;
}
else
{
std::cout << "err string: " << strerror(errno) << std::endl;
break;
}
}
close(rfd);
removeFifo(NAMED_PIPE);
return 0;
}