进程间也要唠一唠

讲讲进程间通信是怎么会是吧

之前用的环境一直是vim+Centos

到后面可以尝试改变一下

这就是我们崭新的组合技:

VScode + Ubuntu + 20.04/22.04 + C++11、C++14

Centos快要停止维护了,尽力搞Ubuntu

VScode是微软开发滴

介素官网:

Visual Studio Code - Code Editing. RedefinedVisual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.  Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.icon-default.png?t=N7T8https://code.visualstudio.com/

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中执行命令可以用管道传输,而被执行的命令是分别创建进程的,但是创建的进程的父进程都是同一个,这是匿名管道让它们进行通信

从内核角度上看管道就和看文件一样,管道的使用和文件一致:

先聊到这里吧,我要去炉石了

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值