Linux -- 进程间通信

news2025/1/20 3:56:35

文章目录

  • 1. vscode软件下载和使用
    • 1.1 下载
      • 1.1.1 解决下载慢问题
      • 1.1.2 推荐下载链接
    • 1.2 vscode是什么
    • 1.3 Windows本地vscode使用
    • 1.4 远程连接linux
    • 1.5 推荐插件
  • 2. 进程间通信目的
  • 3. 为什么需要通信
  • 4. 匿名管道
    • 4.1 原理
    • 4.2 代码案例
    • 4.3 玩一玩(进程池)
      • 4.3.1 模型
      • 4.3.2 代码
  • 5. 命名管道
    • 5.1 概念
    • 5.2 系统调用
    • 5.3 不同进程间通信
    • 5.4 命名管道实现进程池
  • 6. 共享内存
    • 6.1 概念
    • 6.2 原理
    • 6.3 共享内存数据结构
    • 6.4 认识接口
    • 6.5 代码

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

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

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

1.4 远程连接linux

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmDdkau8-1681550357620)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132005.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进程看到。

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

4.1 原理

创建子进程,只会复制进程相关的数据结构对象,并不会拷贝父进程的文件对象。所以上述两个进程中文件描述符表中每个指针的指向的都是同一个文件对象。这让就可以解析一个现象: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;
}

五个特点

  1. 管道是单向通信
  2. 管道本质是文件,文件描述符声明周期是随进程的,管道的声明周期是随进程的
  3. 不仅父子进程可以通信,爷孙进程也可以通信,兄弟进程也可以通信,所以管道通信通常用来具有"血缘关系"的进程进行进程间通信。(pipe()打开管道并不清楚管道名字 --> 匿名管道)
  4. 写入的次数和读取的次数不是严格匹配的,可能一次写的东西分很多次读,可能多次写的东西一次读取,读写没有强相关
  5. 如果一个进程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 模型

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

5. 命名管道

5.1 概念

命名管道(named pipe)又被称为先进先出队列(FIFO),是一种特殊的管道,存在于文件系统中。命名管道与管道非常类似,但是又有自身的显著特征:

  • 命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程。
  • 命名管道作为一种特殊的文件存放在文件系统中,而不是像匿名管道那样存放在内核中。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会自行消失。

5.2 系统调用

选项mkdifo()系统调用
声明int mkfifo(const char *pathname, mode_t mode);
头文件<sys/types.h>、 <sys/stat.h>
作用创建一个命名管道
返回值成功返回0,失败返回-1
选项unlink()系统调用
声明int unlink(const char *pathname);
头文件<unistd.h>
作用删除一个指向这个文件名的文件
返回值成功返回0,失败返回-1

5.3 不同进程间通信

  • common.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <strings.h>

const std::string path_name = "./fifo";
#define MODE 0664
  • client.cc
#include "common.hpp"

int main()
{
    int open_fd = open(path_name.c_str(), O_WRONLY);
    assert(open_fd != -1);
    while(true){
        char buffer[1024];
        if(strcasecmp(buffer, "quit") == 0){
            break;
        }
        std::cout << "请输入:>";
        char* message = fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';
        assert(message != nullptr);
        (void)message;
        write(open_fd, buffer, strlen(buffer));
    }
    close(open_fd);

    return 0;
}
  • serve.cc
#include "common.hpp"

int main()
{
    //创建命名管道
    umask(0);
    int mkfifo_ret = mkfifo(path_name.c_str(), MODE);
    assert(mkfifo_ret != -1);
    //打开并读取
    int open_fd = open(path_name.c_str(), O_RDONLY);
    assert(open_fd != -1);
    
    while(true){
        char buffer[1024];
        ssize_t size = read(open_fd, buffer, sizeof(buffer) - 1);
        if(size > 0){
            buffer[size] = '\0';
            std::cout << buffer << std::endl;
        }else{
            break;
        }
    }

    close(open_fd);
    //关闭命名管道
    int unlink_ret = unlink(path_name.c_str());
    assert(unlink_ret != -1);
    
    return 0;
}

5.4 命名管道实现进程池

  • control_center.cc
#include "namepipe_pool.hpp"

void create_namepipe()
{
    int fifo_net_request_ret = mkfifo(fifo_net_request.c_str(), MODE);
    assert(fifo_net_request_ret != -1);
    int fifo_dispose_data_ret = mkfifo(fifo_dispose_data.c_str(), MODE);
    assert(fifo_dispose_data_ret != -1);
    int fifo_manage_system_ret = mkfifo(fifo_manage_system.c_str(), MODE);
    assert(fifo_manage_system_ret != -1);
}

Manage_fifo manage_namepipe()
{
    std::cout << "打开fifo_net_request......" << std::endl;
    sleep(1);
    int fifo_net_request_fd = open(fifo_net_request.c_str(), O_RDWR);
    assert(fifo_net_request_fd != -1);
    std::cout << "打开fifo_dispose_data......." << std::endl;
    sleep(1);
    int fifo_dispose_data_fd = open(fifo_dispose_data.c_str(), O_RDWR);
    assert(fifo_dispose_data_fd != -1);
    std::cout << "打开fifo_manage_system........" << std::endl;
    sleep(1);
    int fifo_manage_system_fd = open(fifo_manage_system.c_str(), O_RDWR);
    assert(fifo_manage_system_fd != -1);
    Manage_fifo center(   fifo(fifo_net_request_fd, fifo_net_request)
                        , fifo(fifo_dispose_data_fd,fifo_dispose_data)
                        , fifo(fifo_manage_system_fd,fifo_manage_system));
    std::cout << "命名管道全部打开!" << std::endl;
    return center;
}

void task_menu()
{
    std::cout << "----------------------------------------------" << std::endl;
    std::cout << "-- 1.网络请求                     2.数据处理 --" << std::endl;
    std::cout << "-- 3.系统管理                     4.退出中控 --" << std::endl;
    std::cout << "----------------------------------------------" << std::endl;
}

void write_fifo(int& command, Manage_fifo& center)
{
    int buf = command;
    for(int i = 0; i < PROCESS_NUM; ++i){
        if(command - 1 == i){
            write(center._fifos[i]._fifo_fd, &buf, sizeof(buf));
            break;
        }
    }
}

void close_fifo(Manage_fifo& center)
{
    for(int i = 0; i < PROCESS_NUM; ++i){
        close(center._fifos[i]._fifo_fd);
        unlink(center._fifos[i]._fifo_name.c_str());
    }
}

//主控进程管理命令管道写入->任务进程读取执行
int main()
{
    //1. 创建管道文件
    create_namepipe(); 
    //2. 打开文件做管理
    Manage_fifo center;
    try
    {
        center = manage_namepipe(); //TODO(拷贝构造)
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    
    std::cout << "开始执行任务" << std::endl;
    //3. 通过选择执行任务
    while(true)
    {
        //1. 选择执行任务
        // > 列出菜单
        task_menu();
        // > 输入
        int command = 0;
        std::cout << "请求输入#";
        std::cin >> command;
        if(command == 4){
            break;
        }
        if(command < 1 || command > 4){
            std::cout << "输入错误,请重新输入!";
            continue;
        }
        //2. 执行选择写入对应进程
        write_fifo(command, center);
        sleep(1);
    }
    //3. 关闭所有命名管道
    close_fifo(center);
    (void)center;
    return 0;
}
  • namepipe_pool.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <assert.h>

//管道名
std::string fifo_net_request = "./fifo_net_request";
std::string fifo_dispose_data = "./fifo_dispose_data";
std::string fifo_manage_system = "./fifo_manage_system";
#define MODE 0664
#define FIFO_NET_REQUEST 1
#define FIFO_DISPOSE_DATA 2
#define FIFO_MANAGE_SYSTEM 3
#define PROCESS_NUM 3


class fifo
{
public:
    fifo(const int& fd, std::string& name)
        :_fifo_fd(fd)
        ,_fifo_name(name)
    {}
    fifo(const fifo& x)
    {
        _fifo_fd = x._fifo_fd;
        _fifo_name = x._fifo_name;
    }
    ~fifo(){}
public:
    int _fifo_fd;
    std::string _fifo_name;
};

class Manage_fifo
{
public:
    Manage_fifo(){}

    Manage_fifo(const fifo fd1, const fifo fd2, const fifo fd3)
    {
        _fifos.push_back(fd1);
        _fifos.push_back(fd2);
        _fifos.push_back(fd3);
    }
    // Manage_fifo(const Manage_fifo& center)
    // {
        
    // }

    ~Manage_fifo(){}
public:
    std::vector<fifo> _fifos;
};
  • net_request.cc
#include "namepipe_pool.hpp"

int main()
{
    int fd = open(fifo_net_request.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    ssize_t size = read(fd, &buf, sizeof(buf));
    assert(size > 0);
    if(buf == FIFO_NET_REQUEST){
        int cnt = 5;
        while(cnt > 0)
        {
            printf("网络正在请求中..............(请等待<%d>秒钟)\n", cnt--);
            sleep(1);
        }
    }    
    if(size == -1){
        std::cout << "net_request进程读取错误";
        exit(0);
    }
    return 0;
}
  • dispose_data.cc
#include "namepipe_pool.hpp"

int main()
{
    int fd = open(fifo_dispose_data.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    ssize_t size = read(fd, &buf, sizeof(buf));
    assert(size >= 0);
    if(buf == FIFO_DISPOSE_DATA){
        int cnt = 5;
        while(cnt > 0)
        {
            printf("数据库正在处理数据中..............(请等待<%d>秒钟)\n", cnt--);
            sleep(1);
        }
    }    
    if(size == -1){
        std::cout << "dispose_data进程读取错误";
        exit(0);
    }
    return 0;
}
  • manage_system.cc
#include "namepipe_pool.hpp"

int main()
{
    int fd = open(fifo_manage_system.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    while(true)
    {
        ssize_t size = read(fd, &buf, sizeof(buf));
        assert(size >= 0);
        if(buf == FIFO_MANAGE_SYSTEM){
            int cnt = 5;
            while(cnt > 0)
            {
                printf("系统正在管理各个子系统中..............(请等待<%d>秒钟)\n", cnt--);
                sleep(1);
            }
            break;
        } 
        if(size == -1){
            std::cout << "manage_system进程读取错误";
            exit(0);
        } 
    }
      
    
    return 0;
}
  • makefile
.PHONY:all
all:control_center manage_system net_request dispose_data

control_center:control_center.cc
	g++ -o $@ $^ -std=c++11 -g
manage_system:manage_system.cc
	g++ -o $@ $^ -std=c++11
net_request:net_request.cc
	g++ -o $@ $^ -std=c++11 -g
dispose_data:dispose_data.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f control_center manage_system net_request dispose_data 
	rm -f fifo_*
	

6. 共享内存

6.1 概念

共享内存是System V版本的最后一个进程间通信方式,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

注意:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

6.2 原理

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

6.3 共享内存数据结构

共享内存可能被多个进程使用会有多份共享内存,那么OS就需要对共享内存做管理,就需要对应的描述(结构体)来组织进行管理

ipcrm -m [shmid]struct shmid_ds {
	struct ipc_perm shm_perm; /* operation perms */
	int shm_segsz; /* size of segment (bytes) */
	__kernel_time_t shm_atime; /* last attach time */
	__kernel_time_t shm_dtime; /* last detach time */
	__kernel_time_t shm_ctime; /* last change time */
	__kernel_ipc_pid_t shm_cpid; /* pid of creator */
	__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
	unsigned short shm_nattch; /* no. of current attaches */
	unsigned short shm_unused; /* compatibility */
	void *shm_unused2; /* ditto - used by DIPC */
	void *shm_unused3; /* unused */
};

6.4 认识接口

  • 命令
    1. ipcs -m :显示共享内存段
    2. ipcrm -m [shmid] : 删除共享内存段
选项shmget()系统调用
声明int shmget(key_t key, size_t size, int shmflg);
头文件<sys/ipc.h> <sys/shm.h>
作用分配System V共享内存段
返回值成功返回标识符,失败返回-1
选项ftok()系统调用
声明key_t ftok(const char *pathname, int proj_id);
头文件<sys/types.h> <sys/ipc.h>
作用ftok()函数使用给定路径名(必须指向一个现有的、可访问的文件)和最低有效值8个比特位的文件的标识符位的proj_id(必须非零)来生成一个key_t类型的System V IPC密钥
返回值成功返回生成的key值,失败返回-1
选项shmctl()系统调用
声明int shmctl(int shmid, int cmd, struct shmid_ds *buf);
头文件<sys/ipc.h> <sys/shm.h>
作用控制共享内存
返回值失败返回-1
选项shmdt()系统调用
声明int shmdt(const void *shmaddr);
头文件<sys/types.h> <sys/shm.h>
作用去掉进程和该共享内存的关联
返回值成功返回0,失败返回-1
选项shmat()系统调用
声明void *shmat(int shmid, const void *shmaddr, int shmflg);
头文件<sys/types.h> <sys/shm.h>
作用进程和该共享内存关联
返回值成功返回共享内存起始虚拟地址,失败返回-1

6.5 代码

  • share_memory.hpp
#ifndef __SHARE_MEMORY_HPP__
#define __SHARE_MEMORY_HPP__

#include <iostream>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <cstring>
#include <string>
#include <cstdio>
#include <sys/stat.h>
#include <sys/shm.h>
#include <unistd.h>


#define PATH_NAME "."
#define PRO_ID 0xff
#define MODE 0664

const int size = 4096;

key_t get_key()
{
    key_t key = ftok(PATH_NAME, PRO_ID);
    if(key == -1){
        printf("errno:%d, error:%s\n", errno, strerror(errno));
        exit(1);
    }
    return key;
}

std::string to_16Hex(int x)
{
    char buf[64];
    snprintf(buf, sizeof(buf), "0x%x", x);
    return buf;
}

int select_shm(key_t& key, const int& size, int flags)
{
    //创建shm
    int shm_id = shmget(key, size, flags);
    if(shm_id == -1){
        printf("errno:%d, error:%s\n", errno, strerror(errno));
        exit(2);
    }
    return shm_id;
}

int create_shm(key_t& key, const int& size)
{
    umask(0);
    return select_shm(key, size, IPC_CREAT | IPC_EXCL | MODE);
}

int get_shm(key_t& key, const int& size)
{
    return select_shm(key, size, IPC_CREAT);
}

void delete_shm(int& shm_id)
{
    int ret = shmctl(shm_id, IPC_RMID, nullptr);
    assert(ret != -1);
}

char* attach_shm(int shm_id)
{
    char* start = (char*)shmat(shm_id, nullptr, 0);
    assert(start != nullptr);
    printf("该进程%d成功和shm:%d关联!\n", getpid(), shm_id);
    return start;
}

void nattach_shm(char* start, int& shm_id)
{
    int ret = shmdt(start);
    assert(ret != -1);
    printf("该进程%d成功和shm:%d去关联!\n", getpid(), shm_id);
}


#define SERVE 1
#define CLIENT 2

class share_memory
{
public:
    share_memory(int id)
        :_identity(id)
        ,_start(nullptr)
        ,_shm_id(-1)
    {
        key_t key =  get_key();
        if(_identity == SERVE){
            _shm_id = create_shm(key, size);
        }else{
            _shm_id = get_shm(key, size);
        }
        _start = attach_shm(_shm_id);
    }
    char* get_address()
    {
        return _start;
    }
    ~share_memory()
    {
        nattach_shm(_start, _shm_id);
        if(_identity == SERVE){
            delete_shm(_shm_id);
        }
    }
private:
    char* _start;
    int _identity;
    int _shm_id;
};

#endif

  • serve.cc
#include "share_memory.hpp"

//服务端读 -- 客户端写
int main()
{
    share_memory serve(SERVE);
    char* start = serve.get_address();

    
    int cnt = 0;
    while(cnt <= 26)
    {
        std::cout << "client -> serve#" << start << std::endl;
        sleep(1);
        ++cnt;
    }

    return 0;
}
  • client.cc
#include "share_memory.hpp"

int main()
{
    share_memory client(CLIENT);
    char* start = client.get_address();

    char c = 'A';
    while(c <= 'Z')
    {
        start[c - 'A'] = c;
        ++c;
        start[c - 'A'] = '\0';
        sleep(1);
    }
    
    return 0;
}
  • makefile
.PHONY:all
all: client serve

client:client.cc
	g++ -o $@ $^ -std=c++11
serve:serve.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f client serve

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414649.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32+W5500实现以太网通信

STM32系列32位微控制器基于Arm Cortex-M处理器&#xff0c;旨在为MCU用户提供新的开发自由度。它包括一系列产品&#xff0c;集高性能、实时功能、数字信号处理、低功耗/低电压操作、连接性等特性于一身&#xff0c;同时还保持了集成度高和易于开发的特点。本例采用STM32作为MC…

【开懂C++】命名空间 函数重载 缺省参数

目录一.命名空间二.缺省参数三.函数重载一.命名空间 在编写C语言代码时&#xff0c;当工程较大时&#xff0c;很容易产生变量命名冲突的情况——一般有两种冲突的情况 1.变量名与库中的函数名、关键字冲突。2.工程模块化搭建时不同文件的命名冲突。 而C为了优化这一缺陷&#…

安装Ubuntu系统后的实用工具配置指南

1. 修改软件源 Ubuntu 默认的软件源是境外的&#xff0c;速度上会有些问题&#xff0c;我们可以在Software & Updates(软件和更新)中选择国内的镜像。 一般我们选择清华源或者阿里云源。 2. 安装chorme浏览器 在ubuntu下我比较习惯用火狐浏览器和谷歌浏览器。 谷歌浏览…

vue 自定义指令directive的使用场景

1. 一个指令定义对象可以提供如下几个钩子函数(均为可选) bind:只调用一次&#xff0c;指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用(仅保证父节点存在&#xff0c;但不一定已被插入文档中)。update:只要当前元素不被…

Leetcode.1971 寻找图中是否存在路径

题目链接 Leetcode.1971 寻找图中是否存在路径 easy 题目描述 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi]表示顶点 ui和顶…

关于maxwell

这里写目录标题什么是Maxwell如何使用MaxwellMaxwell是一个mysql二进制binlog日志分析工具&#xff0c;Java语言编写&#xff0c;功能十分强大&#xff0c;可以将日志转换成json并发送到kafka&#xff0c;redis&#xff0c;rabbitmq等中间组件&#xff0c;因为最近在理解怎样在…

QtSqlite加密--QtCipherSqlitePlugin的使用

文章目录QtSqlite加密第一步&#xff1a;环境准备第二步&#xff1a;连接数据库第三步&#xff1a;数据库操作第四步&#xff1a;使用新的可视化工具查看数据库数据QtSqlite加密 上次说了QxOrm的数据库连接、映射和基础的增删改查&#xff0c;但是我们在使用数据库的时候并不希…

期刊论文图片代码复现【由图片还原代码】(OriginMatlab)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

【数据结构】图解八大排序(上)

文章目录一、排序简介二、直接插入排序三、希尔排序四、直接选择排序五、堆排序六、冒泡排序七、冒泡排序与直接插入排序效率对比一、排序简介 生活中&#xff0c;我们经常能看到排序的应用。例如&#xff0c;我们在网购商品的时候&#xff0c;经常按销量从高到低排序。 那么这…

Linux服务器怎么分区

Linux服务器怎么分区 我是艾西&#xff0c;linux系统除了从业某个行业经常要用到的程序员比较熟悉&#xff0c;对于小白或只会用Windows系统的小伙伴还是会比较难上手的。今天艾西简单的跟大家聊聊linux系统怎么分区&#xff0c;让身为小白的你也能一眼看懂直接上手操作感受程序…

【数据结构】用Java实现七大排序算法

目录 &#x1f337;1. 排序的概念及引用 1.1 排序的概念 1.2 衡量指标 1.2 十个排序算法 1.3 十个排序性能对比 &#x1f337;2. 冒泡排序 2.1 算法描述 2.2 动图 ⭐️代码优化 &#x1f337;3. 选择排序 3.1 算法描述 3.2 动图 3.3 代码 &#x1f337;4. 插入排序 4.1 算法描述…

(大数据开发随笔9)Hadoop 3.3.x分布式环境部署——全分布式模式

索引完全分布式模式守护进程布局集群搭建准备总纲配置文件格式化集群启动集群集群控制命令集群启停进程查看启动日志查看集群常见问题案例演示&#xff1a;WordCount完全分布式模式 分布式文件系统中&#xff0c;HDFS相关的守护进程也分布在不同的机器上&#xff0c;如&#x…

cgroups是linux内核中限制、记录、隔离进程组(process groups)所使用的物理资源的机制

容器虚拟化 可以实现应用程序的隔离 直接使用物理机的操作系统可以快速响应用户请求 不占用部署时间 占用少量磁盘空间 缺点∶学习成本增加、操作控制麻烦、网络控制与主机虚拟化有所区别、服务治理难。 微服务架构师需要会多门编程语言&#xff0c;才能治理各种服务 三种…

web路径专题+会话技术

目录自定义快捷键1. 工程路径问题及解决方案1.1 相对路径1.2 相对路径缺点1.3 base标签1.4 作业11.5 作业21.6注意细节1.7 重定向作业1.8 web工程路径优化2. Cookie技术2.1 Cookie简单示意图2.2 Cookie常用方法2.2 Cookie创建2.3 Cookie读取2.3.1 JSESSIONID2.3.2 读取指定Cook…

Linux文件目录操作命令

目录 Linux常用的基础命令 使用技巧 1. ls命令&#xff1a;查看当前目录所有内容 ls 命令的多种使用方法&#xff1a; 注&#xff1a;假如执行乱码&#xff0c;则执行以下两步的代码&#xff1a; 2. cd命令&#xff1a;切换当前工作目录&#xff0c;即进入指定目录 3. …

网络-IP地址(嵌入式学习)

IP地址基本概念IPv4 五类&#xff1a;A B C D E特殊地址子网掩码子网号概念IPv6优势举个栗子基本概念 IP地址是Internet中主机的标识 IP地址&#xff08;Internet Protocol Address 互联网国际地址&#xff09;是一种在Internet上的给主机编址的方式&#xff0c;它主要是为互…

Java Web 开发技术的演进:从 Servlet、Spring MVC 到 WebFlux 及其竞品分析

前言 随着互联网技术的快速发展&#xff0c;Web 应用程序在处理海量用户访问和大数据时面临着巨大的挑战。在这个过程中&#xff0c;Java Web 开发技术经历了从 Servlet 到 Spring MVC 再到 WebFlux 的演变。在这篇文章中&#xff0c;我们将探讨这三个技术的发展历程、痛点及解…

Go的IO -- Go语言设计与实现

Go合IO的不解之缘 协程是Go的很大的一个优势。Go天然支持高并发&#xff0c;那么我们来研究一下这个高并发的秘诀在哪里&#xff1f; 执行体调度得当。CPU 不停的在不同的执行体&#xff08; Goroutine &#xff09;之间反复横跳&#xff01;CPU 一直在装填和运行不同执行体的…

数字化坚鹏:金融数据治理、数据安全政策解读及银行数字化转型

金融数据治理、数据安全政策解读及银行数字化转型课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不知道如何准确理解金融数据治理及数据安全相关政策 不清楚金融数据治理及数据安全相关政策对银行有什么影响&#xff1f; 不清楚如何进行银行数字化转型&#xff1f…

Azure DevOps Pipelines

Azure DevOps主要通过管理代码、管理服务器、管理发布的管道来实现一体化解决方案 发布流程&#xff1a; 1、代码上传Repos仓储 略 2、DevOps连接并管理发布服务器 2.1、Deployment Groups配置 2.2、服务器执行连接指令 2.3、服务器状态查看 3、创建 Pipline(构建代码) 3.1…