Linux--进程间通信(system V共享内存)

目录

1.原理部分

 2.系统调用接口

参数说明

返回值

1. 函数原型

2. 参数说明

3. 返回值

4. 原理

5. 注意事项

3.使用一下shmget(一段代码) 

4.一个案例(一段代码)

1.简单封装一下

2.使用共享内存

 2.1挂接(shmat)和取消挂接(shmdt)

2.2重新封装

2.3使用共享内存进行通信

 5.共享内存的优劣

优点:

缺点:


1.原理部分

以shmget系统调用为例:

  1. 创建共享内存区域
    • 使用shmget系统调用来请求创建一块共享内存区域。这个函数会分配一块指定大小的内存,并返回一个标识符(shmid),用于后续对这块共享内存的引用。
    • 在创建过程中,System V共享内存会在内核中创建一个特殊的数据结构(如shmid_kernel)来描述这块共享内存,并在特殊文件系统shm中创建一个与该共享内存关联的文件。
  2. 映射共享内存到进程地址空间
    • 通过shmat系统调用,进程可以将之前创建的共享内存区域映射到自己的虚拟地址空间中。这样,进程就可以通过指针直接访问这块共享内存,而无需通过内核进行数据的拷贝。
    • shmat调用会返回一个指向共享内存区域的指针,进程可以使用这个指针来读写共享内存中的数据。
  3. 进程间通信
    • 当多个进程都映射了同一块共享内存到各自的地址空间后,它们就可以通过读写这块共享内存来进行通信了。
    • 进程A可以将数据写入共享内存,然后进程B可以从共享内存中读取这些数据,从而实现进程间的数据交换。
  4. 解除映射和删除共享内存
    • 当进程不再需要访问共享内存时,可以使用shmdt系统调用来解除映射,即将共享内存从进程的地址空间中移除。
    • 当所有进程都解除了对共享内存的映射,并且确定不再需要这块共享内存时,可以使用shmctl系统调用来删除它,释放系统资源。


 2.系统调用接口

shmget

  • 功能:创建或获取共享内存。
  • 参数
    • key_t key:一个键值,用于唯一标识一个共享内存段。你可以使用IPC_PRIVATE常量创建一个私有共享内存段,或使用ftok()函数根据文件路径生成一个唯一的键值。
    • size_t size:共享内存的大小,以字节为单位。
    • int shmflg:标志位,用于控制创建和获取共享内存的行为。
      • IPC_CREAT:如果指定的共享内存不存在,则创建它;如果已存在,则返回其标识符。
      • IPC_EXCL:与IPC_CREAT一起使用时,如果共享内存已存在,则调用失败(返回-1)。
  • 返回值:成功时返回共享内存的标识符(一个非负整数),失败时返回-1。

ftok函数是用于生成一个唯一的键值(key)的函数,通常用于创建共享内存、消息队列等进程间通信的标识符。它的原型是:

参数说明:

  1. const char *pathname:一个文件路径名字符串,它应该指向一个已存在的文件。
  2. int proj_id:一个非负整数,用于与pathname一起生成唯一的键值。

ftok函数会返回一个唯一的键值,如果发生错误则返回-1。这个键值可以用于后续的shmget等系统调用中,以标识和引用特定的共享内存段。

        这意味着,我们给两个进程使用同样的pathname和同样的id,调用同样的ftok,就能形成同样的key了。通过同样的key,就是能看到同一份资源的必要条件。


共享内存是需要手动释放的,不随着进程的结束而结束

ipcs -m查共享内存,ipcrm -m删除共享内存(按照共享内存的shmid删除)

当然你也可以使用函数shmctl

参数说明

  • shmid:共享内存标识符,即要控制的共享内存段的标识符,通常由 shmget 函数返回。
  • cmd:控制命令,用于指定要执行的操作。常用的命令包括:
    • IPC_RMID:删除共享内存段。
    • IPC_SET:设置共享内存段的权限和所有者。
    • IPC_STAT:获取共享内存段的状态信息。
  • buf:指向 struct shmid_ds 结构的指针,用于存储共享内存段的信息或设置其属性。可以为 NULL,表示不获取或设置共享内存段的信息。

返回值

如果函数执行成功,返回值为 0;如果出现错误,返回值为 -1,并设置 errno 来指示具体的错误原因。


key vs shmid

key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理,是给内核用来区分shm唯一性的(用户给操作系统用的)

shmid:内核给用户返回的一个标识符,让用户对shm进行管理的id值(操作系统给用户的)

        使用共享内存标识符而不是键进行后续操作也增加了安全性。因为即使其他进程知道了你的共享内存段的键,它们也无法直接访问或修改你的共享内存段,除非它们也通过shmget获取了对应的共享内存标识符,并且有足够的权限来操作这个共享内存段。


使用共享内存时要挂接,这时就要用到一个函数shmat。

hmat函数在Linux系统编程中用于将进程挂接到共享内存上,从而实现不同进程间的通信。以下是关于shmat函数的详细解释:

1. 函数原型

2. 参数说明

  • shmid:由shmget函数返回的共享内存标识符。
  • shmaddr:指定共享内存连接到当前进程时的地址。有三种情况:
    • 如果shmaddr是NULL,系统将自动选择一个合适的地址。
    • 如果shmaddr不是NULL并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
    • 如果shmaddr非0并且指定了SHM_RND,则此段连接到shmaddr - (shmaddr mod SHMLAB)所表示的地址上,其中SHMLAB是低边界地址的倍数,它总是2的乘方。
  • shmflg:是一组按位OR(或)在一起的标志,用来控制读写权限等。其两个可能取值是SHM_RND和SHM_RDONLY。如果指定了SHM_RDONLY,则以只读方式连接此段,否则以读写的方式连接此段。

3. 返回值

  • 如果调用成功,返回一个指向共享内存的指针。
  • 如果出错,返回-1。

4. 原理

shmat函数从进程空间中选择一个合适的或者用户指定的线性地址,并将其挂接到共享内存的物理页上。一旦挂接完成,用户就可以通过返回的指针来访问共享内存中的数据。

5. 注意事项

  • 在使用shmdt函数断开线性地址到物理地址的映射关系后,不能再次使用shmat函数进行挂接,否则可能会导致segment fault。
  • 共享内存是IPC(进程间通信)中效率最高的方式之一,因为它允许进程直接访问内存中的数据,而不需要像管道、消息队列那样进行内核与用户空间的数据拷贝。

shmdt 的基本使用:用于从共享内存段中分离一个进程

  • shmaddr:这是指向共享内存段的指针,该指针是之前通过 shmat 系统调用返回的。

返回值:

  • 如果成功,返回 0。
  • 如果失败,返回 -1 并设置 errno 以指示错误。

使用 shmdt 时,要注意以下几点:

  1. 只影响当前进程:调用 shmdt 只影响当前进程。其他已经附加到该共享内存段的进程仍然可以访问它。
  2. 数据持久性:即使调用了 shmdt,共享内存段中的数据仍然保持不变,直到所有进程都与其分离,或者调用者(通常是创建该段的进程)显式地删除了它(使用 shmctl 系统调用和 IPC_RMID 命令)。
  3. 多次附加和分离:一个进程可以多次使用 shmat 附加到同一个共享内存段,并可以多次使用 shmdt 分离。但每次附加都需要一个单独的分离操作。
  4. 关闭文件描述符:如果在附加共享内存时创建了一个文件描述符(例如,使用 O_CREAT | O_RDWR 标志调用 shmat),则在调用 shmdt 后,应使用 close 系统调用来关闭该文件描述符。但请注意,这不会影响其他进程对共享内存段的访问。
  5. 错误处理:如果 shmdt 调用失败,应检查 errno 以了解失败的原因。可能的错误包括 EINVAL(无效的 shmaddr 参数)和 EACCES(调用进程没有足够的权限来分离该共享内存段)。

3.使用一下shmget(一段代码) 

创建方法:

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

//用当前路径形成key值
const std::string gpathname = "/home/ubuntu/work/shm";
//随意设置一个
const int gproj_id = 0x66;
//将key转为16进制
std::string ToHex(key_t k)
{
    char buffer[128];
    snprintf(buffer,zizeof(buffer),"0x%x",k);
}

    //形成相同的key值
    key_t GetCommKey(const std::string &gpathname,int gproj_id)
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }
    //创建shm
    int ShmGet(key_t key,int size)
    {
        int shmid =shmget(key,size,IPC_CREAT | IPC_EXCL);
        if(shmid<0)
        {
            perror("shmget");
        }

        return shmid;
    }

服务端创建:

#include "Shm.hpp"

int main()
{   
    key_t key=GetCommKey(gpathname,gproj_id);
    std::cout<<"key:"<<ToHex(key)<<std::endl;

    int shmid=ShmGet(key,4096);
    std::cout<<"shmid:"<<shmid<<std::endl;

    return 0;
}

客户端:

#include "Shm.hpp"

int main()
{
    key_t key=GetCommKey(gpathname,gproj_id);
    std::cout<<"key:"<<ToHex(key)<<std::endl;

    return 0;
}

运行结果:有相同的key,


4.一个案例(一段代码)

1.简单封装一下

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

const int gCreater = 1;
const int gUser = 2;
//用当前路径形成key值
const std::string gpathname = "/home/ubuntu/work/shm";
//随意设置一个
const int gproj_id = 0x66;
//将key转为16进制
const int gShmSize = 4097;
class Shm
{
private:
    key_t GetCommKey()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }
    int GetShmHelper(key_t key, int size, int flag)
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }

        return shmid;
    }
public:
    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who)
    {
        _key = GetCommKey();
        if (_who == gCreater)
            GetShmUseCreate();
        else if (_who == gUser)
            GetShmForUse();
        std::cout << "shmid: " << _shmid << std::endl;
        std::cout << "_key: " << ToHex(_key) << std::endl;
    }
    ~Shm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "shm remove done..." << std::endl;
    }

    std::string ToHex(key_t key)
    {
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "0x%x", key);
        return buffer;
    }
    bool GetShmUseCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL);
            if (_shmid >= 0)
                return true;
            std::cout << "shm create done..." << std::endl;
        }
        return false;
    }
    bool GetShmForUse()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);
            if (_shmid >= 0)
                return true;
            std::cout << "shm get done..." << std::endl;
        }
        return false;
    }
private:
private:
    key_t _key;
    int _shmid;

    std::string _pathname;
    int _proj_id;
    //我是谁
    int _who;
};

服务段:

#include "Shm.hpp"

int main()
{
    Shm shm(gpathname,gproj_id,gCreater);
    return 0;
}

用户段:

#include "Shm.hpp"

int main()
{
    Shm shm(gpathname,gproj_id,gUser);
    return 0;
}

运行结果:


2.使用共享内存

 2.1挂接(shmat)和取消挂接(shmdt)

现阶段,我们只是让进程A和B看到了共享内存,使用共享内存的前提是要,将共享内存挂接(使用函数shmat)进程的地址空间上。进程可以将之前创建的共享内存区域映射到自己的虚拟地址空间中。这样,进程就可以通过指针直接访问这块共享内存,而无需通过内核进行数据的拷贝。在使用这个函数的时候,是要访问共享内存的,共享内存也是文件,当然也有权限,因此我们创建共享内存的时候是要带权限的。

_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
           

         当多个进程都映射了同一块共享内存到各自的地址空间后 ,进程A可以将数据写入共享内存,然后进程B可以从共享内存中读取这些数据,从而实现进程间的数据交换。

        当你不再需要这块共享内存,或者你的进程即将结束时,你应该使用 shmdt 来从这块内存中分离。这样做可以确保系统能够正确地管理这块内存,防止资源泄露。

    void *AttachShm()
    {
        if (_addrshm != nullptr)
            DetachShm(_addrshm);
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
        return shmaddr;
    }
    void DetachShm(void *shmaddr)
    {
        if (shmaddr == nullptr)
            return;
        shmdt(shmaddr);
        std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
    }

2.2重新封装

这里我们新增了一个成员变量,addshm,用于记录共享内存的地址。

在向共享内存中写东西之前,我们先做清空,这里我们多加一个操作函数。

    void Zero()
    {
        if(_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
    }

重新封装后:

​
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*n

class Shm
{
private:
    key_t GetCommKey()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }
    int GetShmHelper(key_t key, int size, int flag)
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }

        return shmid;
    }
    std::string RoleToString(int who)
    {
        if (who == gCreater)
            return "Creater";
        else if (who == gUser)
            return "gUser";
        else
            return "None";
    }
    void *AttachShm()
    {
        if (_addrshm != nullptr)
            DetachShm(_addrshm);
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
        return shmaddr;
    }
    void DetachShm(void *shmaddr)
    {
        if (shmaddr == nullptr)
            return;
        shmdt(shmaddr);
        std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
    }

public:
    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = GetCommKey();
        if (_who == gCreater)
            GetShmUseCreate();
        else if (_who == gUser)
            GetShmForUse();
        _addrshm = AttachShm();

        std::cout << "shmid: " << _shmid << std::endl;
        std::cout << "_key: " << ToHex(_key) << std::endl;
    }
    ~Shm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "shm remove done..." << std::endl;
    }

    std::string ToHex(key_t key)
    {
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "0x%x", key);
        return buffer;
    }
    bool GetShmUseCreate()
    {
        if (_who == gCreater)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm create done..." << std::endl;
        }
        return false;
    }
    bool GetShmForUse()
    {
        if (_who == gUser)
        {
            _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
            if (_shmid >= 0)
                return true;
            std::cout << "shm get done..." << std::endl;
        }
        return false;
    }
    void Zero()
    {
        if(_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
    }

    void *Addr()
    {
        return _addrshm;
    }



private:
    key_t _key;
    int _shmid;

    std::string _pathname;
    int _proj_id;

    int _who;
    void *_addrshm;
};

​

2.3使用共享内存进行通信

server作为读端

int main()
{
    //读端
    Shm shm(gpathname,gproj_id,gCreater);
    char *shmaddr =(char*)shm.Addr();

    //读内容
    while(true)
    {
        std::cout<<"shm memory content:"<<shmaddr<<std::endl;
        sleep(1);
    }
    return 0;
}

client作为写端

int main()
{
    //写端
  
    Shm shm(gpathname,gproj_id,gUser);
    shm.Zero();
    char *shmaddr = (char*)shm.Addr();

    //写内容
    char ch='A';
    while(ch<='Z')
    {
        shmaddr[ch-'A'] =ch;
        ch++;
        sleep(2);
    }
    return 0;
}

运行结果:

 5.共享内存的优劣

以下是对其优缺点的详细分析:

优点:

  1. 高效性
    • 共享内存是IPC通信中传输速度最快的通信方式。因为数据不需要在客户机和服务器之间复制,数据直接写到内存,避免了多次数据拷贝,从而大大提高了通信效率。
    • 进程对共享内存的访问就如同访问自己的内存空间一样,不需要进行额外的系统调用或内核操作,进一步提升了效率。
  2. 灵活性
    • 允许多个进程共享数据,提供了一种灵活的通信方式。
    • 进程间可以通过共享的内存区域进行双向通信,满足了多种通信需求。
  3. 支持大量数据传输
    • 适用于需要快速传递大量数据的场景,特别是在大数据处理、实时通信等领域表现突出。

缺点:

  1. 同步问题
    • 由于多个进程可以同时访问共享内存,需要额外的同步机制来避免数据竞争和一致性问题。内核并不提供任何对共享内存访问的同步机制,因此通常需要使用信号量等其他IPC机制进行读写同步与互斥。(写端没写,读端不会阻塞等待,依旧进行读取)
  2. 安全性
    • 需要额外的安全机制来保护数据,防止其他进程非法访问。若未采取适当的安全措施,可能导致数据泄露或被篡改。
  3. 编程复杂性
    • 使用共享内存进行通信需要处理同步和数据一致性等复杂问题,编程复杂度较高。需要开发者具备深厚的操作系统和并发编程知识。
  4. 依赖操作系统支持
    • 共享内存的使用依赖于操作系统的支持。不同的操作系统或版本可能对共享内存的实现和管理方式存在差异,这增加了跨平台开发的难度。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024 年适用于 Linux 的 5 个微软 Word 替代品

对于那些最近由于隐私问题或其他原因而转向 Linux 的用户来说&#xff0c;可能很难替换他们最喜欢的、不在 Linux 操作系统上运行的应用程序。 寻找流行程序的合适替代品可能会成为一项挑战&#xff0c;而且并不是每个人都准备好花费大量时间来尝试弄清楚什么可以与他们在 Win…

新买的移动硬盘无法识别

文章目录 背景解决方案 背景 同事新买的移动硬盘&#xff0c;插在电脑上识别不出来盘符&#xff0c;检查了一下&#xff0c;硬盘没问题应该&#xff0c;是ssk的硬盘盒M.2的SSD&#xff0c;硬盘驱动也是正常的&#xff0c;插拔了几次&#xff0c;都不识别&#xff0c;换了太电脑…

fl studio怎么设置中文及 2024年最新fl studio选购指南

FL Studio让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制。zol提供FL Studio中文版下载。 FL Studio中文版下载软件简介 FL Studio 让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混…

基于实验的电动汽车动力电池SOC

前言 本文为笔者在学习《基于MATLAB的新能源汽车仿真》过程中学习笔记&#xff0c;所涉及的表格数据和公式均为书籍里的。仿真数据是网上找的恒电流放电数据。本文仅作为笔者的入门学习记录。 一、分析动力电池SOC估算方法 SOC是指动力电池按照规定放电条件可以释放的容量占…

java版知识付费saas租户平台:剖析现代知识付费平台的功能架构与运营逻辑

在数字化学习的时代背景下&#xff0c;知识付费平台已经成为教育行业的一颗璀璨明星&#xff0c;以其用户需求为中心&#xff0c;提供便捷高效的学习途径。这些平台汇聚了众多专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需…

安全专业的硬件远控方案 设备无网也能远程运维

在很多行业中&#xff0c;企业的运维工作不仅仅局限在可以联网的IT设备&#xff0c;不能连接外网的特种设备也需要专业的远程运维手段。 这种特种设备在能源、医疗等行业尤其常见&#xff0c;那么我们究竟如何通过远程控制&#xff0c;对这些无网设备实施远程运维&#xff0c;…

[Algorithm][动态规划][01背包问题][目标和][最后一块石头的重量Ⅱ]详细讲解

目录 1.目标和1.题目链接2.算法原理详解3.代码实现 2.最后一块石头的重量 II1.题目链接2.算法原理详解3.代码实现 1.目标和 1.题目链接 目标和 2.算法原理详解 问题转化&#xff1a;在数组中选择一些数&#xff0c;让这些数的和等于a&#xff0c;一共有多少种选法&#xff1f…

推荐4个好用有趣的软件

MyComic——漫画聚合软件 MyComic是一款界面简洁、分类详尽的漫画阅读软件&#xff0c;专为动漫爱好者设计。它提供了丰富的高清漫画资源&#xff0c;支持在线免费阅读&#xff0c;并且可以一键下载到书架&#xff0c;方便随时离线观看&#xff0c;节省流量。用户可以轻松找到喜…

CSAPP Lab01——Data Lab完成思路

陪你把想念的酸拥抱成温暖 陪你把彷徨写出情节来 未来多漫长再漫长还有期待 陪伴你 一直到 故事给说完 ——陪你度过漫长岁月 完整代码见&#xff1a;CSAPP/datalab-handout at main SnowLegend-star/CSAPP (github.com) 01 bitXor 这道题是用~和&计算x^y。 异或是两个…

Android Compose 十:常用组件列表 监听

1 去掉超出滑动区域时的拖拽的阴影 即 overScrollMode 代码如下 CompositionLocalProvider(LocalOverscrollConfiguration provides null) {LazyColumn() {items(list, key {list.indexOf(it)}){Row(Modifier.animateItemPlacement(tween(durationMillis 250))) {Text(text…

接口(API)开发,测试工具-apifox

前言 为什么需要接口&#xff08;API&#xff09;? 因为不同的平台或系统可能使用不同的技术栈、编程语言或数据格式。API提供了一个标准化的方式&#xff0c;使得这些不同的系统可以相互交换数据和功能调用&#xff0c;实现互操作性 在开发日常的项目交互中&#xff0c;不…

英伟达的GPU(4)

更第四篇&#xff0c;上周有点私事&#xff0c;恢复更新 上次的文章 英伟达的GPU(3) (qq.com) 书接前文&#xff0c;我们上章说要更新GPU的内存机制&#xff0c;本次就讲点这个 先做个定义&#xff0c;我们说内存&#xff08;显存&#xff09;&#xff0c;也分物理内存&#…

Linux系统推出VB6开发IDE了?Gambas,Linux脚本编写

第一个Linux程序&#xff0c;加法计算加弹窗对话框,Gambas,linux版的类似VB6的IDE开发环境 一开始想用VB6的Clng函数转成整数&#xff0c;没这函数。 输入3个字母才有智能提示&#xff0c;这点没做好 没有msgbox函数&#xff0c;要用messagebox.warning 如果可以添加函数别名就…

【算法篇】求最长公共前缀JavaScript版本

题目描述 给你一个大小为 n 的字符串数组 strs &#xff0c;其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀&#xff0c;返回这个公共前缀。 数据范围&#xff1a; 数据范围:0<n<5000&#xff0c;0<len(strsi)< 5000 进阶:空间复杂度 O(1)&a…

2024年第三届数据统计与分析竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 详细请查 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024年第三届数据统计与分析竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有…

Unity | Shader基础知识(番外:了解内置Shader-Standard<一>)

目录 前言 一、什么是Standard 二、Standard参数详解 1.了解着色前 2. 着色拆分 3.参数RenderingMode 4.参数Albedo 5.参数Metallic 三、作者的话 前言 有粉丝给我说&#xff0c;感觉自己内部自带的Shader都还不知道怎么用&#xff0c;希望我讲一下内置Shader。 那…

Docker Desktop - WSL distro terminated abruptly

打开 PowerShell 或以管理员身份运行的命令提示符。运行以下命令以列出已安装的 WSL 分发&#xff1a; wsl --list 运行以下命令以注销 Docker 相关的分发 wsl --unregister <distro_name> 将<distro_name>替换为实际的 Docker 相关分发的名称。将<distro_…

[书生·浦语大模型实战营]——LMDeploy 量化部署 LLM 实践

1.基础作业 1.1配置 LMDeploy 运行环境 创建开发机 创建新的开发机&#xff0c;选择镜像Cuda12.2-conda&#xff1b;选择10% A100*1GPU&#xff1b;点击“立即创建”。注意请不要选择Cuda11.7-conda的镜像&#xff0c;新版本的lmdeploy会出现兼容性问题。其他和之前一样&…

智能组网节点是什么?

智能组网节点是一种用于解决复杂网络环境下远程连接问题的关键技术。它是一种通过智能化的方式&#xff0c;在任何网络环境下实现不同地区之间快速组建局域网的解决方案。其中&#xff0c;【天联】组网就是一款优秀的智能组网节点产品&#xff0c;是北京金万维科技有限公司自主…

苍穹外卖笔记-07-菜品管理-增加、删除、修改、查询分页还有菜品起售或停售状态

菜品管理 1 新增菜品1.1 需求分析与设计1.2 代码开发文件上传新增菜品实现 1.3 功能测试 2 菜品分页查询2.1 需求分析和设计2.2 代码开发设计DTO类设计VO类Controller层Service层Mapper层 2.3 功能测试 3 删除菜品3.1 需求分析和设计3.2 代码开发Controller层Service层Mapper层…