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); //父进程退出
}




















相关文章
|
2天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
2月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
126 1
|
15天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
66 34
|
19天前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
57 16
|
1月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
151 20
|
3月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
489 58
|
2月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
126 13
|
2月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
3月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
235 4
linux进程管理万字详解!!!
|
2月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####