Linux进程间通信(IPC)机制之一:共享内存

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:Nonsense—Sabrina Carpenter

                                                                0:50━━━━━━️💟──────── 2:43
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

什么是共享内存?

共享内存介绍

共享内存原理

函数接口详解

通过ftok获取key值

通过shmget创建共享内存

一些小细节

通过shmat挂接进程

通过shmdt取消与共享内存的关联

通过shmctl控制共享内存

IPC_RMID:删除共享内存

IPC_STAT:获取共享内存的状态

IPC_SET:改变共享内存的状态

共享内存的拓展

Makefile

server.cc

client.cc

代码效果


什么是共享内存?

共享内存介绍

        Linux共享内存是一种快速的数据交换手段,允许多个进程访问同一块内存区域

        共享内存在Linux中是一种高效的进程间通信(IPC)机制,它使得不同的进程可以访问同一段内存区域,从而实现数据共享和传输。它是内核级别的资源,并且通常是进程间通信方式中最快的一种。Linux共享内存的使用方式主要有以下几种(本文主要介绍基于system V的共享内存):

  1. 基于system V的共享内存:这是传统的方法,历史悠久,但API较为复杂。如果编译内核时没有选择CONFIG_SYSVIPC,则不会支持这种方式。
  2. 基于POSIX mmap文件映射实现共享内存:这种方法使用mmap系统调用将文件映射到内存中,从而实现共享。
  3. 通过memfd_create()和fd跨进程共享:这是一种较新的方法,用于创建匿名内存区域,并通过文件描述符在不同进程间共享。
  4. 基于dma-buf的共享内存:这在多媒体和图形领域广泛使用,适用于高性能的数据传输需求。

        共享内存的优势在于其高速的数据传输能力,因为数据不需要在用户空间和内核空间之间复制。然而,它也有一些劣势,比如需要手动管理同步和并发访问,以及可能的安全问题,因为它绕过了操作系统的正常内存保护机制。为了提高安全性,可以使用命名管道等机制来实现访问控制。

共享内存原理

        我们还是需要遵守一句话:进程间通信的前提是让不同的进程看到同一块资源(必须由OS提供)。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。也就是说他是在用户空间中的而不是在内核空间中(缓冲区、文件属性等等就是在此)。如下是简易的图示:

​        更为详细的图示:

        共享内存的原里实际上就是(1)在物理内存中申请一块空间(2)将申请的空间通过页表映射到进程的虚拟内存中的共享区中,共享区通过返回虚拟地址的首地址就可让进程看到资源。接着再让另外的进程做同样的操作,不够需要注意的是要指向同一块物理空间。这样我们就让不同的进程看到了同一块资源。(3)当我们去掉页表的映射关系。(4)释放物理内存的空间后,就解除了共享内存。

        需要注意的是:系统中一定会有很多的共享内存存在,操作系统需要管理全部共享内存,因此我们会按照“先描述,在组织”的原则创建对应的数据结构进行管理。共享内存会必须要求有唯一标识符来区分,我们需要制定一定的规则、约定让不同的进程识别同一块共享内存从而得以通信

函数接口详解

        共享内存函数Linux共享内存的相关函数主要包括shmgetshmatshmdtshmctl。具体如下:

         1、shmget:这是创建或获取共享内存段的函数。它的原型是

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
    • key:用于指定共享内存的键值,可以是任意非负整数,通常通过ftok函数生成。
    • size:指定共享内存段的大小。
    • shmflg:设置共享内存段的权限和属性。

         2、shmat:这个函数用于将共享内存段附加到进程的地址空间上。它的原型是

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
    • shmid:由shmget返回的共享内存标识符。
    • shmaddr:可选参数,通常设置为nullptr,让系统自动选择一个地址来附加共享内存。
    • shmflg:附加标志,如SHM_RDONLY以只读方式附加。

         3、shmdt:当进程完成对共享内存的使用后,需要使用shmdt函数将其从进程的地址空间中分离(detach)。它的原型是

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *addr);
    • addr:之前通过shmat附加的共享内存地址。

         4、shmctl:这个函数用于对共享内存段进行控制操作,如删除共享内存段。它的原型是:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid:共享内存标识符,即要控制的共享内存段的标识符。
  • cmd:控制命令,可取值如下:
    • IPC_STAT:获取共享内存的状态。
    • IPC_SET:改变共享内存的状态。
    • IPC_RMID:删除共享内存。
  • buf:指向struct shmid_ds结构的指针,该结构用于存储共享内存的状态信息。当cmd为IPC_STAT或IPC_SET时,需要使用此参数。

        在实际应用中,通常会结合信号量或互斥锁等同步机制来确保数据的一致性和访问的安全性。此外,共享内存的使用也需要考虑到系统的资源限制,例如共享内存段的大小和数量都可能受到系统配置的限制。

通过ftok获取key值

        如果我们要找到或者创建一个共享内存,我们需要约定一个相同的key值,这样才能找到对应的共享内存,才能让进程间相互通信。因此,这也是创建共享内纯的前提。ftok函数用于将一个已存在的路径名和一个整数标识符转换成IPC键值,以便在进程间通信中使用

    ftok函数是Linux系统编程中用于创建唯一键值的工具,这个键值通常用于进程间通信(IPC)的机制,如消息队列、信号量和共享内存。它的函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
  • 参数说明
    • pathname:这是一个已存在的文件路径名,ftok会从这个路径名导出信息。
    • proj_id:这是一个与路径名相关的项目ID,通常是0到255之间的整数。
  • 返回值:函数返回一个key_t类型的键值,这个键值是由pathname导出的信息与proj_id的低序8位组合而成的。

        在使用ftok时,需要注意以下几点:

  • 确保提供的pathname确实存在,因为ftok会根据这个路径名生成键值的一部分。
  • proj_id应该是一个相对较小的整数,因为它只会使用其低序8位。
  • 生成的键值应该是唯一的,以确保不同的进程间通信不会相互干扰。

        如下示例:

#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>

const std::string pathname = "/home/land/109/pip/systemV";
const int proj_id = 0x11223344;

key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    std::cout <<"key:"<< key << std::endl;
    return key;
}

通过shmget创建共享内存

        通过手册理解:

​        我们最经常使用的是IPC_CREAT、IP_EXCL。他们分别的含义是:

IPC_CREAT//shm不存在,就创建。存在,就获取并返回。
IP_EXCL//不能单独使用,shm不存在就创建,存在就出错返回。

        如下示例:

    key_t key = GetKey();
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    std::cout << "shmid:" << shmid << std::endl;
    return 0;

​        穿插认识一下查看和删除共享内存的命令:

ipcs -m//显示共享内存的属性
ipcrm -m shmid//删除指定的共享内存

一些小细节

        前面我们在shmget的shmflg的介绍中知道这实际上还包含权限的设置,实际上要设置权限只需要在选完IPC_CREAT或IP_EXCL等后 | 上对应权限即可,如下:

    key_t key = GetKey();
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    std::cout << "shmid:" << shmid << std::endl;
    return 0;

        当我们设置要申请的共享内存大小时,强烈建议申请4096的整数倍数大的大小,因为低层分配是按4096的整数倍数来分配大小的,比如:你申请4097大小,但是低层实际上是4096*2的大小。

通过shmat挂接进程

        通过shmaddr可以选择一个地址来附加共享内存,但是通常我们会填nullptr,shmflg填是0值得注意的是:它的返回值是一个void* 的挂接成功后的虚拟地址空间的起始地址,也就是之前原理所述start_addr。如下是对应的手册:

        如下示例:

int main()
{
    key_t key = GetKey();
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    sleep(5);

    std::cout << "shmid:" << shmid << std::endl;
    std::cout<<"开始将shm映射到进程的地址空间中"<<std::endl;
    char* s=(char*)shmat(shmid,nullptr,0);
    
    sleep(10);
    return 0;
}

通过shmdt取消与共享内存的关联

        根据上shmat的返回值,也就是虚拟地址空间的起始地址来实现取消与共享内存的关联。实际上,取消关联的本质就是修改页表,我们可以从起始地址连续释放对应的虚拟内存的大小,在页表中连续释放对应大小的映射关系。根据shmat的示例来取消关联:

int main()
{
    key_t key = GetKey();
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }
    sleep(5);

    std::cout << "shmid:" << shmid << std::endl;
    std::cout<<"开始将shm映射到进程的地址空间中"<<std::endl;
    char* s=(char*)shmat(shmid,nullptr,0);

    sleep(10);
    shmdt((char*)s);

    sleep(5);
    return 0;
}

通过shmctl控制共享内存

IPC_RMID:删除共享内存

        如下示例:

// 定义共享内存结构体变量,当然也可直接传nullptr
struct shmid_ds shmbuffer;
// 获取共享内存状态
int ret = shmctl(shmid, IPC_STAT, &shmbuffer);
if (ret == -1) {
  perror("shmctl");
  exit(1);
}

IPC_STAT:获取共享内存的状态

        如下示例:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    key_t key = GetKey();
    // 共享内存标识符
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0664);

    // 定义共享内存状态结构体变量
    struct shmid_ds shmbuffer;

    // 获取共享内存状态
    int ret = shmctl(shmid, IPC_STAT, &shmbuffer);
    if (ret == -1) {
        perror("shmctl");
        return 1;
    }

    // 打印共享内存状态信息
    printf("共享内存状态信息:\n");
    printf("当前连接数: %d\n", shmbuffer.shm_nattch);
    printf("最后一次操作进程ID: %d\n", shmbuffer.shm_lpid);
    printf("最后一次操作时间: %ld\n", shmbuffer.shm_change_time);
    printf("共享内存大小: %ld\n", shmbuffer.shm_segsz);

    return 0;
}

IPC_SET:改变共享内存的状态

        如下示例:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    key_t key = GetKey();
    // 共享内存标识符
    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0664);

    // 定义共享内存状态结构体变量
    struct shmid_ds shmbuffer;

    // 获取共享内存状态
    int ret = shmctl(shmid, IPC_STAT, &shmbuffer);
    if (ret == -1) {
        perror("shmctl");
        return 1;
    }

    // 修改共享内存状态
    shmbuffer.shm_perm.mode |= 0666; // 设置共享内存权限为可读写
    shmbuffer.shm_perm.uid = getuid(); // 设置共享内存所有者为用户ID
    shmbuffer.shm_perm.gid = getgid(); // 设置共享内存所属组为用户组ID

    // 更新共享内存状态
    ret = shmctl(shmid, IPC_SET, &shmbuffer);
    if (ret == -1) {
        perror("shmctl");
        return 1;
    }

    printf("共享内存状态已成功更新。\n");

    return 0;
}

共享内存的拓展

        值得注意的是:共享内存是没有同步机制的,但是可以通过其他方法实现同步。由于多个进程可能同时读写共享内存,因此需要一种同步机制来确保数据的一致性和防止竞态条件的发生。以下是一些常用的同步方法:

  • 互斥锁(Mutexes):互斥锁是一种用于保护共享资源不被多个线程同时访问的同步机制。在Linux中,可以使用pthread库中的互斥锁来实现进程间的同步。
  • 信号量(Semaphores):信号量是另一种用于同步不同进程或线程的机制。它可以控制对共享资源的访问数量。在Linux中,可以使用POSIX有名信号量或POSIX基于内存的信号量来实现同步。
  • 信号(Signals):虽然信号主要用于进程间的通知,但它们也可以用于同步。例如,一个进程可以发送信号给另一个进程,告知它共享内存已经更新,从而触发接收进程执行某些操作。

        需要注意的是:在使用这些同步机制时,应当小心避免死锁和活锁的情况。此外,设计良好的同步策略对于提高系统性能和可靠性至关重要。

        下面是一个通过命名管道控制共享内存同步的例子:通过命名管道控制client每秒向共享内存写入一个字母,每次写入成功后会发送一个信号给server,server在收到信号后才去读取共享内存中的内容,并且将读取到的内容打印出来,如下为完整的代码以及效果:

Makefile

.PHONY:all
all:server client

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

server.cc

#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include "comm.hpp"

class Init
{
public:
    Init()
    {
        bool r = MakeFifo();
        if (!r)
            return;

        key_t key = GetKey();
        std::cout << "key : " << ToHex(key) << std::endl;

        shmid = CreateShm(key);
        std::cout << "shmid: " << shmid << std::endl;

        std::cout << "开始将shm映射到进程的地址空间中" << std::endl;

        s = (char *)shmat(shmid, nullptr, 0);
        fd = open(filename.c_str(), O_RDONLY);
    }
    ~Init()
    {
        shmdt(s);
        std::cout << "开始将shm从进程的地址空间中移除" << std::endl;

        shmctl(shmid, IPC_RMID, nullptr);
        std::cout << "开始将shm从OS中删除" << std::endl;

        close(fd);
        unlink(filename.c_str());
    }

public:
    int shmid;
    int fd;
    char *s;
};

int main()
{
    Init init;

    //TODO
    while (true)
    {
        // wait
        int code = 0;
        ssize_t n = read(init.fd, &code, sizeof(code));
        if (n > 0)
        {
            // 直接读取
            std::cout << "共享内存的内容: " << init.s << std::endl;
            sleep(1);
        }
        else if (n == 0)
        {
            break;
        }
    }

    sleep(10);
    return 0;
}

client.cc

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h> //Inter-Process Communication
#include <sys/shm.h>
#include "comm.hpp"

int main()
{
    key_t key = GetKey();
    int shmid = GetShm(key);
    char *s = (char*)shmat(shmid, nullptr, 0);
    std::cout << "attach shm done" << std::endl;
    int fd = open(filename.c_str(), O_WRONLY);

    // sleep(10);
    // TODO
    // 共享内存的通信方式,不会提供同步机制, 共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
    // 
    char c = 'a';
    for(; c <= 'z'; c++)
    {
        s[c-'a'] = c;
        std::cout << "write : " << c << " done" << std::endl;
        sleep(1);

        // 通知对方
        int code = 1;
        write(fd, &code, sizeof(4));
    }

    shmdt(s);
    std::cout << "detach shm done" << std::endl;
    close(fd);
    return 0;
}

代码效果


                   感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

2024年【危险化学品经营单位安全管理人员】考试内容及危险化学品经营单位安全管理人员模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【危险化学品经营单位安全管理人员】考试内容及危险化学品经营单位安全管理人员模拟考试&#xff0c;包含危险化学品经营单位安全管理人员考试内容答案和解析及危险化学品经营单位安全管理人员模拟考试练习。安…

如何提高思维能力,洞悉事物本质?(一)

什么是思维模型&#xff08;Mental Model&#xff09;&#xff1f; 你可能会在不少文章、培训课里面&#xff0c;看到这个名字。它们往往会用复杂的词汇和概念&#xff0c;通常还会扯上一些名人&#xff0c;把它包装得高深莫测。 但实际上&#xff0c;思维模型究竟是什么呢&…

照明灯具哪个品牌好知乎?质量最好的护眼台灯排行榜

台灯是家中必不可少的用品之一&#xff0c;它不仅能够提供基础的照明功能&#xff0c;还能营造出不一样的风格和氛围&#xff0c;影响人们的心情和生活品质。而一台好的护眼台灯还能够呵护我们的眼睛&#xff0c;保护好视力健康。想拥有一台使用体验感又好&#xff0c;寿命又长…

故障诊断 | 一文解决,SVM支持向量机的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,SVM支持向量机的故障诊断(Matlab) 支持向量机(Support Vector Machine,SVM)是一种常用的监督学习算法,用于分类和回归分析。SVM的主要目标是找到一个最优的超平面(或者在非线性情况下是一个最优的超曲面),将不同类别的样本分开…

Vue3中ElementPlus组件二次封装,实现原组件属性、插槽、事件监听、方法的透传

本文以el-input组件为例&#xff0c;其它组件类似用法。 一、解决数据绑定问题 封装组件的第一步&#xff0c;要解决的就是数据绑定的问题&#xff0c;由于prop数据流是单向传递的&#xff0c;数据只能从父流向子&#xff0c;子想改父只能通过提交emit事件通知父修改。 父&a…

【JavaEE】网络原理:UDP数据报套接字编程和TCP流套接字编程

目录 1.UDP数据报套接字编程 1.1 DatagramSocket 1.2 DatagramPacket 1.3 InetSocketAddress 1.4 基于UDP实现回响服务器 2.TCP流套接字编程 2.1 ServerSocket 2.2 Socket 2.3 基于TCP实现回响服务器 1.UDP数据报套接字编程 API 介绍 1.1 DatagramSocket DatagramS…

Transformer 自然语言处理(三)

原文&#xff1a;Natural Language Processing with Transformers 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第八章&#xff1a;使 transformers 在生产中更高效 在之前的章节中&#xff0c;您已经看到了 transformers 如何被微调以在各种任务上产生出色的结果。…

[机器学习]TF-IDF算法

一.TF-IDF算法概述 什么是TF-IDF&#xff1f; 词频-逆文档频率&#xff08;Term Frequency-Inverse Document Frequency&#xff0c;TF-IDF&#xff09;是一种常用于文本处理的统计方法&#xff0c;可以评估一个单词在一份文档中的重要程度。简单来说就是可以用于文档关键词的提…

k8s 1.29 一键安装脚本, 丝滑致极

博客原文 文章目录 集群配置配置清单集群规划集群网络规划 环境初始化主机配置 安装脚本需要魔法的脚本不需要魔法的脚本配置自动补全加入其余节点 验证集群 高可用版本: 高可用 k8s 1.29 一键安装脚本 集群配置 配置清单 OS&#xff1a; ubuntu 20.04kubernetes&#xff1a;…

“国潮风”带火年货消费,新中式服装在抖音电商销量同比增长超21倍

1月31日&#xff0c;抖音电商发布“抖音商城好物年货节”数据报告&#xff0c;展现龙年春节年货市场消费趋势及大众购买偏好。数据显示&#xff0c;1月13日至28日&#xff0c;货架场抖音商城日均GMV比去年年货节增长了98%&#xff0c;年货节电商直播累计时长达4385万小时&#…

Next.js如何正确处理跨域问题?

以前一直使用Vue来写前端。去年下半年接手了一个基于React Next.js的项目&#xff0c;于是顺带学习了一下Next.js。由于Next.js的特点&#xff0c;这个项目的前后端是放在一起的。一开始没什么问题&#xff0c;看了半天文档就上手了。 上周我们需要在另一个网页项目中&#x…

添加了gateway之后远程调用失败

前端提示500&#xff0c;后端提示[400 ] during [GET] to [http://userservice/user/1] 原因是这个&#xff0c;因为在请求地址写了两个参数&#xff0c;实际上只传了一个参数 解决方案&#xff1a;加上(required false)并重启所有相关服务

单元/集成测试服务

服务概述 单元/集成测试旨在证明被测软件实现其单元/架构设计规范、证明被测软件不包含非预期功能。经纬恒润测试团队拥有丰富的研发经验、严格的流程管控&#xff0c;依据ISO26262/ASPICE等开展符合要求的单元测试/集成测试工作。 在ISO 26262 - part6 部分产品开发&#xff…

最全前端 HTML 面试知识点

一、HTML 1.1 HTML 1.1.1 定义 超文本标记语言&#xff08;英语&#xff1a;HyperTextMarkupLanguage&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言 HTML元素是构建网站的基石 标记语言&#xff08;markup language &#xff09; 由无数个…

解读4篇混合类型文件Polyglot相关的论文

0. 引入 Polyglot文件指的是混合类型文件&#xff0c;关于混合类型文件的基础&#xff0c;请参考文末给出的第一个链接&#xff08;参考1&#xff09;。 1. Toward the Detection of Polyglot Files 1.1 主题 这篇2022年的论文&#xff0c;提出了Polyglot文件的检测方法。虽…

git操作之本地代码修改后想回退成当前最新版本

这张图很关键&#xff0c;取自https://www.cnblogs.com/cblx/p/12467083.html 我们的vscode就是workspace&#xff0c;我们提交代码需要三步&#xff0c;add&#xff0c;commit&#xff0c;push&#xff0c;其中我们想拉取代码有两种方式&#xff0c;git pull或者git fetch/cl…

QCM6125 UEFI XBL显示开机Logo不居中

QCM6125 UEFI XBL显示开机Logo在竖直方向不是居中显示,如果显示屏是正向安装到产品的话可能没有什么问题,但是如果产品的显示屏是90、180、270度旋转后安装到产品上的话显示开机Logo会有一些问题,并且大多数的平台以及产品开机Logo都是水平竖直方向都是居中显示,所以修改成…

【开源操作系统】上海道宁为您带来稳定、安全、开源和易用的操作系统——Ubuntu,为您的数字化生活保驾护航

Ubuntu是 源于非洲的一种传统价值观 意为“人性、关爱和共享” 这种价值观在 开源、稳定、安全、易用的 Ubuntu操作系统中 得到了完美的体现 除此之外&#xff0c;Ubuntu还具有 强大的安全性 它自带了诸多安全功能 如防火墙、加密文件系统等 可以有效地保护用户的隐私…

uniapp基于Android平台的校园生活服务交流论坛系统(二手,失物招领 -跑腿) 小程序hbuiderx

作为一款APP的校园论坛系统&#xff0c;面向的是大多数学者&#xff0c;软件的界面设计简洁清晰&#xff0c;用户可轻松掌握使用技巧。在调查之后&#xff0c;获得用户以下需求&#xff1a; &#xff08;1&#xff09;用户注册登录后&#xff0c;可进入系统解锁更多功能&#x…

【Java基础】之进程与线程

进程与线程 1. 线程与进程1.1 概念1.2 区别与联系 2. 线程的5种状态和切换2.1 简单介绍2.2 状态切换2.2.1 重点情况 3. 线程中常见的方法4. 线程池 1. 线程与进程 1.1 概念 进程&#xff1a;资源分配的基本单元&#xff0c;如QQ音乐 线程&#xff1a;资源调度的基本单元&…