1. 什么是system V

System V IPC(Interprocess Communication,进程间通信)是Unix系统中一种经典的进程间通信机制,由AT&T在System V.2版本中引入,并广泛应用于Linux等现代操作系统中。它通过三种核心机制实现进程间的同步、数据传递和资源共享。

在Linux中支持了这种标准,专门设计了IPC通信模块。

2. 共享内存原理

这里我们先来宏观的认识一下什么是共享内存。假设有俩个进程ab,如果它们需要进行进程间通信,除了我们之前说的命名管道,还可以通过共享内存的方式实现。我们之前再谈动态库加载原理的时候说过进程的地址空间的堆栈之间有一块共享区,用来记录动态库的虚拟地址。其实,在这块空间中,还用一片区域->共享内存区。进程ab在物理内存中申请同一块区域,然后各自通过页表映射建立物理内存和共享内存区之间的关系。至此,两个进程就可以看到同一份资源了!以上就是我们对共享内存的宏观认识,还有许多细节没有说。

> 我们刚刚所说的所有工作都涉及内核数据结构和磁盘,这些工作都由操作系统完成,我们使用相应的系统调用完成上面的工作。

> 在实际情况中,可能有多组进程在进行进程间通信,那势必有多个共享内存,有的是被创建,有的正在打开,有的正在关闭……所以,在内核中势必也会有描述共享内存的结构体对象,也势必会有管理这些共享内存的内核数据结构!

3. shmget[share memory get]

shmget是我们用来获取共享内存的接口:

> 参数size是用来设置我们创建共享内存的大小的,很好理解。

> 第三个参数是共享内存标记位, 有两个选项:IPC_CREAT、IPC_EXCL。

IPC_CREAT:只带这一个选项表示,如果目标共享内存不存在则创建并打开共享内存,否则就直接打开已近存在的目标共享内存。

IPC_EXCL:该选项单独使用无任何意义,必须和IPC_CREAT一起使用,使用是表示如果目标内存不存在则创建并打开该共享内存,如果存在,则会直接报错!从解释上来看该选项是保证我们创建一个全新的共享内存。

> 不过,这里还有许多问题:我们怎么知道一个共享内存到底是否存在,并且如何保证两个进程打开同一个共享内存呢?

> 这就由第一个参数key来标识共享内存的唯一性了!这个key不是由内核直接生成,而是让用户来构建并传入给操作系统的。这是为什么呢???

> 假如,进程a在内存中创建了一个共享内存区,内核直接生成对应放id给管理该区域的结构体对象。进程b需要和进程a进行进程间通信,那么进程b就需要拿到这个id来找到同一块共享内存但是,id也是数据,如果进程a可以把id给到进程b,它们不就已经可以进程间通信了吗!!!所以,由内核自己生成是做不到共享内存的!

> 但是,如果这两个进程其中一方在创建共享内存时就做好约定,在用户层规定一个key,传给操作系统,让这个key来唯一标记这个共享内存。有朝一日,另一个进程就可以通过这个key找到目标共享内存来完成进程间通信!

> 其实,这个key原理不就是我们用路径和管道文件名找同一个管道文件一样的吗。

> 好了,明白了上面的原理,我们就来说说key如何给定。理论上来说,这个key我们可以随便给,但是,为了减少key之间的冲突,系统给了我们一个生成key的接口ftok()

这个接口会根据用户传入的字符串和id整合形成一个key并返回给用户,正常情况下,我们也是使用这个接口来生成key。 这里有一个问题:为什么操作系统不这样设置呢->提供一个系统调用,在内核遍历已有的key,生成一个不存在的key返回给用户,这样做不是更好吗?但是,如果其他用户同时生成key,这些key会不会一样呢?

> 最后,我们再来说一下shmget的返回值

 如果一个共享内存被创建成功,会返回一个整形来唯一标记这个共享内存。这个整形是给用户看到的,而key则是给操作系统来寻找共享内存的。这就好比我们之前学文件时,打开一个文件时,系统通过file*的指针找到文件,却返回一个整形文件描述符fd给用户使用。

4. 共享内存demon代码编写

4.1 预备代码的编写过程

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)const std::string gpathname = ".";
int gproj_id = 0x66;
int g_size = 1024;
int g_default_id=-1;// 共享内存
class shm
{
public:shm() : _shm_id(g_default_id), _size(g_size) {}// 创建共享内存void create_shm(){// 获取keykey_t k = ftok(gpathname.c_str(), gproj_id);if (k < 0){// 获取key失败ERR_EXIT("ftok");}printf("key:0x%0x\n", k);// 创建共享内存_shm_id = shmget(k, _size, IPC_CREAT | IPC_EXCL);if (_shm_id < 0){// 创建共享内存失败ERR_EXIT("shmget");}printf("_shm_id:%d\n", _shm_id);}~shm() {}private:int _shm_id;int _size;
};

./server运行之后,我们果然创建共享内存成功了,也看到了相应的key和id。 

不过,当我们结束进程,再次运行时,却发现共享内存创建失败了。这说明之前的共享内存并没有随进程的结束而释放,事实上,共享内存的生命周期是随系统的,我们关闭系统重启之后,共享内存便释放了。

我们可以用命令:ipcs  -m 来查看系统内的共享内存情况。【ipcs即查看系统进程间通信资源,-m则标识查看共享内存部分】。

如果我们想要删除一个共享内存资源,可用命令ipsrm -m +shmid 

接下来,我们来看看代码级别怎么删除共享内存资源的,这里我们再认识一个接口shmctl: 

该系统调用是用来控制共享内存资源的,包括删除,查看共享内存资源属性等。第二个参数用来控制我们想要对共享内存做和控制,而第三个参数我们暂时用不到,直接设为nullptr即可。

要想释放目标共享内存,我们仅需将第二个参数设为IPC_RMID即可。好了,下面我们就来实现destroy接口。

// 释放共享内存资源void destroy(){if (_shm_id == g_default_id){// 没有创建共享内存资源,无需释放return;}int n = shmctl(_shm_id, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}printf("共享内存释放成功! shmid->%d\n", _shm_id);}

 有了上面的铺垫,我们就可以来学习如何使用共享内存来完成进程间通信了。首先,我们需要将我们创建的共享内存和我们的进程关联【本质就是让物理地址和进程虚拟地址空间完成映射】。这由系统完成,所以由对应的系统调用帮我们完成工作->shmat【at->attach】:

第一个参数很好理解,第二个参数用来设置固定虚拟地址映射,在我们应用层开发一般不需要关心这个参数,设为nullptr即可,第三个参数是用来标识我们的共享内存资源的权限的,我们一般设置为0,则系统会使用缺省权限->只读和只写。最后,如果成功完成内存和进程地址空间的关联,则该接口会返回系统选定虚拟地址映射共享内存起始虚拟地址,这个返回值和我们使用的malloc返回值是一样的,使用上也没什么区别,我们这样就很好理解了。

好了,接下来我们就设计一个接口来完成共享内存和进程的关联:

//进程和共享内存完成关联void attach(){_start_addr=shmat(_shm_id,nullptr,0);if((long long)_start_addr<0){ERR_EXIT("shmat");}printf("进程关联成功!_start_addr->%p\n",_start_addr);}

诶?为什么我们在关联共享内存时报错了呢,这个报错说明我们的行为被决绝了。其实,共享内存和文件在某些方面有些类似。共享内存也有对应的读写执权限,所以我们在创建共享内存时,需要设定相应的权限,做法也是和文件权限一样。

 执行结果:

现在,服务端创建对应的共享内存并完成地址空间的映射。那么,客户端也需要相应的接口获取同一份共享内存并进行地址空间的映射。创建和删除共享内存的工作在客户端也就不需要做了!

具体做法如下图所示:

我们做完以上工作之后,就可以开始进程间通信了。做法非常简单,将我们返回的虚拟地址用指针指向,把共享内存当做长字符串来使用就可以了。

4.2 共享内存优缺点 

最后,我们再来总结一下:我们发现在使用共享内存实现进程间通信的读写的时候,并没有使用系统调用,而我们在使用管道的时候,切切实实的使用了系统调用。很好理解,因为共享内存的读写是在进程的虚拟地址空间上面操作的【这属于用户层】,所以共享区属于用户层可以让用户直接使用,而管道读写则是在内核文件缓冲区完成的,这属于系统层。

共享区也是因此有以下的优点:
> 共享区是进程间通信中,速度最快的方式:映射之后,内容直接被双方进程看到,不需要进行系统调用来获取和写入。

不过,共享内存机制的优点也是牺牲了很多换来的:第一点就是共享内存没有像管道那样的同步机制服务端开启后就一直读,如果客户端不开启呢??而管道文件一方开始读后,如果另一方没有写,则读取放就会同步阻塞并不开始读取,知道另一方写入。另一点就是共享内存没有保护机制,这里的保护不是指对共享内存的保护,而是对读写数据的保护。如果写入端对自己写入的内容在读取时有特定的读取要求【比如一次读多少字节,一次读一整句话等等】,目前的共享内存就无能为力了,因为读取方不停的读,就无所谓读取格式的要求了,它也正因此是进程间通信中最快的方式。

那么,我们目前有没有什么方案可以将我们的数据保护起来呢?当然有了,我们可以同时用管道把这两个进程关联起来。而这个管道的作用就是用来做服务端等待wait,客户端唤醒wake假设客户端自己写入两个字符后,服务端成对的读取打印结果。那我在客户端写入成对字符后,通过管道写入来唤醒【唤醒的方式随便啦】服务端服务端通过是否收到管道信号来决定是否读取共享内存中的数据。如此一来,我们就通过管道,讲我们的共享内存的读写控制起来了!话不多说,直接上代码!

客户端和服务端之间的通信逻辑:

 结果如下:

4.3 共享内存去关联 

上面,我们在释放共享内存的时候是直接shmctl删除的,但是我们再删除之前还遗漏了一步,就是去关联!在释放共享内存之前,我们还应该将关联该共享内存的所有进程去关联。这里我们使用shmdt接口即可,使用也很简单,传入虚拟地址空间的其实地址即可。

 修改以下部分即可:

4.3 共享内存大小问题 

然后,我们再来说一下创建共享内存时的大小问题,实际上,共享内存的大小一定是4kb的整数倍的 。但是如果我们创建大小时,给定的不是4kb的整数被,那么系统则会向上取整开辟对应大小的共享内存空间。不过,我们用ipcs -m 命令查看到的大小还是我们自己定义的大小!

4.4 获取共享内存属性

在最后的最后,我们再来见一见共享内存是如何被系统管理起来的。

内核中,有一种结构体对象shmid_ds,该结构体对象记录了共享内存的一些属性,其中有一个结构体ipc_perm里面记录了共享内存的key!

 我们使用shmctl传入IPC_STAT参数即可查看到先关的属性!

5. 源码 

5.1 comm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>#define FIFO_FILE "fifo"
#define FIFO_PATH "."const std::string gpathname = ".";
int gproj_id = 0x66;
int g_size = 4097;
int g_default_id = -1;
int gmode = 0666;#define USER "user"
#define CREATER "creater"#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)

5.2 Shm.hpp

#include "comm.hpp"// 共享内存
class shm
{
private:// 创建共享内存void create_help(int flag){// 创建共享内存// _shm_id = shmget(k, _size, IPC_CREAT | IPC_EXCL | gmode);_shm_id = shmget(_key, _size, flag);if (_shm_id < 0){// 创建共享内存失败ERR_EXIT("shmget");}printf("共享内存创建成功!_shm_id->%d\n", _shm_id);}// 创建共享内存void create_shm(){create_help(IPC_CREAT | IPC_EXCL | gmode);}// 获取共享内存void get_shm(){create_help(IPC_CREAT);}// 释放共享内存资源void destroy(){if (_shm_id == g_default_id){// 没有创建共享内存资源,无需释放return;}// 去关联detach();if (_user_type == CREATER){int n = shmctl(_shm_id, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}printf("共享内存释放成功! shmid->%d\n", _shm_id);}}// 进程和共享内存去关联void detach(){int n = shmdt(_start_addr);if (n < 0)ERR_EXIT("shmdt");printf("共享内存去关联成功!\n");}// 进程和共享内存完成关联void attach(){_start_addr = shmat(_shm_id, nullptr, 0);if ((long long)_start_addr < 0){ERR_EXIT("shmat");}printf("进程关联成功!_start_addr->%p\n", _start_addr);}public:shm(const std::string &pathname, int projid, const std::string &user_type): _shm_id(g_default_id),_size(g_size),_user_type(user_type){// 获取key_key = ftok(pathname.c_str(), projid);if (_key < 0){// 获取key失败ERR_EXIT("ftok");}printf("成功获取key!key->0x%0x\n", _key);if (user_type == CREATER)create_shm();else if (user_type == USER)get_shm();else{}attach();}~shm(){if (_user_type == CREATER)destroy();}void *get_start_addr(){return _start_addr;}int get_size(){return _size;}// 获取共享内存相关属性void get_property(){struct shmid_ds tmp;int n = shmctl(_shm_id, IPC_STAT, &tmp);if (n < 0)ERR_EXIT("shmctl");printf("shm_segsz->%ld\n", tmp.shm_segsz);printf("key->0x%0x\n", tmp.shm_perm.__key);}private:key_t _key;int _shm_id;int _size;void *_start_addr;std::string _user_type;
};

5.3 name_fifo.hpp

#include "comm.hpp"// 命名管道类
class name_fifo
{
public:// 构造函数name_fifo(const std::string &path, const std::string &name): _path(path),_name(name){umask(0);_fifo_name = _path + "/" + _name;// 创建命名管道int n = mkfifo(_fifo_name.c_str(), 0666);if (n < 0){// 命名管道创建失败// perror("命名管道创建失败\n");ERR_EXIT("mkfifo");}else{std::cout << "管道创建成功!" << std::endl;}}// 析构函数~name_fifo(){// 删除管道文件int n = unlink(_fifo_name.c_str());if (n < 0){// perror("删除管道文件失败!\n");ERR_EXIT("unlink");}else{std::cout << "管道删除成功!" << std::endl;}}private:std::string _path;std::string _name;std::string _fifo_name;
};// 命名管道操作类
class fifo_opre
{
public:fifo_opre(const std::string &path, const std::string &name): _path(path),_name(name),_fd(-1){_fifo_name = _path + "/" + _name;}void open_for_read(){// 创建成功->服务端打开管道->读取内容_fd = open(_fifo_name.c_str(), O_RDONLY);if (_fd < 0){// 命名管道打开失败// perror("命名管道打开失败\n");ERR_EXIT("open");}else{std::cout << "管道打开成功!" << std::endl;}}void open_for_write(){// 打开管道文件_fd = open(_fifo_name.c_str(), O_WRONLY);if (_fd < 0){// 命名管道打开失败// perror("命名管道打开失败\n");ERR_EXIT("open");}else{std::cout << "管道打开成功!" << std::endl;}}bool wait(){char c;int num = read(_fd, &c, 1);if (num > 0)return true;return false;}bool wake(){char c = 'a';int num = write(_fd, &c, 1);if (num > 0)return true;return false;}void Close(){if (_fd > 0)close(_fd);}~fifo_opre(){}private:std::string _path;std::string _name;std::string _fifo_name;int _fd;
};

5.4 server.cc

#include "comm.hpp"
#include "Shm.hpp"
#include "name_fifo.hpp"int main()
{// 服务端先创建关联共享内存shm s(gpathname, gproj_id, CREATER);// // 创建命名管道// name_fifo nf(FIFO_PATH, FIFO_FILE);// char *mem = (char *)s.get_start_addr();// // 命名管道读取端// fifo_opre fifo_reader(FIFO_PATH, FIFO_FILE);// fifo_reader.open_for_read();// // 服务端读取// while (true)// {//     // 如果等待成功则读取//     if (fifo_reader.wait())//     {//         printf("%s\n", mem);//     }//     else//         break;// }// // 关闭命名管道// fifo_reader.Close();s.get_property();return 0;
}

5.5 client.cc

#include "comm.hpp"
#include "Shm.hpp"
#include "name_fifo.hpp"int main()
{// 客户端获取共享内存shm s(gpathname, gproj_id, USER);char *mem = (char *)s.get_start_addr();// 客户端打开管道写入唤醒fifo_opre fifo_writer(FIFO_PATH, FIFO_FILE);fifo_writer.open_for_write();int index = 0;for (char c = 'A'; c <= 'Z'; index += 2, c++){sleep(1);mem[index] = c;mem[index + 1] = c;fifo_writer.wake();}return 0;
}

5.6 Makefile

.PHPNY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server fifo

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从输入到路径:AI赋能的地图语义解析与可视化探索之旅(2025空间智能全景)

​​摘要​​在空间智能爆发的2025年&#xff0c;地图系统已从静态导航工具进化为​​实时决策中枢​​。本文深度解析AI如何重构地理信息处理全链路&#xff1a;通过​​多模态语义理解​​&#xff08;文本/语音/图像→空间意图&#xff09;、​​动态路网建模​​&#xff0…

安全运维新趋势:AI 驱动的自动化威胁检测

在数字化浪潮中&#xff0c;网络攻击正从 “单点突破” 进化为 “链状打击”&#xff1a;2024 年某金融机构遭遇供应链攻击&#xff0c;恶意代码通过运维通道潜伏 3 个月&#xff0c;传统规则引擎因未识别 “正常运维指令中的异常参数”&#xff0c;导致数据泄露损失过亿。这背…

数据库复合索引设计:为什么等值查询列应该放在范围查询列前面?

前言作为后端开发工程师&#xff0c;我们经常会遇到数据库查询性能问题。在一次系统优化中&#xff0c;我发现一个简单的索引顺序调整竟然让查询速度提升了10倍&#xff01;这让我意识到复合索引列顺序的重要性。今天&#xff0c;我就来分享一下这个经验&#xff0c;希望能帮助…

【PMP备考】每日一练 - 2

1、一个建筑项目的项目经理发现&#xff0c;他管理的项目所在地附近正在新建一条新的水管线。公司政策要求&#xff0c;在他的团队继续完成这个项目之前&#xff0c;必须先填写一系列有关城市环境变化的表格。这是那两种情况的例子&#xff1f;&#xff08;选2个选项&#xff0…

【三】ObservableCollection 与 List 的区别

文章目录前言一、核心概念简介ObservableCollectionList二、关键差异对比三、典型使用场景ObservableCollection 的适用场景List 的适用场景四、在Community Toolkit MVVM中使用ObservableCollection<Data>和List<Data>场景1&#xff1a;动态列表&#xff08;Obser…

网安-SSRF-pikachu

目录 SSRF:Server-Side Request Forgery PHP curl PHP 可能引起SSRF的函数 PHP其他函数 CURL其他协议 SSRF利用&#xff1a; SSRF的发现 工具 SSRF的防御 pikachu-SSRF 一&#xff1a;curl 1.访问连接&#xff1a; 2.读取本地文件 3.dict协议扫描主机端口 二&…

在Centos系统上如何有效删除文件和目录的指令汇总

CentOS系统是一款开源的类Unix操作系统&#xff0c;极其亲和程序员和技术人员。这个系统最大的优势就是其高度自由化的特性&#xff0c;世界各地的开发者可以依照实际需求去修改和运行。在这个操作系统中&#xff0c;如果你想删除文件和目录&#xff0c;你可以使用各式各样的命…

Spring(四) 关于AOP的源码解析与思考

Spring&#xff08;四&#xff09; 关于AOP的源码解析与思考 每种语言都有其独特的机制和特点&#xff0c;那么说到Java你可能会首先想到反射&#xff0c;反射是Java语言提供的一种能够在程序运行时动态操作类或对象的能力&#xff0c;比如获取某个对象的类定义、获取类声明的属…

Android 15 Settings 搜索框:引入关键字过滤功能

在日常使用 Android 手机时,我们经常会用到“设置”应用中的搜索功能来快速定位所需选项。然而,有时搜索结果可能会包含一些我们不希望看到或者过于宽泛的条目。 本文将深入探讨这一变化,通过分析 SearchResultsAdapter.java 文件中的代码修改,揭示 Android 如何实现对特定…

Python-魔术方法-创建、初始化与销毁-hash-bool-可视化-运算符重载-容器和大小-可调用对象-上下文管理-反射-描述器-二分-学习笔记

序 欠4前年的一份笔记 &#xff0c;献给今后的自己。 魔术方法 特殊属性查看属性如果dir&#xff08;lobji&#xff09;参数obj包含方法 __dir__()&#xff0c;该方法将被调用。如果参数obj不包含__dir__()&#xff0c; 该方法将最大限度地收集参数信息。 dir()对于不同类型的对…

redis的一些疑问

spring集成redisCacheEvict(value "commonCache", key "#uniqueid_userInfo")什么时候会执行缓存移除呢&#xff1f;如果方法执行异常是否移除&#xff1f;如果缓存不存在还会移除么&#xff1f;这个移除会在redis的执行历史命令中监控到么&#xff1f;.…

3.检查函数 if (!CheckStart()) return 的妙用 C#例子

在桌面/WPF 开发中&#xff0c;我们经常需要在按钮事件里先判断“能不能做”&#xff0c;再决定“怎么做”。如果校验不过&#xff0c;就直接返回&#xff1b;校验通过&#xff0c;才继续执行业务逻辑。 今天分享一个极简写法&#xff1a;if (!CheckStart()) return;&#xff0…

炎热工厂救援:算法打造安全壁垒

高温天气下智慧工厂&#xff1a;算法赋能&#xff0c;安全救援无忧背景&#xff1a;极端高温下工厂的严峻挑战近年来&#xff0c;极端高温天气频发&#xff0c;部分地区气温接近甚至超过50℃。在这样酷热的环境中&#xff0c;工厂面临着诸多严峻问题。一方面&#xff0c;高温容…

pgsql模板是什么?

查找所有的数据库 select datname from pg_database运行该命令后&#xff0c;我们会发现其中出现了一些其它的数据库接下来&#xff0c;我们分析 template0 和 template1 的作用。template1 template1 是 PostgreSQL 默认用于创建新数据库的模板。当执行 CREATE DATABASE new_d…

LLM 不知道答案,但是知道去调用工具获取答案?

思考&#xff1a; LLM 自己“不知道”某个事实性问题的答案&#xff0c;但仍然能“知道”去调用工具获取正确答案&#xff0c;这听起来确实有点像个悖论该内容触及了大型语言模型&#xff08;LLM&#xff09;的核心局限性以及&#xff08;Agents&#xff09;的智能所在。实际上…

2025年7月11日学习笔记一周归纳——模式识别与机器学习

2025年7月11日学习笔记&一周归纳——模式识别与机器学习一.一周工作二.我的一些笔记汇总三.发现的一些新的学习资料和爱用好物1.百度网盘AI笔记&#xff1a;2.b站资料&#xff1a;3.听说的一些好书&#xff1a;一.一周工作 本周学习了清华大学张学工汪小我老师的模式识别与…

LeetCode 138题解 | 随机链表的复制

随机链表的复制一、题目链接二、题目三、分析四、代码一、题目链接 138.随机链表的复制 二、题目 三、分析 数据结构初阶阶段&#xff0c;为了控制随机指针&#xff0c;我们将拷贝结点链接在原节点的后面解决&#xff0c;后面拷贝节点还得解下来链接&#xff0c;非常麻烦。这…

【计算机存储架构】分布式存储架构

引言&#xff1a;数据洪流时代的存储革命“数据是新时代的石油” —— 但传统存储正成为制约数据价值释放的瓶颈核心矛盾&#xff1a;全球数据量爆炸增长&#xff1a;IDC预测2025年全球数据量将达175ZB&#xff08;1ZB10亿TB&#xff09;传统存储瓶颈&#xff1a;单机IOPS上限仅…

【Linux-云原生-笔记】数据库操作基础

一、什么是数据库&#xff1f;数据库就是一个有组织、可高效访问、管理和更新的电子化信息&#xff08;数据&#xff09;集合库。简单来说&#xff0c;数据库就是一个高级的Excel二、安装数据库并初始化1、安装数据库&#xff08;MySQL&#xff09;dnf search一下mysql数据库的…

HarmonyOS中各种动画的使用介绍

鸿蒙&#xff08;HarmonyOS&#xff09;提供了丰富的动画能力&#xff0c;涵盖属性动画、显式动画、转场动画、帧动画等多种类型&#xff0c;适用于不同场景的交互需求。以下是鸿蒙中各类动画的详细解析及使用示例&#xff1a;1. 属性动画&#xff08;Property Animation&#…