Linux:共享内存

文章目录

  • System V
  • 共享内存的原理
  • 管理共享内存
    • shmget
    • shmat
    • shmdt
    • shmctl
  • 共享内存和管道实现进程间同步通信

前面介绍完了匿名管道和命名管道,那么本篇要引入的主题是共享内存

System V

作为进程通信部分的内容,共享内存必然有其存在的意义和价值,例如对于网络来说,有了对应的服务端和客户端,那么在服务端中有了一个用户发送的消息,这些消息都被放到了管道中,之后经过加工后,要不然会选择把信息放到数据库中,要不然会选择把内容返回到上层,这是管道的作用,而对于System V版本的共享内存来说,也有它自己的作用

对于操作系统来说,通信的场景是有很多很多种的,有的是要传输数据为目的,有的是要以传输特定数据块为目的,也有的是以进程之间进行协同控制为目的,但是不管出于什么场景,只要是通信,那么就必然意味着要有固定的通信方式,就会有对应的接口参数返回值等等,再基于一定的格式进行统一,打包整体就叫做进程之间的通信,那么现在问题是,统一的格式是什么?哪里有格式的问题呢?为了解决这样的问题,有专门的人去定标准,再去基于这些标准进行具体的视线,实现出的这通信的模式就叫做System V,也叫做系统V,所以说对于这个模式来说有很多的通信方式,例如有共享内存,消息队列,信号量等等诸多方式,但是不管是什么模式,它们的接口,参数返回值,都是基于一定的标准来实现的,具有一定的相似性,这样对于使用者来说是比较方便的

所以接下来本篇介绍的内容就是这个System V模式下的第一种通信方式–共享内存

共享内存的原理

共享内存是基于通信的目的的,那么通信的本质是让不同的进程看到同一份资源,那么现在就要将注意点转移到同一份资源这个角度,这个资源不能是和进程挂钩,必须是操作系统与来提供的,所以在前面的管道中,不管是匿名管道还是命名管道,其实都是操作系统提供的,包括文件缓冲区和文件结构体,所以对于共享内存来说也是一样的道理,首先要创建出一个资源,其次是要让不同的进程看到这份资源,这就是共享内存的基本原理

在这里插入图片描述
上图是前面已经讲述过很多次的一个逻辑,对于共享内存也会从这里入手进行讲解,每一个进程都有自己的进程控制块,也有自己的地址空间,这个进程地址空间最后会根据页表映射到物理内存上,而又由于缺页中断申请内存这样的机制存在,所以说在代码中申请内存的时候,其实真正的物理内存没有进行开辟,而是在地址空间中开辟好,当首次尝试访问这块空间的时候,再去触发缺页中断这个机制,来在对应的物理内存中进行开辟内存,从中也能侧面看出,操作系统具有直接在内存中申请空间的能力

所以想要实现通信的第一步已经实现了,操作系统已经在物理内存中给进程开辟好了一块资源,可以等待它们使用,第二步要通信,起码是需要两个进程,在有了进程之后,就会在堆栈之间开辟一块区域,这块区域用来实现进程之间的通信

堆栈之间的这块共享区也不是第一次接触了,在之前的动静态库中就有过提及,动态库的加载就是加载到内存中,然后映射到堆栈之间的共享区中,进行加载库的这个过程,本质上就是把物理内存中的库数据映射到堆栈之间,只不过这里操作系统做的是在物理内存中开辟一块空间,而在逻辑地址的堆栈之间开辟的是一块新的空的空间,里面没有任何数据,而加载库的过程中这块区域是有空间的

在这里插入图片描述
在操作系统中,在物理内存中已经申请好了一块空间,并且也映射到了共享区中,那么这个共享区的起始地址也就已经知晓,所以在上层用户就可以调用这块空间,直接对这块空间进行数据的写入等等操作

关于用户空间和内核空间

那为什么说,有了这块起始地址,就能直接访问申请的内存了呢?其实原因就在于,有起始地址,并且整个空间有多大也是清楚的,那么未来就可以通过指针的方式,向对应的缓冲区中写数据,数据就会通过页表映射到对应的物理内存中,那在操作系统内部,是如何用地址空间对于物理内存进行访问?虽然现在已经有了虚拟地址,并且也有页表进行自动转换,但是为什么有地址就可以直接访问呢?原因就在内存中的用户空间,对于管道文件,它其实是属于内核数据结构,那么所有的缓冲区文件的属性都是会在这个地址空间的这个内核区域内,想要访问就必须调用对应的系统调用,而现在创建的这个堆栈之间的共享区,是属于用户空间的,用户空间是可以直接访问的,不用对应的系统调用就可以访问,相当于是有了一块内存空间,支持随机访问

在这里插入图片描述

所以现在进程就和共享内存之间建立了对应的映射关系,只要创建好内存,让当前进程把内存块映射到自己的地址空间中,那此时如果有另外一个进程,想要实现进程的通信,就也要进行相同的操作来进行映射,此时这个新的进程也会获得一个虚拟地址,对于这两个进程来说,它们的虚拟地址可以相同也可以不同,到此,这两个进程就都可以使用各自地址空间内的虚拟地址,借助页表来对物理内存中进行访问,相当于是间接的借助地址空间访问同一块内存空间,这样就通过共享内存的原理达到了进程间通信的前提,叫做让不同的进程看到同一份资源

共享内存的本质,在系统层面上把内存申请好,再映射到两个进程的地址空间中,映射结束之后,此时只需要把映射在虚拟地址中的起始地址返回给用户,用户就可以通过起始地址进行访问了

谈谈释放的问题

对于共享内存的释放,只需要把虚拟地址和物理地址之间的这层关系取消掉就可以了,具体的实操来说,就是把页表清空就可以,清空了页表,虚拟空间的所有地址就都失去了对应的意义,这也就是为什么说,所有的地址的概念,都是建立在有页表的基础上,如果没有页表,所有的地址其实都没有多大的意义,而对应与malloc或者是new申请的内存,也都是在页表上申请的,而物理内存并不需要立刻映射,而是在访问的时候再借助缺页中断来填充

管理共享内存

综合上述的内容,可以得出的一个结论是,在操作系统中,一定会存在多个共享内存被创建,在操作系统中会有很多个共享内存,那操作系统当然需要对于共享内存进行管理,那管理的前提是要描述,所以在操作系统内部一定会存在管理共享内存的概念,于是就有了下面的话题:管理共享内存

在管理之前,要有的第二个概念是,对于这块空间的识别问题,如何保证两个想要通信的进程可以识别到同一块资源呢?说明共享内存被创建出来之后,一定是具有一定的识别能力,有它独特的标识,才能让另外一个进程能够找到这块内存,进而进行后面的通信工作,所以下面就要研究这个识别的东西到底是什么

  1. 标识由什么组成?
  2. 怎么传递给另外一个进程?

Linux在内部提供了很多的接口,那么就要对于这些接口进行一定的认识了

shmget

在这里插入图片描述
这个命令就是创建共享内存的命令,对于参数的解析来说,抛开最前面的key值,对于第二个参数size来说,这个参数的意思是要开辟的共享内存有多大,函数的返回值会返回一个标识符,也就是内存标识符,如果创建失败会返回-1,并且会设置错误码,第三个参数是选项,不再多说,主要是介绍有两个选项

在这里插入图片描述
第一个选项的意思是,如果这个shm不存在就创建,存在就获取,并且返回
第二个选项不会单独使用,它一般会和第一个选项组合起来使用,表示的意思是,如果shm不存在就创建,存在就会提示出错并且返回,这样的意义是可以保证每次申请的共享内存都是全新的内存

这个内存标识符实际上就是前面所说的识别问题,这个整数的作用就有些类似于文件描述符的作用,在文件的接口中,都是靠这个文件描述符来工作,同理,在共享内存的接口中也是根据这个值来运转的

key值的问题

对于key值是多少,其实没有一个明确的标准,想写多少就写多少,在创建共享内存的时候,只要让通信的这两个进程之间约定一个数字,把这个数字作为标记符,那么在创建共享内存的时候,就会把这个数字表示写到共享内存的属性中,未来另外一个进程只需要在创建好的这些共享内存中去寻找这个标识符,就能找到前面的这个共享内存,进而就可以进行通信了

这个数字的取值是有讲究的,如果出现两个共享内存的key值相同,那必然是会出现严重的问题,所以对于这个key值的取值需要做足准备,而在接口中当然是有对应的解决措施的,这个函数就叫做ftok函数:

在这里插入图片描述
这个函数的作用就是专门用来,把一个地址和id转换成一个key值,所以借助这个东西就能生成一个不错的key值,以来区分内存

所以此时,对于创建共享内存的这个接口的参数就都介绍结束了,下面就实践一下,创建一个共享内存:

int main()
{int key = ftok("linux-system-and-network/shm", 1234);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);// 创建失败就返回if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}cout << "create success" << endl;return 0;
}

运行后,就会创建出一个共享内存了!

在这里插入图片描述
但是,如果再次异常会出错,提示现在已经有共享内存了,这说明一个结论,共享内存和文件不一样,打开的文件的生命周期是随进程的,进程结束这个打开的文件生命周期也就结束了,而共享内存是在System V当中单独设计出的用来进阶通信的方案,这个方案的特点是,共享内存必须让用户主动释放,也就是说如果不释放,这个内存就会一直存在,所以这里的结论是,共享内存以及未来的和通信有关的这些资源,和普通文件是不一样的,除非手动关闭,否则会一直存在

查看共享内存信息

ipcs -m

在这里插入图片描述
删除共享内存信息

ipcrm -m [shmid]

权限问题

在创建的时候,也可以在选项中带上权限的选项:

int main()
{int key = ftok("linux-system-and-network/shm", 1234);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);// 创建失败就返回if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}cout << "create success" << endl;return 0;
}

在这里插入图片描述

开辟空间大小的问题

正常来说,开辟空间是可以随意设置的,但是建议开辟大小设置成4096字节,原因在于,在操作系统申请内存的时候,是以4096为单位进行申请的,也就是说,如果申请大小为4097,实际上申请的是两个4096字节,只不过是用户层面上只用了4097个字节,即便未来被占用了,也不会进行分配,所以未来在越界方面可能会有异常,因此建议是以4096的整数倍进行大小的分配

shmat

在这里插入图片描述
这个命令可以将指定的共享内存挂接到自己的地址空间中

对于第二个参数shmaddr来说,这个shmaddr默认设置成nullptr就可以了,这个参数的意义是如果想要手动把共享内存挂接地址中的一个指定起始地址处,但是如果对于地址空间的不了解的情况下,直接传参传nullptr就可以了,让操作系统来进行选择就可以了

对于第三个参数shmflag来说,这个选项代表的是挂接到共享内存中的对应方式,这个不需要进行管控,因为在创建的时候就已经有权限来进行控制了,直接设置成0就可以了

对于返回值来说,如果挂接成功后,进程就会凭空多出来一块空间,有点类似于c语言中的malloc函数,所以在用法上也和它一样,需要进行强转成需要的类型

下面做出下面的实验:

int main()
{int key = ftok("linux-system-and-network/shm", 1234);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);// 创建失败就返回if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}cout << "create success" << endl;// 创建成功后就挂接到当前进程cout << "开始挂接" << endl;sleep(2);char *s = (char*)shmat(shmid, nullptr, 0);cout << "进程退出" << endl;sleep(2);return 0;
}

在这里插入图片描述
从中看出这也确实完成了初步的预期,在进程退出的时候会删除页表,页表清空后就随之解除了映射关系,这样共享内存也就随之没有关联的必要了,所以这里关联数就减去1即可,这里可以类比是一种引用计数,但是又不完全是,因为不会伴随着减到0而自动释放这段空间,所以只能算是继承了这样的一种思想

shmdt

在这里插入图片描述
这个命令的意思是去关联,其实也就是和上述的函数意思相反,要不然是在特定的空间内取消映射关系,最终的参数就是获得共享内存的起始地址,其实也就是上面这个函数的返回值,就可以用做这个函数的参数

如何理解这个过程呢?其实可以从关联的角度来讲,关联的角度就是修改页表,所以,只需要找到虚拟地址所对应的起始地址就可以了,虚拟地址的起始地址知道,并且共享内存的大小也知道,所以就可以从共享内存的起始地址释放空间,解除对应空间大小的关联关系,那么在这样的基础下,也就将整个的挂接关系都去掉了,所以就实现了解除关联的效果

int main()
{int key = ftok("linux-system-and-network/shm", 1234);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);// 创建失败就返回if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}cout << "create success" << endl;// 创建成功后就挂接到当前进程cout << "开始挂接" << endl;char *s = (char*)shmat(shmid, nullptr, 0);sleep(2);cout << "解除关联" << endl;shmdt(s);sleep(1);cout << "进程退出" << endl;sleep(2);return 0;
}

现象也比较简单,这里就不再展示了

shmctl

在这里插入图片描述
这个接口的作用就是删除指定的共享内存

在对于这个函数了解前,先从源码看一下共享内存:

在这里插入图片描述
在这里插入图片描述
这是在内核中关于共享内存的描述,上面的这个struct shmid_ds结构体中包含了的内容就有,挂接的时间,最后使用时间等等信息,而在其中的这个struct ipc_perm,其实描述的就是关于共享内存的属性信息,例如有key值和多种id的属性,而操作系统也是用这些信息来对共享内存进行管理的,操作系统对于共享内存的管理,就转换成了对于这些数据结构的管理

其实在前面用到的例如有ipcs -m命令或者是其他的删除命令,从本质上来说就是从系统中获取已经被创建的共享内存的属性,例如有共享内存的大小,最近的使用时间,有多少个挂接数,权限等等,都是从这里来的

那转回到我们的这个函数,对于这个函数来说,第一个参数不多解释,第二个参数一般是IPC_RMID,大致意思就可以理解为是立即删除的意思,第三个参数是一个结构体,这里暂时先设置为nullptr,未来有使用场景再继续补充

共享内存和管道实现进程间同步通信

有了上面的基础,对于代码进行简单的封装,可以得到如下的成品

// comm.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string pathname = "/home/test/linux-system-and-network/shm";
const int proj_id = 0x11223344;
const int size = 4096;
const std::string filename = "fifo";// 利用路径名和特定id获取key值
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);}return key;
}// 将十进制转换成十六进制
std::string ToHex(int id)
{char buffer[1024];snprintf(buffer, sizeof(buffer), "0x%x", id);return buffer;
}// 按照特定选项创建对应共享内存
int CreateShmHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag);if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}return shmid;
}// 以644权限创建共享内存
int CreateShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0644);
}// 获取共享权限的shmid
int GetShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT);
}// 创建一个命名管道
bool MakeFifo()
{int n = mkfifo(filename.c_str(), 0666);if (n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}
// client.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.hpp"int main()
{// 利用key值获取共享内存并挂接到程序共享区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(5);for (char c = 'a'; 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;
}
// 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;sleep(3);shmid = CreateShm(key);std::cout << "shmid: " << shmid << std::endl;sleep(10);std::cout << "开始将shm映射到进程的地址空间中" << std::endl;s = (char *)shmat(shmid, nullptr, 0);fd = open(filename.c_str(), O_RDONLY);}~Init(){sleep(5);shmdt(s);std::cout << "开始将shm从进程的地址空间中移除" << std::endl;sleep(5);shmctl(shmid, IPC_RMID, nullptr);std::cout << "开始将shm从OS中删除" << std::endl;close(fd);}public:int shmid;int fd;char *s;
};int main()
{// 创建共享内存和管道Init init;sleep(5);// 从共享内存中读取信息while (true){// waitint 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;
}

从上面的代码中可以看出,在进行进程间通信的时候,使用了一个命名管道,那这个命名管道的作用是什么呢?

这就要涉及到共享内存的同步机制了,共享内存本身是不会存在同步机制的,所以加装一个管道,可以造成的效果就是向共享内存写入数据后,必须要另外一个进程得到了共享内存的信息后,再得到管道的信息,这样就能借助管道的同步性,使得共享内存也有了一个模拟的同步机制

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

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

相关文章

【C/C++ 01】初级排序算法

排序算法通常是针对数组或链表进行排序&#xff0c;在C语言中&#xff0c;需要手写排序算法完成对数据的排序&#xff0c;排序规则通常为升序或降序&#xff08;本文默认为升序&#xff09;&#xff0c;在C中&#xff0c;<algorithm>头文件中已经封装了基于快排算法的 st…

51单片机通过级联74HC595实现倒计时秒表Protues仿真设计

一、设计背景 近年来随着科技的飞速发展&#xff0c;单片机的应用正在不断的走向深入。本文阐述了51单片机通过级联74HC595实现倒计时秒表设计&#xff0c;倒计时精度达0.05s&#xff0c;解决了传统的由于倒计时精度不够造成的误差和不公平性&#xff0c;是各种体育竞赛的必备设…

光学3D表面轮廓仪服务超精密抛光技术发展

随着技术的不断进步&#xff0c;精密制造领域对材料表面的处理要求越来越高&#xff0c;超精密抛光技术作为当下表面处理的尖端技术&#xff0c;对各种高精密产品的生产起到了至关重要的作用&#xff0c;已广泛应用于集成电路制造、医疗器械、航空航天、3C电子、汽车、精密模具…

Flutter 高级动画技术综合指南

在动画领域&#xff0c;Flutter 提供了一系列功能&#xff0c;包括基于物理的动画&#xff0c;可以模拟真实世界的动态&#xff0c;在应用程序中创建更逼真和自然的运动。 本文将深入研究 Flutter 动画&#xff0c;探索各种类型&#xff0c;并演示如何在项目中实现它们。 Flu…

Linux系统Shell脚本-----------正则表达式 、grep、 sed

一、正则表达式 1.前言 正则表达式(regular expression)描述了一种字符串匹配的模式&#xff08;pattern&#xff09;&#xff0c;可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。在Linux中也就是代表我们定义的模式模板&…

vue实现跳转传参查询

vue实现跳转传参查询&#xff1a; 应用场景&#xff1a;外部链接携参跳转目标页时,避免多次输入查询信息查询 目标需求&#xff1a;登录及非登录状态均可跳转自动查询 避坑指南&#xff1a;token失效时需要重新缓存及路由导航缓存判断 简单实现&#xff1a;缓存信息&#xff0c…

LLM之makeMoE:makeMoE的简介、安装和使用方法、案例应用之详细攻略

LLM之makeMoE&#xff1a;makeMoE的简介、安装和使用方法、案例应用之详细攻略 目录 makeMoE的简介 1、对比makemore 2、相关代码文件 makMoE_from_Scratch.ipynb文件 makeMoE_Concise.ipynb文件 makeMoE的安装和使用方法 1、基于Databricks使用单个A100进行开发 makeM…

线程锁多线程的复习

线程 实现方式3种乐观锁&悲观锁线程池线程池总结 进程:是正在运行的程序 线程:是进程中的单个顺序控制流,是一条执行路径 实现方式3种 1.Thread //步骤一:定义一个继承Thread的类 //步骤二:再定义的类中重写run()方法 //步骤三:创建定义类对象 //步骤四:启动线程 class M…

Armv8-M的TrustZone技术之在安全状态和非安全状态之间切换

Armv8-M安全扩展允许在安全和非安全软件之间直接调用。 Armv8-M处理器提供了几条指令来处理状态转换: 下图显示了安全状态转换。 如果入口点的第一条指令是SG且位于非安全可调用内存位置中,则允许从非安全到安全软件的直接API函数调用。 当非安全程序调用安全API时,API通过…

vue中的vuex

在Windows的应用程序开发中&#xff0c;我们习惯了变量&#xff08;对象&#xff09;声明和使用方式&#xff0c;就是有全局和局部之分&#xff0c;定义好了全局变量&#xff08;对象&#xff09;以后在其他窗体中就可以使用&#xff0c;但是窗体之间的变量&#xff08;对象&am…

20240129收获

今天终于发现《八部金刚功》第五部我一直做的是错的&#xff0c;嗨。这里这个写法非常聪明&#xff0c;创立的数组&#xff0c;以及用obj[key] item[key]这样的写法&#xff0c;这个写法充分展示了js常规写法中只有等号右边会去参与运算&#xff0c;等号左边就是普通的键的写法…

项目实现网页分享QQ空间功能

文章目录 &#x1f412;个人主页&#x1f3c5;Vue项目常用组件模板仓库&#x1f4d6;前言&#xff1a;&#x1f380;源码如下&#xff1a; &#x1f412;个人主页 &#x1f3c5;Vue项目常用组件模板仓库 &#x1f4d6;前言&#xff1a; 本篇博客主要提供“点击转发按钮&#x…

TensorFlow2实战-系列教程9:RNN文本分类1

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 1、文本分类任务 1.1 文本分类 数据集构建&#xff1a;影评数据集进行情感分析&…

Hana SQL+正则表达式

目录 一、Pre 前言 二、知识点拆解 1&#xff09;case when…then…else 2&#xff09;json_value 函数 拓展资料 3&#xff09;CAST 函数 拓展资料 4) ROUND 函数 5&#xff09;occurences_regexpr 函数 拓展资料 6&#xff09;正则表达式 拓展资料 三、整合分析…

10s 内得到一个干净、开箱即用的 Linux 系统

安装 使用官方脚本安装我的服务器不行 官方脚本 mkdir instantbox && cd $_ bash <(curl -sSL https://raw.githubusercontent.com/instantbox/instantbox/master/init.sh) 下面是我的完整安装过程 mkdir /opt/instantbox cd /opt/instantbox 1.脚本文件 (这个没…

[PHP]严格类型

PHP: 类型声明 - Manual

【学网攻】 第(15)节 -- 标准ACL访问控制列表

系列文章目录 目录 系列文章目录 文章目录 前言 一、ACL(访问控制列表)是什么? 二、实验 1.引入 实验拓扑图 实验配置 测试PC2能否Ping通PC3 配置ACL访问控制 实验验证 PC1 Ping PC3 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认…

Spring Boot导出EXCEL 文件

主要功能:实现java导出excel到本地 JDK版本&#xff1a;openJDK 20.0.1 依赖pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchem…

Java 面试题之 IO(一)

字节流 文章目录 字节流InputStream&#xff08;字节输入流&#xff09;OutputStream&#xff08;字节输出流&#xff09; 文章来自Java Guide 用于学习如有侵权&#xff0c;立即删除 InputStream&#xff08;字节输入流&#xff09; InputStream用于从源头&#xff08;通常是…

Centos Cron设置定时任务

这本是很简单的问题&#xff0c;但是我服务器重装系统两次&#xff0c;遇到的问题都不一样&#xff0c;所以记录一下 1.首先要确保服务器上有 cron 服务 sudo systemctl status crond2.设置时区 sudo timedatectl set-timezone Asia/Shanghai3.重启crond 服务使crond服务的时…