Linux -- 进程间通信(1)

简介: 1. vscode软件下载和使用1.1 下载1.1.1 解决下载慢问题链接:https://blog.csdn.net/wang13679201813/article/details/1253675321.1.2 推荐下载链接链接:https://vscode.cdn.azure.cn/stable/30d9c6cd9483b2cc586687151bcbcd635f373630/VSCodeUserSetup-x64-1.68.1.exe

1. vscode软件下载和使用

1.1 下载

1.1.1 解决下载慢问题

链接:https://blog.csdn.net/wang13679201813/article/details/125367532

1.1.2 推荐下载链接

链接:https://vscode.cdn.azure.cn/stable/30d9c6cd9483b2cc586687151bcbcd635f373630/VSCodeUserSetup-x64-1.68.1.exe

1.2 vscode是什么

vscode(visual studio code)是一个编辑器,不是编译器,这里我们使用vscode+centos:也就是windows+linux开发,使用C++语言,这里用vscode取代Linux的vim。

1.3 Windows本地vscode使用

刚下载的桌面就是这样的:

image-20230405122018828


image.png


打开目录后按照桌面对应路径找到刚刚创建的文件,打开即可。

微信图片_20230524004024.png

通过上面这个框就可以新建文件和新建目录等操作。这里不支持编译和运行。


1.4 远程连接linux

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmDdkau8-1681550357620)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132005.png)]

微信图片_20230524004154.png

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EYMrX7wf-1681550357621)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132821.png)]


重启后就可以看到对应的SSH下就有对应的主机。后续就是连接新的Window,然后选择Linux,输入密码就OK了。连接成功后,Linux主机名会显示一个绿色的勾勾。后续操作按照对应的来就OK了,不演示了。


远程运行直接使用快捷方式:ctrl+s,或者点击最上面框架中的Terminal终端,new一个新的终端即可。


1.5 推荐插件

  1. C/C++
  2. C/C++ Extension Pack
  3. C/C++ Themes
  4. Chinese
  5. vscode-icons
  6. file-size
  7. GBK to UTF8 for vscode
  8. GDB Dubug(不建议使用vscode搭建的远程调试),这里最好使用Linux的gdb调试

2. 进程间通信目的

  1. 数据传输(一个进程需要把自己的数据传输给另外一个进程)
  2. 资源共享(多个进程之间共享同样的资源)
  3. 通知事件(一个进程需要向另外一个或多个进程发送消息,通知它们发生某种事件)
  4. 进程控制(有些进程需要完全控制另外一个进程的执行)

3. 为什么需要通信

进程具有独立性,需要让独立进程通信就需要成本。不能让一个进程直接访问另外一个进程的"资源",不能违背进程具有独立性,所以让两个程序通信前提条件是:先让两个进程看到同一份“资源”,不直接访问,"资源"就由操作系统来提供!

4. 匿名管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

[jyh@VM-12-12-centos study17]$ who //查看当前正在登陆的用户
jyh      pts/0        2023-04-05 16:30 (171.113.60.75) 
[jyh@VM-12-12-centos study17]$ who | wc -l //wc是用来统计个数
1
[jyh@VM-12-12-centos study17]$ 

上述who是一个命令,本质它是一个进程,一个bash的子进程,wc同样是一个bash的子进程,通过管道的方式把who进程的数据也让wc进程看到。

微信图片_20230524004443.png

如何理解呢?首先Linux下一切皆文件,管道也是文件,who进程一般都是从标准输出中获取的信息,wc进程一般都是从标准输入中获取信息的,将who进程的标准输出重定向到管道这个文件中,wc进程则将标准输入重定向到管道文件中,所以这样就可以实现两个进程看到同一份“资源”。

4.1 原理

微信图片_20230524004527.png

创建子进程,只会复制进程相关的数据结构对象,并不会拷贝父进程的文件对象。所以上述两个进程中文件描述符表中每个指针的指向的都是同一个文件对象。这让就可以解析一个现象:fork之后,父子进程会向同一个显示器打印数据的原因。这里OS提供的内存文件就是管道文件,这样就可以达成共享同一"资源"。(这里管道文件不是放在磁盘中的,是一个内存文件,它只是支持单向通信)。另外管道文件是确定数据流向的,关闭不需要的fd,也就是当who进程进行write的时候,wc进程进行read的时候,此时who进程就会关闭对应的read对应的描述符,wc进程就会关闭对应的write对应的描述符,就可以解析清楚上一个管道的案例的现象了。(这里的这个管道也就是匿名管道,不知道它对应的路径,也不知道对应的文件名等等。

4.2 代码案例

选项 pipe()系统调用
声明 int pipe(int pipefd[2]);
头文件 #include <unistd.h>
作用 创建管道
详细描述 Pipe()创建一个管道,这是一个单向数据通道,可用于进程间通信。数组pipefd用于返回2引用管道两端的文件描述符。Pipefd[0]表示管道的读端Pipefd[1]表示写端管道。写入管道写端的数据被内核缓冲,直到从管道的读端读取。
返回值 如果成功,则返回0。如果出现错误,则返回-1,并适当地设置errno。
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{
    //1. 创建管道
    int pipefd[2] = {0};
    int fd_result = pipe(pipefd);
    if(fd_result == -1)
    {
        cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;
    }
    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端
    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //child(写入)
    {
        close(pipefd[0]); //关闭读端
        //4. 开始通信
        const string name_str = "hello, i am child";
        int cnt = 1;
        char buffer_child[1024];
        while(true)
        {
            snprintf(buffer_child, sizeof(buffer_child), "name:%s, cnt:%d, my_pid:%d", name_str.c_str(), cnt++, getpid());
            write(pipefd[1], buffer_child, strlen(buffer_child));
            sleep(1);
        }
        exit(0);    
    }
    //parent(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    while(true)
    {
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
            cout << "call read() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;
        }
        buffer_parent[n] = '\0';
        cout << "i am parent, child give me message:" << buffer_parent << endl;
    }
    return 0;
}

五个特点

管道是单向通信

管道本质是文件,文件描述符声明周期是随进程的,管道的声明周期是随进程的

不仅父子进程可以通信,爷孙进程也可以通信,兄弟进程也可以通信,所以管道通信通常用来具有"血缘关系"的进程进行进程间通信。(pipe()打开管道并不清楚管道名字 --> 匿名管道)

写入的次数和读取的次数不是严格匹配的,可能一次写的东西分很多次读,可能多次写的东西一次读取,读写没有强相关

如果一个进程read端读取完管道所有数据,对方如果不发,那么只能等待;如果write端写满管道了,不能够写了;管道具有一定的协同能力,让读端和写端能够按照顺序通信(自带同步机制)


两个场景

  1. 关闭写端,读端没有关闭
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{
    //1. 创建管道
    int pipefd[2] = {0};
    int fd_result = pipe(pipefd);
    if(fd_result == -1)
{
        cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;
    }
    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端
    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //child(写入)
    {
        close(pipefd[0]); //关闭读端
        //4. 开始通信
        int cnt = 0;
        while(true)
        {
            char x = 'A';
            write(pipefd[1], &x, 1);
            cout << "cnt:" << cnt << endl;
            sleep(3);
            break;
        }
        close(pipefd[1]);
        exit(0);    
    }
    //parent(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    while(true)
    {
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
            cout << "read is anomaly" << endl;
        }
        if(n == 0){
            cout << "read end of file" << endl;
            break;
        }
        buffer_parent[n] = '\0';
        cout << "i am parent, child give me message:" << buffer_parent << endl;
    }
    close(pipefd[0]);
    return 0;
}
//输出结果:
//pipefd[0]:3
//pipefd[1]:4
//i am parent, child give me message:A
//cnt:0
//read end of file

所以,关闭写端,读端没有关闭,读端再去读取数据,read就会返回0,那么就读到了文件结尾。


  1. 关闭读端,写端没有关闭
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;
int main()
{
    //1. 创建管道
    int pipefd[2] = {0};
    int fd_result = pipe(pipefd);
    if(fd_result == -1)
{
        cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl;
    }
    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端
    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //child(写入)
    {
        close(pipefd[0]); //关闭读端
        //4. 开始通信
        int cnt = 0;
        while(true)
        {
            char x = 'A';
            write(pipefd[1], &x, 1);
            cout << "cnt:" << cnt << endl;
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);    
    }
    //parent(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    while(true)
    {
        //sleep(10);
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
            cout << "read is anomaly" << endl;
            break;
        }
        if(n == 0){
            cout << "read end of file" << endl;
            break;
        }
        buffer_parent[n] = '\0';
        cout << "i am parent, child give me message:" << buffer_parent << endl;
        sleep(1);
        break;
    }
    close(pipefd[0]);
    return 0;
}
//输出结果:
//pipefd[0]:3
//pipefd[1]:4
//i am parent, child give me message:A
//cnt:0

关闭读端,写端没有关闭,这样就没有意义,操作系统不会维护不会浪费资源,OS会通过信号直接kill掉一直在写入的进程。

4.3 玩一玩(进程池)

4.3.1 模型

微信图片_20230524005845.png

4.3.2 代码

  • processPool.hpp
#include <iostream>
#include <assert.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
class manage_child;
class Task;
#define CHILD_PROCESS_NUM 3
void create_frame(std::vector<manage_child>& manage);
void control_child_process(const std::vector<manage_child>& manage);
void recycle_process(const std::vector<manage_child>& manage);
class manage_child
{
public:
    manage_child(int fd, int id)
        :_write_fd(fd)
        ,_child_id(id)
    {}
    ~manage_child(){}
public:
    int _write_fd;
    pid_t _child_id;
};
typedef void(*fun_p)();
#define GET_NET 1
#define POLL_SERVER 2
#define PUSH_SOURCE 3
void get_net();
void poll_server();
void push_source();
class Task
{
public:
    Task(){
        funcs.push_back(get_net);
        funcs.push_back(poll_server);
        funcs.push_back(push_source);
    }
    void execute_task(int option){
        assert(option >= 1 && option <= 3);
        funcs[option - 1]();
    }
    ~Task(){}
private:
    std::vector<fun_p> funcs;
};
  • main.cc
#include "processPool.hpp"
int main()
{
    //创建框架:一个父进程控制多个子进程(父进程写,子进程读)
    std::vector<manage_child> manage; 
    create_frame(manage);
    //父进程控制任意子进程执行任务
    control_child_process(manage);
    //回收子进程并关闭管道
    recycle_process(manage);
    return 0;
}
  • processPool.cc
#include "processPool.hpp"
void get_net()
{
    std::cout << "子进程:PID:" << getpid() <<  "获取网络资源中........." << std::endl;
}
void poll_server()
{
    std::cout << "子进程:PID:" << getpid() <<  "下载服务资源中........" << std::endl;
}
void push_source()
{
    std::cout << "子进程:PID:" << getpid() <<  "发送服务资源中........" << std::endl;
}
void execute_command(int read_fd)
{
    Task task;
    while(true){
        int command = 0;
        int read_return = read(read_fd, &command, sizeof(int));
        if(read_return == sizeof(int)){
            //执行任务
            task.execute_task(command);
        }else{
            break;
        }
    }
}
void create_frame(std::vector<manage_child>& manage)
{
    std::vector<int> fds;
    for(int i = 0; i < CHILD_PROCESS_NUM; ++i){ 
        //创建管道
        int pipefd[2];
        int pipe_return = pipe(pipefd);
        assert(pipe_return == 0);
        (void)pipe_return;
        //创建子进程
        pid_t id = fork();
        assert(id != -1);
        if(id == 0){ //子进程 - 读
            for(auto& fd : fds) close(fd); //关闭子进程拷贝父进程的写端
            close(pipefd[1]);
            //dup2(pipefd[0], 0);
            execute_command(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        //父进程
        close(pipefd[0]);
        //父进程对子进程和写端组织管理
        manage.push_back(manage_child(pipefd[1], id)); 
        fds.push_back(pipefd[1]);
    }
}
void show_option(){
    std::cout << "-----------------------------------------" << std::endl;
    std::cout << "---  1. 获取网络资源   2. 下载服务资源  ---" << std::endl;
    std::cout << "---  3. 发送服务资源   4. 退出服务系统  ---" << std::endl;
    std::cout << "-----------------------------------------" << std::endl;
}
void control_child_process(const std::vector<manage_child>& manage)
{
    while(true){
        //选择任务
        show_option();  
        std::cout << "请选择->";
        int command = 0;
        std::cin >> command;
        if(command == 4) break;
        if(command < 1 || command > 3) continue;
        //选择进程
        int rand_index = rand() % manage.size();
        std::cout << "被选中的子进程:" << manage[rand_index]._child_id << std::endl;
        //分发任务
        write(manage[rand_index]._write_fd, &command, sizeof(command));
        sleep(1);
    }   
}
void recycle_process(const std::vector<manage_child>& manage)
{
    for(int i = 0; i < manage.size(); ++i){
        std::cout << "父进程让子进程退出" << manage[i]._child_id << std::endl;
        close(manage[i]._write_fd);
        waitpid(manage[i]._child_id, nullptr, 0); //阻塞式等待
        std::cout << "父进程回收子进程:" << manage[i]._child_id << std::endl;
    }
    sleep(5); //父进程退出
}




















相关文章
|
1月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
38 0
|
22天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
88 4
linux进程管理万字详解!!!
|
12天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
54 8
|
9天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
21天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
60 4
|
22天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
23天前
|
消息中间件 存储 Linux
|
30天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
39 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
26 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
32 0
Linux c/c++之IPC进程间通信
下一篇
无影云桌面