讲讲进程间通信是怎么会是吧
之前用的环境一直是vim+Centos
到后面可以尝试改变一下
这就是我们崭新的组合技:
VScode + Ubuntu + 20.04/22.04 + C++11、C++14
Centos快要停止维护了,尽力搞Ubuntu
VScode是微软开发滴
介素官网:
update是历史的更新列表:
无脑装VScode就完事了
VScode不是IDE,是文本编辑器,逻辑上只能写代码
但是VScode+插件可以打造本地市IDE
VScode也可以远程开发链接云服务器
VScode可以把vim给替换掉
先康康VScode在本地怎么用
先进去然后打开文件夹(因为我的代码比较多所以干脆在D盘新建了个文件夹存代码)
我的VScode是这个主题
还蛮高端的,,,
我最爱的黑金商务风
然后在里面新建文件就可以直接写代码了:
VScode的插件有一个比较主要(对大部分人,喜欢用英文的当我没说),安装个汉化插件:
就是这个Chinese
看到了吗,安装它!
感谢愿意开源的佬
Remote-ssh也是个很好用的插件
安装过后会有远程资源管理器:
登录就是添加个主机:
ssh 用户名@主机的IP地址
在第一次登陆完之后会要求更新一下配置文件,这个时候选第一个即可
介素我的:
让你选择操作系统:
想必是mac,然后就和Xshell一样输密码就好咯:
连接完它会提示你已经链接:
就和Xshell一样可以打开对应目录下的文件咯:
打开文件夹之后,Ctrl+S自动保存一下
写好的代码会自动同步到云服务器中,云服务器上的改动也会同步到VScode上
在开发时需要安装的插件:C/C++
还有这个扩展包:
在设置的时候可以指定支持什么巴拉巴拉
编译采取命令行编译Ctrl+~即可把终端显示出来
连云服务器调试后会很卡,基本上用不了
Ubuntu安装g++:
apt-get install build-essential
Ubuntu安装Cmake:
apt-get install cmake
调试用cgdb
centos安装cgdb:
yum install -y cgdb
Ubuntu安装cgdb:
apt install cgdb
比gdb强一点
进程通信
为什么
进程间需要某种协同,所以如何协同的前提条件--通信
数据是由类别的通知就绪的
可以类比成,体育老师暖暖生病了,于是数学老师墨墨酱只好把课占用过来了
所以体育课最终变成数学课,是暖暖和墨墨酱通信混产生的结果
单纯要传递给我的数据,控制相关的信息
目的:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程具有独立性
进程=内核数据结构+代码和数据
怎么办
因为进程具有独立性,所以进程间通信成本稍高
不说话 一定是在偷吃超市货物
进程间通信的前提:让不同的进程看到同一份系统资源(一段内存)
进程主动要求操作系统给它分配内存(需要通信的进程让操作系统创建一份共享资源),而操作系统为了保证自身的安全也会提供系统调用
OS创建的共享资源不同,系统调用的接口也会不同,进程间通信的种类也会有不同
管道通信比如从键盘读取数据,写入管道,读取管道再写到屏幕:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
{
perror("make pipe"),exit(1);
}
// read from stdin
while (fgets(buf, 100, stdin))
{
len = strlen(buf);
// write into pipe
if (write(fds[1], buf, len) != len)
{
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ((len=read(fds[0], buf, 100)) == -1)
{
perror("read from pipe");
break;
}
// write to stdout
if (write(1, buf, len) != len)
{
perror("write to stdout");
break;
}
}
return 0;
}
常见方式
从开始的本地通信到后面的网络通信都属于进程间通信的范畴
进程通信有一些标准:
system V IPC (unix操作系统众多版本的一支)
System V 消息队列
System V 共享内存
System V 信号量
还有POSX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
标准:
标准很重要,因为它能实现由不同国家不同公司生产出的电子产品间相互通信,标准的指定肯定是由业界翘楚进行了,领头羊有专利,它是一套标准体系
方式:
1.消息队列
2.共享内存(🍍)
3.信号量
能否复用内核代码直接进行通信呢?
可以的
用管道就好,管道分为命名和匿名
管道通信
匿名管道
管道是Unix中最古老的进程间通信的形式
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
进程有进程控制块task_struct,每个还相应的有文件描述符表struct file *fd_array[ ],磁盘上有个文件:hello.txt,进程分别以读方式和写方式打开同一文件
读方式和写方式不一样,要创建不同的struct file,struct file里存在文件所有的属性innode,还有操作方法的集合,以及内核级文件缓冲区
操作系统会将磁盘文件的内容加载到缓冲区,属性加载到innode中
写的时候也是向缓冲区写,写完操作系统会自己刷新
struct file会被创建两次,但是文件只会加载一次
为了通信要创建子进程,子进程的创建是以父进程为模板的,所以子进程在创建的时候会问父进程要一份模板,所以子进程拷贝一份task_struct和struct file_struct,那struct file有没有必要给紫禁城拷贝一份捏?
需要注意的是,进程具有独立性,但是文件系统并没这样的限制,当你拷贝完struct file_struct时就已经结束了,里面的指针已经指向对应的文件咯,至此父进程打开的文件紫禁城也可以看到了
为什么父子进程会向同一个显示器终端打印数据呢?
因为父子进程指向的文件是同一个,当写数据的时候也会都向对应的缓冲区里写,所以刷新的时候就打印到了同一个显示器上啦
进程默认会打开三个标准输出012,它是怎么做到的捏?
是因为所有进程都是bash的紫禁城,bash打开了,所有的紫禁城默认也就打开了,我们只需做好约定即可
还有个问题:为什么紫禁城使用close的时候关闭012,却不会影响父进程继续使用显示器文件呢?
因为文件系统内部存在内存级的引用计数
file内部有ref_count,当紫禁城关闭了文件时,引用计数--,当引用计数为0时才会释放文件资源
父进程让操作系统巴拉巴拉,,,
进程间通信的本质是让两个进程能看到同一份公共的资源,这份公共资源是由操作系统统一分配的,而我们把这样的文件叫做管道文件
为了保证通信的合理合法性,有一些相应的规定:针对这种通信方式,只允许父子之间单向通信(因为它简单)
互相关闭自己不需要的文件描述符,而父子进程既然要关闭不需要的fd,那为何曾经要打开呢?
当然是为了让紫禁城继承下去啦
关闭是必须的吗?
不是,可以不关闭,不影响通信,但是依然建议关闭
进程打开的文件描述符是数组形式,那么也就注定了打开是有上限的
本身思源也是有限的,为了不浪费系统资源,还是关闭(万一误写覆盖就不好了)
进程间通信为什么一定要写入磁盘这种方式呢,操作系统也在想这个问题,它在想能不能让它们实现内存级的通信,即不刷新到磁盘
首先来看父进程创建管道:
接着是父进程fork出紫禁城:
然后是父进程关闭fd[0],紫禁城关闭fd[1]
想要创建管道,有这样的一个系统调用接口:
int pipe(int pipefd[2]);
当然底层也是open,而它的参数是输出型参数,会把文件描述符输出
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
而且这样打开文件时不需要文件路径和文件名
所以它叫匿名管道
如果想双向通信该怎么办?
可以创建两个管道
那管道为甚么要单向通信呢?
简单所以单向,因为只让它进行单向通信所以管他叫管道
怎么使用管道通信
写个代码验证下先:
testPipe.cc:
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
int main()
{
int pipefd[2];
int n= pipe(pipefd); //输出型参数,rfd,wfd
if(n!=0)
{
std::cerr<<"errno:"<<errno<<":"<<"errstring:"<<strerror(errno)<<std::endl;
return 1;
}
std::cout<<"piped[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<std::endl;
return 0;
}
makefile:
testPipe:testPipe.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f testPipe
嘻嘻:
可以验证出管道文件描述符确实是3和4
pipefd[0]是古希腊掌管读的神(长得像张开的嘴)
pipefd[1]是古埃及掌管写的神(长得像一支钢笔)
我们成功让不同进程看到了同一份资源,进程间通信是有成本的
进程间通信 --- IPC
进程具有独立性
怎样进行文件的读写捏?
那么问题来了,在写入操作的时候,有没有写入 \0 以及有没有必要写入 \0 呢?
以 \0 为结尾是C的规定,和文件有什么关系呢?
没必要写,写进去也是乱码字符
写一段父子进程间通信的代码吧:
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/wait.h>
const int size = 1024;
std::string getOtherMessage()
{
static int cnt =0;
std::string messageid=std::to_string(cnt);
cnt++;
pid_t self_id=getpid();
std::string stringpid=std::to_string(self_id);
std::string message="messageid: ";
message+=messageid;
message+=" my pid is: ";
message+=stringpid;
return message;
}
//子进程写入
void SubProcessWrite(int wfd)
{
std::string message = "father, I am your son process! ";
while (true)
{
std::string info = message + getOtherMessage();
write(wfd,info.c_str(),info.size());
sleep(1);
}
}
//父进程读取
void FatherProcessRead(int rfd)
{
char inbuffer[size]; //c99 gnu g99
while(true)
{
ssize_t n =read(rfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[n] = 0; //你不带上无所谓,我会自己添加
std::cout << "father get message:" << inbuffer << std::endl;
}
}
}
int main()
{
//创建管道
int pipefd[2];
int n= pipe(pipefd); //输出型参数,rfd,wfd
if(n!=0)
{
std::cerr<<"errno:"<<errno<<":"<<"errstring:"<<strerror(errno)<<std::endl;
return 1;
}
std::cout<<"piped[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<std::endl;
//创建子进程
pid_t id=fork();
if(id==0)
{
std::cout << "我是儿子,我准备好了,我准备好了!" << std::endl;
//关闭不需要的fd
close(pipefd[0]);
//子进程 -- write
SubProcessWrite(pipefd[1]);
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
//父进程 -- read
std::cout << "我是爹,我准备好了,我准备好了!" << std::endl;
FatherProcessRead(pipefd[0]);
close(pipefd[0]);
pid_t rid=waitpid(id,nullptr,0);
if(rid>0)
{
std::cout<<"wait child process done"<<std::endl;
}
return 0;
}
顺带搓个监控脚本,VScode是真方便呀
while :; do ps ajx | head -1 && ps ajx | grep testPipe | grep -v grep; echo "-------------------------------------------------------------------------------"; sleep 1; done
至此就完成了一次父子间进程的通信咯
但是有个问题
说好的通信呢?
没办法修改数据(有写时拷贝,对方看不到对面修改的数据),没办法一直通信(只能通信一次)
还只能单向通信
管道四种情况
🍍如果管道的内部是空的,并且写端的文件描述符没有关闭,读取条件不具备,读进程会被阻塞等待读取条件具备(写入数据)
🍍如果父进程一直不读,但是紫禁城一直写,到最后就会卡在65536处(写满咯)
65536/1024=64kb
紫禁城写满之后会进行阻塞等待(写条件不具备,直到写条件具备)
🍍如果管道一直在读或者写端关闭了wfd,那么读端的返回值会读到0,表示读到了文件的结尾
🍍如果rfd直接关闭,写端wfd一直在进行写入呢?
有点joker了,操作系统不会做这种浪费时间浪费生命的事
broken pipe
OS会杀掉对应的进程(写端进程被OS直接用13号信号关掉,相当于进程出现了异常)
管道五种特征
🍍匿名管道只能用于进行父子间通信(毫不相干的是不可以的,因为我们无法让两个不ins的进程看到同一文件),有个问题:爷孙进程能通信么?
可以的,稍加改动就可实现爷孙通信:
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/wait.h>
const int size = 1024;
std::string getOtherMessage()
{
static int cnt =0;
std::string messageid=std::to_string(cnt);
cnt++;
pid_t self_id=getpid();
std::string stringpid=std::to_string(self_id);
std::string message="messageid: ";
message+=messageid;
message+=" my pid is: ";
message+=stringpid;
return message;
}
//子进程写入
void SubProcessWrite(int wfd)
{
std::string message = "grandfather, I am your grandson process! ";
while (true)
{
std::string info = message + getOtherMessage();
write(wfd,info.c_str(),info.size());
sleep(1);
}
}
//父进程读取
void FatherProcessRead(int rfd)
{
char inbuffer[size]; //c99 gnu g99
while(true)
{
ssize_t n =read(rfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[n] = 0; //你不带上无所谓,我会自己添加
std::cout << "father get message:" << inbuffer << std::endl;
}
}
}
int main()
{
//创建管道
int pipefd[2];
int n= pipe(pipefd); //输出型参数,rfd,wfd
if(n!=0)
{
std::cerr<<"errno:"<<errno<<":"<<"errstring:"<<strerror(errno)<<std::endl;
return 1;
}
std::cout<<"piped[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<std::endl;
//创建子进程
pid_t id=fork();
if(id==0)
{
std::cout << "我是孙子,我准备好了,我准备好了!" << std::endl;
//关闭不需要的fd
close(pipefd[0]);
//子进程 -- write
//创建孙子进程
if(fork()>0)
{
exit(0);
}
SubProcessWrite(pipefd[1]);
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
//父进程 -- read
std::cout << "我是爷,我准备好了,我准备好了!" << std::endl;
FatherProcessRead(pipefd[0]);
close(pipefd[0]);
pid_t rid=waitpid(id,nullptr,0);
if(rid>0)
{
std::cout<<"wait child process done"<<std::endl;
}
return 0;
}
所以严格意义上来说,这种管道只能用于进行有血缘关系的进程的通信
🍍父进程在读取消息的时候速度要和紫禁城保持一致,这样设计的主要原因是在多进程情况下,公共资源可能会存在被多个进程同时访问的情况,会造成由并发导致的数据不一致问题
所以管道内部自带进程间同步的机制,在多执行流执行代码的时候具有明显的顺序性
🍍管道文件的生命周期是随进程的
🍍管道文件在通信的时候是面向字节流的(写入的次数和读取的次数不是一 一匹配的)
🍍管道的通信模式是一种特殊的半双工模式(俩人你一句我一句,但是不要同时说)
吵架是全双工模式了
在Linux中执行命令可以用管道传输,而被执行的命令是分别创建进程的,但是创建的进程的父进程都是同一个,这是匿名管道让它们进行通信
从内核角度上看管道就和看文件一样,管道的使用和文件一致:
先聊到这里吧,我要去炉石了