当前位置: 首页 > news >正文

CTF-虚拟机-QEMU-前置知识-操作流程与源码阅读

文章目录

  • 总览
  • 内存
  • PCI设备
    • PCI配置空间前64个字节
    • 对应源码
    • Memorry空间的BAR
    • IO空间的BAR
  • MMIO
  • PMIO
  • Ispci
  • 访问PCI设备配置空间中的Memory空间和IO空间
    • MMIO
    • PMIO
  • QQM(qemu object model)
  • 简洁概要
    • 将 TypeInfo 注册 TypeImpl:
    • ObjectClass的初始化:
    • 实例化 Instance(Object)
    • 准备自己写mini版QEMU吧,不然实在迷糊

吹爆这篇博客,写得巨好

总览

QEMU能够为用户进程进行CPU仿真提供环境
一个QEMU进程提供一种环境可启动一个虚拟机

KVM是在内核中运行的,让QEMU启动的虚拟机能直接在host的CPU上安全地执行guest的代码,作用为负责虚拟机的创建,虚拟内存的分配,虚拟CPU

// 第一步,获取到 KVM 句柄
kvmfd = open("/dev/kvm", O_RDWR);
// 第二步,创建虚拟机,获取到虚拟机句柄。
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
// 第三步,为虚拟机映射内存,还有其他的 PCI,信号处理的初始化。
ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem);
// 第四步,将虚拟机镜像映射到内存,相当于物理机的 boot 过程,把镜像映射到内存。
// 第五步,创建 vCPU,并为 vCPU 分配内存空间。
ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid);
vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
// 第五步,创建 vCPU 个数的线程并运行虚拟机。
ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
// 第六步,线程进入循环,并捕获虚拟机退出原因,做相应的处理。
for (;;) {ioctl(KVM_RUN)switch (exit_reason) {case KVM_EXIT_IO:  /* ... */case KVM_EXIT_HLT: /* ... */}
}
// 这里的退出并不一定是虚拟机关机,
// 虚拟机如果遇到 I/O 操作,访问硬件设备,缺页中断等都会退出执行,
// 退出执行可以理解为将 CPU 执行上下文返回到 Qemu。

退出时判断原因,可能由KVM执行也有可能由QEMU执行

内存

可以这么认为,guest所使用的物理内存,实际上是对应的启动它的那个QEMU的虚拟内存的一部分。即该部分可能是对应gust的物理内存是从0开始的(guest视角)

两层转换

  1. 从guest的虚拟地址转换到guest的物理地址
    相当于从页表得到物理地址
  2. 从guest的物理地址转换到host的QEMU进程中的虚拟地址
    该物理地址再加上guest对应在host的QEMU进程中的虚拟地址中起始地址的就是对应的host的虚拟地址了

第一层转换。用pagemap的页面映射文件来转换

  1. 虚拟地址对应的pagemap中的偏移(此时为pagemap中第几个)乘8可得到在pagemap中的偏移(此时为pagemap中对应的地址)

在这里插入图片描述

  1. 读取后判断内容是否存在并且判断最高位是否1,为1则代表页面存在,然后将读取的内容左移12位得到低52位(物理页的地址)再或上原虚拟地址的低12位的页内偏移就是guest的物理地址了

用QEMU运行下列代码

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)int fd;
// 获取页内偏移
uint32_t page_offset(uint32_t addr)
{// addr & 0xfffreturn addr & ((1 << PAGE_SHIFT) - 1);
}uint64_t gva_to_gfn(void *addr)
{uint64_t pme, gfn;size_t offset;printf("pfn_item_offset : %p\n", (uintptr_t)addr >> 9);offset = ((uintptr_t)addr >> 9) & ~7;下面是网上其他人的代码,只是为了理解上面的代码//一开始除以 0x1000  (getpagesize=0x1000,4k对齐,而且本来低12位就是页内索引,需要去掉),即除以2**12, 这就获取了页号了,//pagemap中一个地址64位,即8字节,也即sizeof(uint64_t),所以有了页号后,我们需要乘以8去找到对应的偏移从而获得对应的物理地址//最终  vir/2^12 * 8 = (vir / 2^9) & ~7 //这跟上面的右移9正好对应,但是为什么要 & ~7 ,因为你  vir >> 12 << 3 , 跟vir >> 9 是有区别的,vir >> 12 << 3低3位肯定是0,所以通过& ~7将低3位置0// int page_size=getpagesize();// unsigned long vir_page_idx = vir/page_size;// unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);lseek(fd, offset, SEEK_SET);read(fd, &pme, 8);// 确保页面存在——page is present.if (!(pme & PFN_PRESENT)) //同时判断return -1;// physical frame number gfn = pme & PFN_PFN; //取低52位return gfn;
}uint64_t gva_to_gpa(void *addr)
{uint64_t gfn = gva_to_gfn(addr);assert(gfn != -1);return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);//合并
}int main()
{uint8_t *ptr;uint64_t ptr_mem;fd = open("/proc/self/pagemap", O_RDONLY);if (fd < 0) {perror("open");exit(1);}ptr = malloc(256);strcpy(ptr, "Where am I?");printf("%s\n", ptr); //此时ptr是guest中虚拟地址ptr_mem = gva_to_gpa(ptr); //此时转换成了guest中物理地址printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);getchar();return 0;
}

此时

 printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);ptr_mem输出为0x68cf00100x7fcddc000000  为guest的物理地址在host视角下的起始地址0x7fcddc000000+0x68cf0010即对应where am I?

PCI设备

PCI是一个外部链接(Peripheral Component Interconnect)标准,PCI设备就是符合这个标准的设备,且连接到PCI总线上。而PCI总线是CPU与外部设备沟通的桥梁。

符合 PCI 总线标准的设备就被称为 PCI 设备
PCI 设备同时也分为主设备和目标设备两种,主设备是一次访问操作的发起者,而目标设备则是被访问者。

每个PCI设备对应备一个PCI配置空间(PCI Configuration Space),它记录了关于此设备的信息。PCI配置空间最大256个字节,其中前64字节都是预定义好的标准。

PCI配置空间前64个字节

在这里插入图片描述

对应源码

typedef struct {WORD   wBusNum;		// Bus No. input fieldWORD   wDeviceNum;		// Device No. input fieldWORD   wFunction;		// Function No. input fieldWORD   wVendorId;		// Vendor ID input fieldWORD   wDeviceId;		// Device ID input fieldWORD   wDeviceIndex; 	// Device Search No. input fieldWORD   wCommand;             // CommandWORD   wClassId; 		// Class IDBYTE   byInterfaceId;        // Interface IDBYTE   byRevId;              // Revision IDBYTE   byCLS;                // Cache Line SizeBYTE   byLatency;            // Latency TimerDWORD  dwBaseAddr[6];        // 6个Base Address Register为32位DWORD  dwCIS;WORD   wSubSystemVendorId;WORD   wSubSystemId;DWORD  dwRomBaseAddr;        // Extension ROM Base AddressBYTE   byIntLine;            // Interrupt LineBYTE   byIntPin;             // Interrupt Pin BYTE   byMaxLatency;         // Max LatencyBYTE   byMinGrant;           // Min Grant} PCIDEV, *LPPCIDEV;

6个BAR,每个BAR记录了该设备映射的一段地址空间,有Memorry空间和IO空间

Memorry空间的BAR

在这里插入图片描述
第0位为0,表示该为Memorry空间
第1位为0表示32位地址,为1表示64位地址
第2为为0表示区间大小超过1M,为0表示不超过1M
第3位表示是否支持可预读取

IO空间的BAR

在这里插入图片描述第0位为1,表示该为IO空间

MMIO

内存映射io,和内存共享一个地址空间。可以和像读写内存一样读写其内容。
通过Memory 空间访问设备I/O的方式称为memory mapped I/O,即MMIO,这种情况下,CPU直接使用普通访存指令即可访问设备I/O。

PMIO

端口映射io,内存和io设备有各自独立的地址空间,cpu需要通过专门的指令才能去访问。在intel的微处理器中使用的指令是IN和OUT。

通过I/O 空间访问设备I/O的方式称为port mapped I/O,即PMIO,这种情况下CPU需要使用专门的I/O指令如IN/OUT访问I/O端口

Ispci

pci外设地址,形如0000:00:1f.1。第一个部分16位表示域;第二个部分8位表示总线编号;第三个部分5位表示设备号;最后一个部分3位表示功能号。
lspci 命令可以显示当前的pci设备

在这里插入图片描述

lspci -v可以显示当前的pci设备的详细信息,如mmio的地址,pmio的端口号
在这里插入图片描述

lspci -v -m -n -s 设备可以显示头部的一些信息
在这里插入图片描述

/sys/bus/pci/devices可以找到pci设备相关的文件。
在这里插入图片描述
/sys/devices/pci0000:00也可以找到pci设备的相关的文件
在这里插入图片描述
查看设备id是device文件
cat /sys/devices/pci0000:00/0000:00:03.0/device
在这里插入图片描述

随便进入一个pci设备文件用ls查看
在这里插入图片描述

在这里插入图片描述
每个设备的目录下resource0 对应MMIO空间。resource1 对应PMIO空间。(不是所有设备文件都有resource0或者resource1)
resource文件里面会记录相关的数据,第一行就是MIMO的信息,从左到右是:起始地址、结束地址、标识位。第二行是PMIO

I/O 内存:/proc/iomem
I/O 端口:/proc/ioports
使用cat /proc/iomem可查看当前PCI设备的映射内存空间
使用cat /proc/ioports可查看当前PCI设备的映射端口空间

访问PCI设备配置空间中的Memory空间和IO空间

MMIO

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";unsigned char* mmio_base;unsigned char* getMMIOBase(){int fd;if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {perror("open pci device");exit(-1);}mmio_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//根据resource0中的文件内容来分配Memory空间if(mmio_base == (void *) -1) {perror("mmap");exit(-1);}return mmio_base;
}void mmio_write(uint64_t addr, uint64_t value)
{*((uint64_t*)(mmio_base + addr)) = value;
}uint64_t mmio_read(uint64_t addr)
{return *((uint64_t*)(mmio_base + addr));
}int main(int argc, char const *argv[])
{getMMIOBase();printf("mmio_base Resource0Base: %p\n", mmio_base);mmio_write(144, val);mmio_read(144);return 0;
}

PMIO

需要权限才能访问端口
0x000-0x3ff端口可以用ioperm(from, num, turn_on)获得权限
比如ioperm(0x300,5,1); 获得 0x300 到 0x304 端口的访问权限
更高端口需要iopl(3)获得权限,这个可以获得范围所有端口权限
in,out系列函数如下,分别是写入/读取一个字节(b结尾),两个字节(w结尾),四个字节(l结尾)

#include <sys/io.h >iopl(3); 
inb(port); 
inw(port); 
inl(port);outb(val,port); 
outw(val,port); 
outl(val,port);

QQM(qemu object model)

QEMU提供了一套面向对象编程的模型——QOM,即QEMU Object Module,几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。
而对象的初始化分为四步:

  1. 将 TypeInfo 注册 TypeImpl
  2. 实例化 ObjectClass
  3. 实例化 Object
  4. 添加 Property

ObjectClass: 是所有类对象的基类,仅仅保存了一个整数 type 。
Object: 是所有对象的 基类Base Object , 第一个成员变量为指向 ObjectClass 的指针。
TypeInfo:是用户用来定义一个 Type 的工具型的数据结构。
TypeImpl:对数据类型的抽象数据结构,TypeInfo的属性与TypeImpl的属性对应。

简洁概要

将 TypeInfo 注册 TypeImpl:

1、首先__attribute__((constructor))的修饰让type_init在main之前执行,type_init的参数是XXX_register_types函数指针,将函数指针传递到ModuleEntry的init函数指针,最后就是将这个ModuleEntry插入到ModuleTypeList
2、main函数中的module_call_init(MODULE_INIT_QOM);调用了MODULE_INIT_QOM类型的ModuleTypeList中的所有ModuleEntry中的init()函数,也就是第一步type_init的第一个参数XXX_register_types函数指针
3、那就下了就是XXX_register_types函数的操作了,就是创建TypeImpl的哈希表

ObjectClass的初始化:

调用链main->select_machine->object_class_get_list->object_class_foreach->object_class_foreach_tramp->type_initialize

将parent->class->interfaces的一些信息添加到ti->class->interfaces列表上面,ti->interfaces[i].typename对应的type的信息也添加到ti->class->interfaces列表,最后最重要的就是调用parent的class_base_init进行初始化,最后调用自己ti->class_init进行初始化。

实例化 Instance(Object)

调用链qemu_opts_foreach->device_init_func->qdev_device_add->object_new->object_new_with_type

object_new_with_type函数里面初始化了Object的一些成员,并通过object_init_with_type函数调用ti->instance_init函数(有parent就会先递归调用object_init_with_type,再调用自身的ti->instance_init函数),而最后就是通过object_post_init_with_type函数差不多,只不过先调用自身的ti->instance_post_init,再递归调用parent的ti->instance_post_init

准备自己写mini版QEMU吧,不然实在迷糊

相关文章:

CTF-虚拟机-QEMU-前置知识-操作流程与源码阅读

文章目录 总览内存PCI设备PCI配置空间前64个字节对应源码Memorry空间的BARIO空间的BAR MMIOPMIOIspci访问PCI设备配置空间中的Memory空间和IO空间MMIOPMIO QQM&#xff08;qemu object model&#xff09;简洁概要将 TypeInfo 注册 TypeImpl&#xff1a;ObjectClass的初始化&…...

java成神秘籍第一卷

前言 适合还没有入行小白学习&#xff0c;有些朋友会跑来问我这行的一些问题&#xff0c;下面算是详细系统的整理了一下啦。 全当是学习 复盘 整理 记录了 java成神秘籍第一卷 前言一 前提1 要不要考公&#xff0c;考编&#xff0c;考研2 语言选择3 就业岗位4 目标5 考不考虑…...

golang实现文件上传(高并发+分块+断点续传+加密)

运行视频 // todo 根据前端传递文件加密 func (s *FileProcess) FileProcessEncryptionByFront(file multipart.File, h *multipart.FileHeader) interface{} { //根据字节直接处理文件 这个是前端传递的二进制流s.FileProcessInit() //文件初始化 设置原来文件…...

用HeidiSQL在MySQL中新建用户

用HeidiSQL登录到MySQL数据库&#xff0c;注意登录的时候要使用有权限的用户&#xff1a; 选择工具-》用户管理&#xff1a; 点击左上角的“添加”&#xff1a; 输入用户名、密码&#xff0c;并且分配权限&#xff1a; 点击右边的“添加对象”&#xff1a; 可以根据自己…...

【IPv6】IPv6协议

一、IPv6数据报格式 这是与v4报头的对比 1.8bit的版本保留了&#xff0c;v4版本就是4&#xff0c;v6就是6。 2.v6去除了v4的首部长度字段&#xff0c;因为v6的首部长是固定的40字节。 3.服务类型&#xff08;Type of Service, ToS&#xff09;和通信类型&#xff08;Traffi…...

无需服务器,无需魔法,拥有一个微信机器人就是这么简单

前情提要 还没看过的朋友可以看一下上一篇文章《拥有一个微信机器人总共需要几步&#xff1f;》在这篇文章里&#xff0c;我们提到&#xff0c;创建微信机器人需要一个大前提--你得有一台服务器。现在&#xff0c;不再需要了&#xff01;没错&#xff0c;上一篇提到的Serverles…...

1、命名空间、C++的复合类型、缺省参数

命名空间 1、命名空间的定义 使用namespace定义&#xff0c;使用作用域限定符::访问 #include <iostream> namespace ICBC{int money 0;void save( int m){money m;} } int main( void ){ICBC::save( 100); std::cout << "工行卡余额&#xff1a;"…...

colab notebook导出为PDF

目录 方法一&#xff1a;使用浏览器打印功能 方法二&#xff1a;使用nbconvert转换 方法三&#xff1a;在线转换 方法一&#xff1a;使用浏览器打印功能 一般快捷键是CTRLP 然后改变目标打印机为另存为PDF 这样就可以将notebook保存为PDF了 方法二&#xff1a;使用nbconver…...

【Python动漫系列】名侦探柯南(完整代码)

文章目录 名侦探柯南环境需求完整代码程序分析系列文章名侦探柯南 《名侦探柯南》是由青山刚昌创作的一部侦探漫画,于1994年开始连载,并被改编为动画、电影、游戏等多种形式。故事讲述了高中生侦探工藤新一在破案时被不良组织所毒害,身体缩小成了一个小学生,为了寻找解药并…...

【matlab】QR分解

QR分解 给定一个mn的矩阵A&#xff0c;其中m≥n&#xff0c;即矩阵A是高矩阵或者是方阵&#xff0c;QR分解将矩阵A分解为两个矩阵Q和R的乘积&#xff0c;其中矩阵Q是一个mn的各列正交的矩阵&#xff0c;即QTQI&#xff0c;矩阵R是一个nn的上三角矩阵&#xff0c;其对角线元素为…...

Liunx系统使用超详细(三)

本篇内容开始逐渐描述有关liunx的各种命令的使用方法&#xff01; 目录 一、目录和文件区别 1.1目录&#xff1a; 1.2文件&#xff1a; 1.3总结&#xff1a; 二、Linux命令的写法 三、linux命令清屏 四、pwd命令 五、ls命令 5.1 ls&#xff1a; 5.2 ls -l&#xff1a…...

Kubernetes学习笔记-Part.06 Docker安装

目录 Part.01 Kubernets与docker Part.02 Docker版本 Part.03 Kubernetes原理 Part.04 资源规划 Part.05 基础环境准备 Part.06 Docker安装 Part.07 Harbor搭建 Part.08 K8s环境安装 Part.09 K8s集群构建 Part.10 容器回退 第六章 Docker安装 在master、worker、harbor上均需…...

现在的00后,实在是太卷了......

现在的小年轻真的卷得过分了。前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…...

Maven项目目录结构

项目结构 目录说明.ideaIDEA工具的配置文件.mvn用于运行Maven项目src源码文件夹target字节码文件夹.gitignore配置git忽略文件HELP.md自述文件mvnw运行Maven命令&#xff08;Linux&#xff09;mvnw.cmd运行Maven命令&#xff08;Windows&#xff09;pom.xml依赖管理文件 如图…...

感慨!一路从测试小白到现在的高级测试工程师,方向不对真的很艰辛

1 功能测试人员&#xff0c;也就是我们常常俗称的进行点点点测试工程师。前 1-2 年做功能测试&#xff0c;使用其他人开发的工具进行测试。这个阶段的测试工作都比较初级&#xff0c;严重依赖于别人开发的工具和系统。当系统进行升级换代的时候&#xff0c;强哥会明显的感觉到…...

字符串经典基础面试题

关卡名 字符串经典基础面试题 我会了✔️ 内容 1.理解字符串反转的处理方法 ✔️ 2.熟练掌握回文串的判断方法 ✔️ 3.掌握字符串中搜索第一个唯一字符的方法 ✔️ 4.掌握判断是否互为字符串重排的处理技巧 ✔️ 1 反转的问题 我们知道反转是链表的一个重要考点&#xf…...

【华为OD题库-062】计算礼品发放的最小分组数目-java

题目 又到了一年的末尾&#xff0c;项目组让小明负责新年晚会的小礼品发放工作。为使得参加晚会的同时所获得的小礼品价值相对平衡&#xff0c;需要把小礼品根据价格进行分组&#xff0c;但每组最多只能包括两件小礼品&#xff0c;并且每个分组的价格总和不能超过一个价格上限。…...

[go 面试] 构建高效微服务通信:选择合适的通信方式

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 构建分布式系统或微服务架构时&#xff0c;服务间通信成为至关重要的一环。不同的通信方式各有优劣&#xff0c;因此在选择时需根…...

【华为OD题库-048】拔河比赛-java

题目 公司最近准备进行拔河比赛&#xff0c;需要在全部员工中进行挑选。选拔的规则如下: 1.按照身高优先、体重次优先的方式准备比赛阵容 2.规定参赛的队伍派出10名选手 请实现一个选拔队员的小程序。 输入为一个数组&#xff0c;记录了部门人员的身高、体重信息&#xff0c;如…...

【WebSocket】通信协议基于 node 的简单实践和心跳机制和断线重连的实现

前后端 WebSocket 连接 阮一峰大佬 WebSocket 技术博客 H5 中提供的 WebSocket 协议是基于 TCP 的全双工传输协议。它属于应用层协议&#xff0c;并复用 HTTP 的握手通道。它只需要一次握手就可以创建持久性的连接。 那么什么是全双工呢&#xff1f; 全双工是计算机网络中的…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...