Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列

news2024/12/23 7:34:30

进程间通信 System V系列: 共享内存,初识信号量

  • 一.共享内存
    • 1.引入
    • 2.原理
    • 3.系统调用接口
      • 1.shmget
      • 2.shmat和shmdt
      • 3.shmctl
    • 4.边写代码边了解共享内存的特性
      • 1.ftok形成key,shmget创建与获取共享内存
      • 2.shm相关指令
      • 3.shmat和shmdt挂接和取消挂接
      • 4.shmctl获取共享内存信息,释放共享内存
      • 5.开始通信
    • 5.利用管道实现共享内存的协同机制
      • 1.Sync(同步类)
      • 2.读写端的修改
      • 3.动图演示
    • 6.共享内存的优缺点
  • 二.消息队列
    • 1.概念
    • 2.接口,数据结构等等
  • 三.信号量理论
    • 1.信号量的原理
    • 2.信号量的理论
      • 1.从生活中的例子理解信号量
      • 2.进程角度的信号量
      • 3.信号量的细节
        • 1.信号量必须要由OS提供并维护
        • 2.信号量的基本操作
    • 3.信号量的接口
      • 1.semget
      • 2.semctl
      • 3.semop
  • 四.System V系列的进程间通信的小总结
  • 五.利用信号量实现共享内存的协同机制
    • 1.思路
    • 2.Server创建并获取信号量,Client获取信号量 -> ftok和semget
      • 1.ftok
      • 2.shmget
    • 3.Server阻塞申请信号量资源 - semop
    • 4.Client初始化信号量资源 - semctl
    • 5.Server释放信号量资源 - semctl
    • 6.完整代码
      • 1.Common.hpp
      • 2.sem.hpp
      • 3.ShmServer.cpp
      • 4.ShmClient.cpp
    • 7.演示

我们不是都有管道了吗?为什么还要有其他的进程间通信方式呢?
当时的年代,通信技术是一个非常火的点,就像现在人工智能和各种大模型一样,类似于百家争鸣的样子,所以有很多进程间通信的方式

因为共享内存跟我们学的进程地址空间有密切联系,所以我们重点学习
而信号量我们就先认识一下,学习一下理论即可

一.共享内存

1.引入

管道方便是方便,直接复用文件接口即可,但是想要使用管道是需要访问内核的,而且管道的内核级缓冲区也是在内核当中的,因此会导致效率不是特别好(因为访问内核本身就是一个比较大的消耗)

那么有没有什么办法能够让两个进程无需访问内核就能进行进程间通信呢?
在这里插入图片描述

2.原理

跟命名管道一样,共享内存也是允许完全无关的两个进程商量一下一起使用同一份资源,从而实现进程间通信的
在这里插入图片描述
看似很好懂,但是有几个值得关注的点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.系统调用接口

shm就是shared_memory:共享内存
ipc: InterProcess Communication:进程间通信

1.shmget

在这里插入图片描述
第三个参数为什么要这么设计呢?
我们一起来分析一下
在这里插入图片描述
我们刚才还没有说返回值
在这里插入图片描述

2.shmat和shmdt

分配完一个共享内存了,下面要做的事情就是把共享内存映射到进程的进程地址空间当中,并用页表建立该映射

shmat:shmattach是负责建立映射的,也就是将共享内存和进程挂接起来
shmdt:shmdetach(detach是分离,拆卸的意思),也就是取消该共享内存跟进程的挂接关系
在这里插入图片描述
shmdt直接传入shmat的返回值即可
在这里插入图片描述
shmat:如果挂接失败,返回(void*)-1

3.shmctl

在这里插入图片描述

4.边写代码边了解共享内存的特性

1.ftok形成key,shmget创建与获取共享内存

在这里插入图片描述
在这里插入图片描述
下面我们应该是要使用shmat和shmdt了,不过在此之前,我们还要介绍几个指令

2.shm相关指令

在这里插入图片描述
如何释放呢?
可以通过shmctl系统调用接口来释放,也可以通过指令来释放
我们先介绍指令释放
在这里插入图片描述
这里的key显示的是16进制,我们刚才打印的是10进制
因此我们改一下代码,让它以16进制打印
在这里插入图片描述

3.shmat和shmdt挂接和取消挂接

在这里插入图片描述
在这里插入图片描述

while :;do ipcs -m;sleep 1;done
这里反过滤掉了root创建的共享内存
while :;do ipcs -m | grep -v root;sleep 1;done

在这里插入图片描述
我们看到了挂接和取消挂接的全过程

4.shmctl获取共享内存信息,释放共享内存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们实现了共享内存的创建/获取,挂接,取消挂接和释放
下面是时候开始让这两个进程开始通信了
在挂接之后,取消挂接之前开始通信

5.开始通信

我们刚才获取信息只是为了告诉大家这个函数有这么个功能而已,因此我们就不调用这个获取信息的函数了哦
在这里插入图片描述
在这里插入图片描述
通信成功
在这里插入图片描述
那么没有协同机制怎么办?
一个很好的方法是借助信号量来解决这一问题,但是因为信号量的接口太麻烦(比共享内存的这些接口还要麻烦很多),因此我们以后详细介绍信号量的时候再去使用信号量的接口

要不然是像我刚才那样通信双方约定好一个暗号,读端读到暗号时意味着通信结束
而是那样只能解决一部分情况下保证读端读取完所有的写端数据时才退出
还是无法解决写端还没写入你读端就开始读了啊

我们可以利用天然具有协同机制的管道啊!!
又因为我们这两个进程是没有血缘关系的,因此我们用一下命名管道吧
这里直接把我们之前写的管理命名管道的代码拿过来

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <string>
using namespace std;
const char* path="./namedpipe";
#define MODE 0666
class Fifo
{
public:
    Fifo(const char* path):_path(path)//用成员变量保存路径
    {
        int ret=mkfifo(_path.c_str(),MODE);
        if(ret==-1)//说明创建失败
        {
            cerr<<"create namedpipe fail, errno: "<<errno<<" , strerror: "<<strerror(errno)<<endl;
        }
        else
        {
            cout<<"create namedpipe succeed"<<endl;
        }
    }
    ~Fifo()
    {
        unlink(_path.c_str());
    }
private:
    string _path;
};

5.利用管道实现共享内存的协同机制

1.Sync(同步类)

在这里插入图片描述
在这里插入图片描述

2.读写端的修改

在这里插入图片描述
在这里插入图片描述

3.动图演示

在这里插入图片描述
成功解决了写端没写数据,读端还读的问题

6.共享内存的优缺点

在这里插入图片描述
下面我们趁热打铁快速了解一下消息队列

二.消息队列

1.概念

在这里插入图片描述
消息队列的生命周期也是随内核的,跟共享内存一样

2.接口,数据结构等等

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三.信号量理论

1.信号量的原理

这里介绍信号量的原理,
一方面是为了让我们更好地理解信号量,一方面是先提出一些多线程当中的概念

在这里插入图片描述

2.信号量的理论

1.从生活中的例子理解信号量

在这里插入图片描述

2.进程角度的信号量

在这里插入图片描述

3.信号量的细节

1.信号量必须要由OS提供并维护

在这里插入图片描述

2.信号量的基本操作

在这里插入图片描述

3.信号量的接口

1.semget

在这里插入图片描述

2.semctl

在这里插入图片描述

3.semop

在这里插入图片描述
关于信号量的更多话题我们等到多线程的时候还会再说明的

四.System V系列的进程间通信的小总结

共享内存,消息队列和信号量的很多接口都是相同的,它们的内核数据结构当中也都有一个一样的结构体:ipc_perm,
它们都是主要应用于本地通信的,因此在目前的网络时代当中并不常用(用的更多的还是网络通信)

它们都属于System V系列的进程间通信,OS为了管理它们搞了一个
ipc_ids结构体,ipc_id_ary结构体,kern_ipc_perm结构体实现了ipc资源的动态申请和释放,并将对ipc资源的管理转换为了对kern_ipc_perm的增删查改和对ipc_id_ary的动态扩容
在这里插入图片描述
在这里插入图片描述
不过因为System V系列的进程间通信的结构和数据结构都是独立设计的,跟文件是完全解耦的,因此不符合Linux一切皆文件的设计思想,这也是System V系列的进程间通信并不热门的原因

如果OS能够在struct file当中封装一个ipc_perm的指针,把kern_ipc_perm关联起来,并利用文件接口封装ipc资源使用的接口,就能让System V系列的进程间通信符合一切皆文件

那样的话使用起来肯定也就更容易,肯定就能热门了

五.利用信号量实现共享内存的协同机制

本来想写完前4点就结束吧,不过心血来潮想用一下信号量,下面我们一起来用一下信号量吧

1.思路

依旧是回归我们之前需要利用管道实现共享内存的协同机制的时候
我们的目的是让读端一开始阻塞等待,等到写端准备要进行写入的时候告诉读端: 我要开始写啦,你也开始读吧

此时就能够保证读端不会在一开始的时候做无意义的读取操作

大致流程分为:

  1. 读端(Server)创建并获取信号量
  2. Server阻塞申请信号量资源,此时读端就是阻塞等待写端进行写入
  3. Client获取读端创建好的信号量
  4. 写端(Client)准备写入时初始化信号量资源
  5. Server成功申请信号量资源,开始进程间通信
  6. 最后Server释放信号量资源

流程很清晰,
(那为什么我们一开始不用信号量呢? 因为信号量接口太麻烦了…,而且我用管道和信号量来解决这一共享内存的同步机制是为了学习熟悉这些接口)

之前有一些点我没有注意到,写代码的时候屡屡碰壁,最后才搞过来了

2.Server创建并获取信号量,Client获取信号量 -> ftok和semget

1.ftok

在这里插入图片描述

  1. ftok里面传入的路径必须是我们Linux系统中的确存在的路径!!!
  2. 我们申请的共享内存和信号量各自的key是不可以相同的(大家也能够很好的理解,因为key才是ipc资源的唯一标识嘛)

我们就用这个产生sem的key
在这里插入图片描述
用这个产生shm的key
在这里插入图片描述

2.shmget

在这里插入图片描述
我们共享内存的资源就是一个整体,因此nsems传入1
然后跟共享内存一样,Server传IPC_CREAT | IPC_EXCL | 0666, Client传入IPC_CREAT即可
在这里插入图片描述
Server:
在这里插入图片描述
Client:
在这里插入图片描述

3.Server阻塞申请信号量资源 - semop

在这里插入图片描述
当sem_op<0即需要申请信号量时,如果信号量==0,那么该进程就会阻塞,等待信号量>0
而信号量在还没有被进程设置之前默认值是0,因此我们可以这样来玩
(注意:

  1. semget时传入的nsems时你申请的这个信号量集当中的信号量数目,而不是信号量的初始值!!
  2. 初始值需要进程显式传入,而且默认值是0[我就是因为这点屡屡碰壁]
  3. sembuf是本来就有的,不需要我们显式提供[我就是因为这点屡屡碰壁]
    )
    Server不设置信号量,在读取之前申请信号量资源阻塞等待写端进行写入(我们起名为lock函数)
    Client即将进行写入之前初始化该信号量为1(我们起名为Unlock函数),此时Server等待成功,退出阻塞状态,开始进行读取操作
    在这里插入图片描述

4.Client初始化信号量资源 - semctl

在这里插入图片描述
在这里插入图片描述

5.Server释放信号量资源 - semctl

在这里插入图片描述

6.完整代码

1.Common.hpp

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;

const char* shm_path="/home/wzs/ubuntucode/process_ipc/semaphore";
const int shm_id=0x5678;
const int agreeSize=4096;

key_t GetKey(const char* k_path,int proj_id)
{
    key_t ret=ftok(k_path,proj_id);
    if(ret==-1)
    {
        cout<<"ftok fail"<<endl;
        exit(1);
    }
    return ret;
}

class Shm
{
public:
    int GetShmid(int key,int size,int shmflg)
    {
        int shmid=shmget(key,size,shmflg);
        if(shmid==-1)
        {
            cout<<"shmget fail"<<endl;
            exit(1);
        }
        return shmid;
    }

    void* Attach(int shmid)
    {
        void* addr=shmat(shmid,nullptr,0);
        if(addr==(void*)-1)
        {
            cout<<"shmat fail"<<endl;
            exit(1);
        }
        return addr;
    }

    void Detach(void* addr)
    {
        shmdt(addr);
    }

    void DelShm(int shmid)
    {
        shmctl(shmid,IPC_RMID,nullptr);
    }
};

2.sem.hpp

#pragma once
#include <sys/sem.h>
const char* sem_path="/home/wzs/ubuntucode/process_ipc/pipe/namepipe/name_pipepool";
const int sem_id=0x3f45289;

union semun {  
    int val;                /* 用于SETVAL */  
    struct semid_ds *buf;  /* 用于IPC_STAT和IPC_SET */  
    unsigned short *array; /* 用于GETALL和SETALL */  
};

void ChangeCount(sembuf* buf,int val)
{
    buf->sem_num=0;
    buf->sem_op=val;
    buf->sem_flg=SEM_UNDO;
}

class Sem
{
public:
    int GetSemid(key_t key,int nsems,int semflg)
    {
        int semid=semget(key,nsems,semflg);
        if(semid==-1)
        {
            cout<<"semget fail"<<endl;
            exit(1);
        }
        return semid;
    }

    void DelSem(int semid)
    {
        semctl(semid,0,IPC_RMID);
    }

    void Change(int semid,sembuf* sops)
    {
        cout<<"wait sem resource..."<<endl;
        semop(semid,sops,1);
        cout<<"wait success!"<<endl;
    }

    void GetInfo(int semid)
    {
        int val=semctl(semid,0,GETVAL);
        cout<<val<<endl;
    }

    void Init(int semid,semun un)
    {
        semctl(semid,0,SETVAL,un);
    }


    void Unlock(int semid)
    {
        union semun sem_union;
        sem_union.val=1;//将信号量的初始值设置为1,此时相当于开锁,读端可以拿到信号量,开始读取
        Init(semid,sem_union);
    }

    void lock(int semid)
    {
        sembuf buf;
        ChangeCount(&buf,-1);
        Change(semid,&buf);//我想申请信号量,但是信号量默认是0,我需要阻塞等待
    }
};

3.ShmServer.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{
    Shm shm;
    key_t sem_key=GetKey(shm_path,shm_id);//获取Key
    int shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT | IPC_EXCL | 0666);//申请shm
    char* addr=(char*)shm.Attach(shmid);//挂接shm

    //利用二元信号量(锁)
    Sem sem;
    key_t k=GetKey(sem_path,sem_id);

    int semid=sem.GetSemid(k,1,IPC_CREAT | IPC_EXCL | 0666);

    //等待获取锁
    sem.lock(semid);

    cout<<"receive message begin########################################"<<endl;
    //开始读取
    while(true)
    {
        if(addr[0]=='q') break;
        cout<<"this is message:"<<addr<<"。"<<endl;
        sleep(1);
    }
    cout<<"receive message over#########################################"<<endl;
    cout<<"Server will detach shm now..."<<endl;

    shm.Detach(addr);//解除挂接
    shm.DelShm(shmid);//删除shm
    sem.DelSem(semid);//删除sem
    return 0;
}

4.ShmClient.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{
    Shm shm;
    key_t sem_key=GetKey(shm_path,shm_id);
    int shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT);

    Sem sem;
    key_t k=GetKey(sem_path,sem_id);
    int semid=sem.GetSemid(k,1,IPC_CREAT);
    char* addr=(char*)shm.Attach(shmid);
    memset(addr,0,agreeSize);


    cout<<"send message begin########################################"<<endl;
    //开锁
    sem.Unlock(semid);

    for(char c='A';c<='Z';c++)
    {
        addr[c-'A']=c;
        sleep(1);
    }
    addr[0]='q';

    cout<<"send message over########################################"<<endl;
    cout<<"Client will detach shm now..."<<endl;

    shm.Detach(addr);
    return 0;
}

7.演示

在这里插入图片描述

以上就是Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列的全部内容,希望能对大家有所帮助!!

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

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

相关文章

org.hsqldb.jdbcDriver 类,导致 ClassNotFoundException 异常如何解决?

确保JDBC驱动包存在&#xff1a;检查系统是否已经安装了HSQLDB JDBC驱动。如果没有安装或驱动没有正确放置在类路径中&#xff0c;需要下载并添加它。你可以从 HSQLDB官网 下载JDBC驱动包。 添加JDBC驱动到类路径&#xff1a;将下载的HSQLDB JDBC驱动&#xff08;通常是一个JA…

【ArcGISProSDK】condition属性

示例 通过caption属性可以看出esri_mapping_openProjectCondition的条件是一个工程被打开 condition的作用 由此可知示例中的Tab实在工程被打开才能使用&#xff0c;否则他禁用显示灰色&#xff0c;在未禁用的时候说明条件满足。 参考文档 insertCondition 元素 (arcgis.com…

西门子PLC定时器使用与疑难杂症

一、简介 S7-200提供了256个定时器&#xff0c;依据分辨率分三种类型&#xff1a;1ms&#xff0c;10ms和100ms&#xff1b;依据功能分为接通延时定时器&#xff08;TON&#xff09;、有记忆的接通延时定时器&#xff08;TONR)和断开延时定时器&#xff08;TOF)。 接通延时定时…

Promise魔鬼面试题

文章目录 题目解析难点分析分析输出step1step2step3step4step5step6 参考/致谢&#xff1a;渡一袁老师 题目 Promise.resolve().then(() > {console.log(0);return Promise.resolve(4);}).then((res) > {console.log(res);});Promise.resolve().then(() > {console.l…

MFC中关于CMutex类的学习

MFC中关于CMutex类的学习 最近在项目中要实现两个线程之间的同步&#xff0c;MFC中提供了4个类&#xff0c;分别是CMutex(互斥量)、CCriticalSection(临界区)、CEvent(事件对象)、CSemaphore(信号量)。有关这4个类的说明&#xff0c;大家可以参考微软官方文档&#xff1a; CM…

MySQL的表级锁

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 表级锁 介绍 对于表锁&#xff0c;分为两类&#xff1a; 表共享读锁表独占写锁 语法 1. 加锁&#xff1a;lock tables 表名... read/write 2.…

第十三届蓝桥杯决赛(国赛)真题 Java A 组【原卷】

文章目录 发现宝藏【考生须知】试题 A: 火柴棒数字试题 B: 小蓝与钥匙试题 C: 内存空间试题 D: 斐波那契数组试题 E: 交通信号试题 F: 数组个数试题 G: 六六大顺试题 H : \mathrm{H}: H: 选素数试题 I: 图书借阅试题 J \mathrm{J} J : 括号序列树 发现宝藏 前些天发现了一个…

代码随想录——二叉树的层序遍历Ⅱ(Leetcode107)

题目链接 层序遍历&#xff08;队列&#xff09; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, Tre…

springboot整合redis多数据源(附带RedisUtil)

单数据源RedisUtil(静态) 单数据源RedisUtil,我这里implements ApplicationContextAware在setApplicationContext注入redisTemplate,工具类可以直接类RedisUtil.StringOps.get()使用 package com.vehicle.manager.core.util;import com.alibaba.fastjson.JSON; import lombok.e…

嵌入式学习70-复习(wireshark使用和http协议)

--------------------------------------------------------------------------------------------------------------------------------- wireshark 1.sudo wireshark 2.选择 any &#xff0c; 3.搜索 http/tcp 54 为 发送的数据包 58 回复的数据包 请求报文 请求报文…

【C++】CentOS环境搭建-安装log4cplus日志组件包及报错解决方案

log4cplus简介 log4cplus是C编写的开源的日志系统&#xff0c;前身是java编写的log4j系统&#xff0c;受Apache Software License保护&#xff0c;作者是Tad E. Smith。 log4cplus具有线程安全、灵活、以及多粒度控制的特点&#xff0c;通过将日志划分优先级使其可以面向程序…

IQOO Neo7/7SE/PAD2解BL+完美root权限+LSPosed框架-可虚拟定位

QOO Neo7/Neo7SE/Pad/Pad2搭配的是天玑8200系列芯片&#xff0c;继810/920以后再次支持解锁BL&#xff0c;这给我们的玩机带来了很多可能。解锁BL有什么用途呢&#xff1f;最常见的就是获取root权限&#xff0c;刷入各种各种的magisk模块&#xff0c;使用自己喜欢的插件等&…

【初阶数据结构】顺序表OJ题讲解

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录与初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

macOS Sonoma 无法打开分段式Dmg文件的解决办法

在macOS Sonoma 14.X及更高版本的系统中&#xff0c;用户可能会遇到一个棘手的问题&#xff1a;无法直接打开“分段式”DMG&#xff08;磁盘映像&#xff09;安装包文件。这种情况通常发生在尝试安装一些大型软件或游戏时&#xff0c;尤其是那些因为文件体积巨大而采用分段压缩…

(Java)心得:LeetCode——15.三数之和

一、原题 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。…

带有-i选项的sed命令在Linux上执行成功,但在MacOS上失败了

问题&#xff1a; 我已经成功地使用以下 sed 命令在Linux中搜索/替换文本&#xff1a; sed -i s/old_string/new_string/g /path/to/file然而&#xff0c;当我在Mac OS X上尝试时&#xff0c;我得到&#xff1a; command i expects \ followed by text我以为我的Mac运行的是…

Reactor Netty UDP 客户器端-响应式编程-017

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace The Nex…

Jenkins流水线部署Maven项目

使用Jenkins的流水线功能&#xff0c;构建部署Java Maven项目&#xff0c;步骤很简单但是不少细节需要注意。 一、安装 Jenkins的安装步骤和流程就不具体描述&#xff0c;这里主要介绍一下安装时要注意的几个问题。 1、Jenkins尽量安装最新的几个版本&#xff0c;否则安装完成…

GO语言核心30讲 实战与应用 (第二部分)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、sync.WaitGroup和sync.Once 1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。 2. WaitGroup类型有三个指针方法&#xff1a;Wait、Add和Done&#xff0c;以及内部有一个计数器。 (1) Wa…

从零开始搭建Ubuntu CTF-pwn环境

下面就将介绍如何从零搭建一个CTF-pwn环境&#xff08;由于学习仍在进行&#xff0c;故一些环境如远程执行环境还没有搭建的经历&#xff0c;如今后需要搭建&#xff0c;会在最后进行补充&#xff09; 可以在ubuntu官方网站上下载最新的长期支持版本:(我下载的是22.04版本) h…