HyperPlatform
之前也写过一个vt的框架,但是比较简单,写的比较乱迁移什么的比较麻烦,于是阅读下HyperPlatform的源码学习下。
本文只对主体框架分析。
vt的流程大概如下
1:检测是否支持VT。
2:vmxon。
3:vmclear.
4:vmptrload。
5:vmcs。
6:vmlaunch
7:vmclear
8:vmoff
VmInitialization
首先是关键的VMinit
检测vt是否已被占用
Use_decl_annotations_ static bool VmpIsHyperPlatformInstalled() {
PAGED_CODE()
int cpu_info[4] = {};
__cpuid(cpu_info, 1);
const CpuFeaturesEcx cpu_features = {static_cast<ULONG32>(cpu_info[2])};
if (!cpu_features.fields.not_used) {
return false;
}
__cpuid(cpu_info, kHyperVCpuidInterface);
return cpu_info[0] == 'PpyH';
}
检测是否支持VT
// Checks if the system supports virtualization
_Use_decl_annotations_ static bool VmpIsVmxAvailable() {
PAGED_CODE()
// See: DISCOVERING SUPPORT FOR VMX
// If CPUID.1:ECX.VMX[bit 5]=1, then VMX operation is supported.
int cpu_info[4] = {};
__cpuid(cpu_info, 1);
const CpuFeaturesEcx cpu_features = {static_cast<ULONG32>(cpu_info[2])};
if (!cpu_features.fields.vmx) {
HYPERPLATFORM_LOG_ERROR("VMX features are not supported.");
return false;
}
......
Ia32FeatureControlMsr vmx_feature_control = {
UtilReadMsr64(Msr::kIa32FeatureControl)};
if (!vmx_feature_control.fields.enable_vmxon) {
HYPERPLATFORM_LOG_ERROR("VMX features are not enabled.");
return false;
}
......
return true;
}
检查都通过后进入正题调用VmpStartVm
VmpStartVm
VmpInitializeVm
分配VMXON内存
processor_data->vmxon_region =
static_cast<VmControlStructure *>(ExAllocatePoolZero(
NonPagedPool, kVmxMaxVmcsSize, kHyperPlatformCommonPoolTag));
if (!processor_data->vmxon_region) {
VmpFreeProcessorData(processor_data);
return;
}
RtlZeroMemory(processor_data->vmxon_region, kVmxMaxVmcsSize);
分配VMCS内存
processor_data->vmcs_region =
static_cast<VmControlStructure *>(ExAllocatePoolZero(
NonPagedPool, kVmxMaxVmcsSize, kHyperPlatformCommonPoolTag));
if (!processor_data->vmcs_region) {
VmpFreeProcessorData(processor_data);
return;
}
RtlZeroMemory(processor_data->vmcs_region, kVmxMaxVmcsSize);
分配VMM stack内存
processor_data->vmm_stack_limit =
UtilAllocateContiguousMemory(KERNEL_STACK_SIZE);
if (!processor_data->vmm_stack_limit) {
VmpFreeProcessorData(processor_data);
return;
}
RtlZeroMemory(processor_data->vmm_stack_limit, KERNEL_STACK_SIZE);
VmpEnterVmxMode
对应VMXON,修正cr0和cr4寄存器的值,在上面申请的VMXON内存中写入版本号,然后执行vmxon。
_Use_decl_annotations_ static bool VmpEnterVmxMode(
ProcessorData *processor_data) {
PAGED_CODE()
// Apply FIXED bits
// See: VMX-FIXED BITS IN CR0
// IA32_VMX_CRx_FIXED0 IA32_VMX_CRx_FIXED1 Meaning
// Values 1 * bit of CRx is fixed to 1
// Values 0 1 bit of CRx is flexible
// Values * 0 bit of CRx is fixed to 0
const Cr0 cr0_fixed0 = {UtilReadMsr(Msr::kIa32VmxCr0Fixed0)};
const Cr0 cr0_fixed1 = {UtilReadMsr(Msr::kIa32VmxCr0Fixed1)};
Cr0 cr0 = {__readcr0()};
Cr0 cr0_original = cr0;
cr0.all &= cr0_fixed1.all;
cr0.all |= cr0_fixed0.all;
__writecr0(cr0.all);
HYPERPLATFORM_LOG_DEBUG("IA32_VMX_CR0_FIXED0 = %08Ix", cr0_fixed0.all);
HYPERPLATFORM_LOG_DEBUG("IA32_VMX_CR0_FIXED1 = %08Ix", cr0_fixed1.all);
HYPERPLATFORM_LOG_DEBUG("Original CR0 = %08Ix", cr0_original.all);
HYPERPLATFORM_LOG_DEBUG("Fixed CR0 = %08Ix", cr0.all);
// See: VMX-FIXED BITS IN CR4
const Cr4 cr4_fixed0 = {UtilReadMsr(Msr::kIa32VmxCr4Fixed0)};
const Cr4 cr4_fixed1 = {UtilReadMsr(Msr::kIa32VmxCr4Fixed1)};
Cr4 cr4 = {__readcr4()};
Cr4 cr4_original = cr4;
cr4.all &= cr4_fixed1.all;
cr4.all |= cr4_fixed0.all;
__writecr4(cr4.all);
HYPERPLATFORM_LOG_DEBUG("IA32_VMX_CR4_FIXED0 = %08Ix", cr4_fixed0.all);
HYPERPLATFORM_LOG_DEBUG("IA32_VMX_CR4_FIXED1 = %08Ix", cr4_fixed1.all);
HYPERPLATFORM_LOG_DEBUG("Original CR4 = %08Ix", cr4_original.all);
HYPERPLATFORM_LOG_DEBUG("Fixed CR4 = %08Ix", cr4.all);
// Write a VMCS revision identifier
const Ia32VmxBasicMsr vmx_basic_msr = {UtilReadMsr64(Msr::kIa32VmxBasic)};
processor_data->vmxon_region->revision_identifier =
vmx_basic_msr.fields.revision_identifier;
auto vmxon_region_pa = UtilPaFromVa(processor_data->vmxon_region);
if (__vmx_on(&vmxon_region_pa)) {
return false;
}
// See: Guidelines for Use of the INVVPID Instruction, and Guidelines for Use
// of the INVEPT Instruction
UtilInveptGlobal();
UtilInvvpidAllContext();
return true;
}
VmpInitializeVmcs
对应vmclear和vmptrload。向上面申请的VMCS内存区写入版本号,然后执行__vmx_vmclear和__vmx_vmptrld。
_Use_decl_annotations_ static bool VmpInitializeVmcs(
ProcessorData *processor_data) {
PAGED_CODE()
// Write a VMCS revision identifier
const Ia32VmxBasicMsr vmx_basic_msr = {UtilReadMsr64(Msr::kIa32VmxBasic)};
processor_data->vmcs_region->revision_identifier =
vmx_basic_msr.fields.revision_identifier;
auto vmcs_region_pa = UtilPaFromVa(processor_data->vmcs_region);
if (__vmx_vmclear(&vmcs_region_pa)) {
return false;
}
if (__vmx_vmptrld(&vmcs_region_pa)) {
return false;
}
// The launch state of current VMCS is "clear"
return true;
}
VmpSetupVmcs
对应VMCS。这也是整个框架中最复杂的地方。这一步主要的目的是设置环境以便使虚拟机可以正常运行和退出。
太长了不全分析。
设置GUEST区域
error |= UtilVmWrite(VmcsField::kGuestEsSelector, AsmReadES());
error |= UtilVmWrite(VmcsField::kGuestCsSelector, AsmReadCS());
error |= UtilVmWrite(VmcsField::kGuestSsSelector, AsmReadSS());
error |= UtilVmWrite(VmcsField::kGuestDsSelector, AsmReadDS());
error |= UtilVmWrite(VmcsField::kGuestFsSelector, AsmReadFS());
error |= UtilVmWrite(VmcsField::kGuestGsSelector, AsmReadGS());
error |= UtilVmWrite(VmcsField::kGuestLdtrSelector, AsmReadLDTR());
error |= UtilVmWrite(VmcsField::kGuestTrSelector, AsmReadTR());
......
error |= UtilVmWrite64(VmcsField::kVmcsLinkPointer, MAXULONG64);
error |= UtilVmWrite64(VmcsField::kGuestIa32Debugctl, UtilReadMsr64(Msr::kIa32Debugctl));
.......
error |= UtilVmWrite(VmcsField::kGuestEsLimit, GetSegmentLimit(AsmReadES()));
error |= UtilVmWrite(VmcsField::kGuestCsLimit, GetSegmentLimit(AsmReadCS()));
error |= UtilVmWrite(VmcsField::kGuestSsLimit, GetSegmentLimit(AsmReadSS()));
error |= UtilVmWrite(VmcsField::kGuestDsLimit, GetSegmentLimit(AsmReadDS()));
error |= UtilVmWrite(VmcsField::kGuestFsLimit, GetSegmentLimit(AsmReadFS()));
error |= UtilVmWrite(VmcsField::kGuestGsLimit, GetSegmentLimit(AsmReadGS()));
error |= UtilVmWrite(VmcsField::kGuestLdtrLimit, GetSegmentLimit(AsmReadLDTR()));
error |= UtilVmWrite(VmcsField::kGuestTrLimit, GetSegmentLimit(AsmReadTR()));
error |= UtilVmWrite(VmcsField::kGuestGdtrLimit, gdtr.limit);
error |= UtilVmWrite(VmcsField::kGuestIdtrLimit, idtr.limit);
error |= UtilVmWrite(VmcsField::kGuestEsArBytes, VmpGetSegmentAccessRight(AsmReadES()));
error |= UtilVmWrite(VmcsField::kGuestCsArBytes, VmpGetSegmentAccessRight(AsmReadCS()));
error |= UtilVmWrite(VmcsField::kGuestSsArBytes, VmpGetSegmentAccessRight(AsmReadSS()));
error |= UtilVmWrite(VmcsField::kGuestDsArBytes, VmpGetSegmentAccessRight(AsmReadDS()));
error |= UtilVmWrite(VmcsField::kGuestFsArBytes, VmpGetSegmentAccessRight(AsmReadFS()));
error |= UtilVmWrite(VmcsField::kGuestGsArBytes, VmpGetSegmentAccessRight(AsmReadGS()));
error |= UtilVmWrite(VmcsField::kGuestLdtrArBytes, VmpGetSegmentAccessRight(AsmReadLDTR()));
error |= UtilVmWrite(VmcsField::kGuestTrArBytes, VmpGetSegmentAccessRight(AsmReadTR()));
error |= UtilVmWrite(VmcsField::kGuestSysenterCs, UtilReadMsr(Msr::kIa32SysenterCs));
......
error |= UtilVmWrite(VmcsField::kCr0GuestHostMask, cr0_mask.all);
error |= UtilVmWrite(VmcsField::kCr4GuestHostMask, cr4_mask.all);
error |= UtilVmWrite(VmcsField::kCr0ReadShadow, cr0_shadow.all);
error |= UtilVmWrite(VmcsField::kCr4ReadShadow, cr4_shadow.all);
......
error |= UtilVmWrite(VmcsField::kGuestCr0, __readcr0());
error |= UtilVmWrite(VmcsField::kGuestCr3, __readcr3());
error |= UtilVmWrite(VmcsField::kGuestCr4, __readcr4());
......
error |= UtilVmWrite(VmcsField::kGuestEsBase, 0);
error |= UtilVmWrite(VmcsField::kGuestCsBase, 0);
error |= UtilVmWrite(VmcsField::kGuestSsBase, 0);
error |= UtilVmWrite(VmcsField::kGuestDsBase, 0);
error |= UtilVmWrite(VmcsField::kGuestFsBase, UtilReadMsr(Msr::kIa32FsBase));
error |= UtilVmWrite(VmcsField::kGuestGsBase, UtilReadMsr(Msr::kIa32GsBase));
......
error |= UtilVmWrite(VmcsField::kGuestLdtrBase, VmpGetSegmentBase(gdtr.base, AsmReadLDTR()));
error |= UtilVmWrite(VmcsField::kGuestTrBase, VmpGetSegmentBase(gdtr.base, AsmReadTR()));
error |= UtilVmWrite(VmcsField::kGuestGdtrBase, gdtr.base);
error |= UtilVmWrite(VmcsField::kGuestIdtrBase, idtr.base);
error |= UtilVmWrite(VmcsField::kGuestDr7, __readdr(7));
error |= UtilVmWrite(VmcsField::kGuestRsp, guest_stack_pointer);
error |= UtilVmWrite(VmcsField::kGuestRip, guest_instruction_pointer);
error |= UtilVmWrite(VmcsField::kGuestRflags, __readeflags());
error |= UtilVmWrite(VmcsField::kGuestSysenterEsp, UtilReadMsr(Msr::kIa32SysenterEsp));
error |= UtilVmWrite(VmcsField::kGuestSysenterEip, UtilReadMsr(Msr::kIa32SysenterEip));
设置HOST区域
error |= UtilVmWrite(VmcsField::kHostEsSelector, AsmReadES() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostCsSelector, AsmReadCS() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostSsSelector, AsmReadSS() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostDsSelector, AsmReadDS() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostFsSelector, AsmReadFS() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostGsSelector, AsmReadGS() & 0xf8);
error |= UtilVmWrite(VmcsField::kHostTrSelector, AsmReadTR() & 0xf8);
......
error |= UtilVmWrite(VmcsField::kHostCr0, __readcr0());
error |= UtilVmWrite(VmcsField::kHostCr3, __readcr3());
error |= UtilVmWrite(VmcsField::kHostCr4, __readcr4());
......
error |= UtilVmWrite(VmcsField::kHostFsBase, UtilReadMsr(Msr::kIa32FsBase));
error |= UtilVmWrite(VmcsField::kHostGsBase, UtilReadMsr(Msr::kIa32GsBase));
......
error |= UtilVmWrite(VmcsField::kHostIa32SysenterCs, UtilReadMsr(Msr::kIa32SysenterCs));
......
error |= UtilVmWrite(VmcsField::kHostTrBase, VmpGetSegmentBase(gdtr.base, AsmReadTR()));
error |= UtilVmWrite(VmcsField::kHostGdtrBase, gdtr.base);
error |= UtilVmWrite(VmcsField::kHostIdtrBase, idtr.base);
error |= UtilVmWrite(VmcsField::kHostIa32SysenterEsp, UtilReadMsr(Msr::kIa32SysenterEsp));
error |= UtilVmWrite(VmcsField::kHostIa32SysenterEip, UtilReadMsr(Msr::kIa32SysenterEip));
error |= UtilVmWrite(VmcsField::kHostRsp, vmm_stack_pointer);
error |= UtilVmWrite(VmcsField::kHostRip, reinterpret_cast<ULONG_PTR>(AsmVmmEntryPoint));
设置control域
error |= UtilVmWrite64(VmcsField::kIoBitmapA, UtilPaFromVa(processor_data->shared_data->io_bitmap_a));
error |= UtilVmWrite64(VmcsField::kIoBitmapB, UtilPaFromVa(processor_data->shared_data->io_bitmap_b));
error |= UtilVmWrite64(VmcsField::kMsrBitmap, UtilPaFromVa(processor_data->shared_data->msr_bitmap));
error |= UtilVmWrite64(VmcsField::kEptPointer, EptGetEptPointer(processor_data->ept_data));
......
error |= UtilVmWrite(VmcsField::kPinBasedVmExecControl, vm_pinctl.all);
error |= UtilVmWrite(VmcsField::kCpuBasedVmExecControl, vm_procctl.all);
error |= UtilVmWrite(VmcsField::kExceptionBitmap, exception_bitmap);
error |= UtilVmWrite(VmcsField::kSecondaryVmExecControl, vm_procctl2.all);
......
设置entry域
error |= UtilVmWrite(VmcsField::kVmEntryControls, vm_entryctl.all);
设置exit域
error |= UtilVmWrite(VmcsField::kVmExitControls, vm_exitctl.all);
VmpLaunchVm
对应vmlaunch
_Use_decl_annotations_ static void VmpLaunchVm() {
PAGED_CODE()
auto error_code = UtilVmRead(VmcsField::kVmInstructionError);
if (error_code) {
HYPERPLATFORM_LOG_WARN("VM_INSTRUCTION_ERROR = %Iu", error_code);
}
auto vmx_status = static_cast<VmxStatus>(__vmx_vmlaunch());
// Here should not executed with successful vmlaunch. Instead, the context
// jumps to an address specified by GUEST_RIP.
if (vmx_status == VmxStatus::kErrorWithStatus) {
error_code = UtilVmRead(VmcsField::kVmInstructionError);
HYPERPLATFORM_LOG_ERROR("VM_INSTRUCTION_ERROR = %Iu", error_code);
}
HYPERPLATFORM_COMMON_DBG_BREAK();
}
至此启动vm。
AsmVmmEntryPoint
VM处理程序,没什么说的主要就是保存寄存器然后调用VmmVmExitHandler函数去处理vm-exit。
void __stdcall AsmVmmEntryPoint();
AsmVmmEntryPoint PROC FRAME
.PUSHFRAME
sub rsp, KTRAP_FRAME_SIZE - MACHINE_FRAME_SIZE
.ALLOCSTACK KTRAP_FRAME_SIZE - MACHINE_FRAME_SIZE + 108h
; No need to save the flag registers since it is restored from the VMCS at
; the time of vmresume.
PUSHAQ ; -8 * 16
mov rcx, rsp ; save the "stack" parameter for VmmVmExitHandler
; save volatile XMM registers
sub rsp, 68h ; 8 for alignment
movaps xmmword ptr [rsp + 0h], xmm0
movaps xmmword ptr [rsp + 10h], xmm1
movaps xmmword ptr [rsp + 20h], xmm2
movaps xmmword ptr [rsp + 30h], xmm3
movaps xmmword ptr [rsp + 40h], xmm4
movaps xmmword ptr [rsp + 50h], xmm5
sub rsp, 20h
; All stack allocation is done now. Indicate the end of prologue as required
; by the FRAME attribute.
.ENDPROLOG
call VmmVmExitHandler ; bool vm_continue = VmmVmExitHandler(stack);
add rsp, 20h
; restore XMM registers
movaps xmm0, xmmword ptr [rsp + 0h]
movaps xmm1, xmmword ptr [rsp + 10h]
movaps xmm2, xmmword ptr [rsp + 20h]
movaps xmm3, xmmword ptr [rsp + 30h]
movaps xmm4, xmmword ptr [rsp + 40h]
movaps xmm5, xmmword ptr [rsp + 50h]
add rsp, 68h
test al, al
jz exitVm ; if (!vm_continue) jmp exitVm
POPAQ
vmresume
jmp vmxError
exitVm:
; Executes vmxoff and ends virtualization
; rax = Guest's rflags
; rdx = Guest's rsp
; rcx = Guest's rip for the next instruction
POPAQ
vmxoff
jz vmxError ; if (ZF) jmp
jc vmxError ; if (CF) jmp
push rax
popfq ; rflags <= GurstFlags
mov rsp, rdx ; rsp <= GuestRsp
push rcx
ret ; jmp AddressToReturn
vmxError:
; Diagnose a critical error
pushfq
PUSHAQ ; -8 * 16
mov rcx, rsp ; all_regs
sub rsp, 28h ; 28h for alignment
call VmmVmxFailureHandler ; VmmVmxFailureHandler(all_regs);
add rsp, 28h
int 3
AsmVmmEntryPoint ENDP
VmmVmExitHandler
调用VmmpHandleVmExit处理。
用一个switch结构去处理exit-reason。
switch (exit_reason.fields.reason) {
case VmxExitReason::kExceptionOrNmi:
VmmpHandleException(guest_context);
break;
case VmxExitReason::kTripleFault:
VmmpHandleTripleFault(guest_context);
/* UNREACHABLE */
case VmxExitReason::kCpuid:
VmmpHandleCpuid(guest_context);
break;
case VmxExitReason::kInvd:
VmmpHandleInvalidateInternalCaches(guest_context);
break;
case VmxExitReason::kInvlpg:
VmmpHandleInvalidateTlbEntry(guest_context);
break;
case VmxExitReason::kRdtsc:
VmmpHandleRdtsc(guest_context);
break;
case VmxExitReason::kCrAccess:
VmmpHandleCrAccess(guest_context);
break;
case VmxExitReason::kDrAccess:
VmmpHandleDrAccess(guest_context);
break;
case VmxExitReason::kIoInstruction:
VmmpHandleIoPort(guest_context);
break;
case VmxExitReason::kMsrRead:
VmmpHandleMsrReadAccess(guest_context);
break;
case VmxExitReason::kMsrWrite:
VmmpHandleMsrWriteAccess(guest_context);
break;
case VmxExitReason::kMonitorTrapFlag:
VmmpHandleMonitorTrap(guest_context);
/* UNREACHABLE */
case VmxExitReason::kGdtrOrIdtrAccess:
VmmpHandleGdtrOrIdtrAccess(guest_context);
break;
case VmxExitReason::kLdtrOrTrAccess:
VmmpHandleLdtrOrTrAccess(guest_context);
break;
case VmxExitReason::kEptViolation:
VmmpHandleEptViolation(guest_context);
break;
case VmxExitReason::kEptMisconfig:
VmmpHandleEptMisconfig(guest_context);
/* UNREACHABLE */
case VmxExitReason::kVmcall:
VmmpHandleVmCall(guest_context);
break;
case VmxExitReason::kVmclear:
case VmxExitReason::kVmlaunch:
case VmxExitReason::kVmptrld:
case VmxExitReason::kVmptrst:
case VmxExitReason::kVmread:
case VmxExitReason::kVmresume:
case VmxExitReason::kVmwrite:
case VmxExitReason::kVmoff:
case VmxExitReason::kVmon:
case VmxExitReason::kInvept:
case VmxExitReason::kInvvpid:
VmmpHandleVmx(guest_context);
break;
case VmxExitReason::kRdtscp:
VmmpHandleRdtscp(guest_context);
break;
case VmxExitReason::kXsetbv:
VmmpHandleXsetbv(guest_context);
break;
default:
VmmpHandleUnexpectedExit(guest_context);
/* UNREACHABLE */
如果想要自己增加处理的话在这个结构里注册就可以。
HyperPlatform的更多相关文章
- VT 入门番外篇——初识 VT
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
随机推荐
- C++/Qt网络通讯模块设计与实现(三)
上一节给大家从源码级别分析了SNetClient网络客户端的实现过程,详见C++/Qt网络通讯模块设计与实现(二),并给大家留了一个疑问,即引入SNetClientRunning类是为了解决什么问题 ...
- 小笨自采集在线电脑壁纸 v2.0-支持移动端自适应,支持https
这周闲着没事,翻着网站,记得去年的发表的一篇文章小笨分享一款高清电脑壁纸API,主要是将孟坤壁纸改造支持https,还加了一个搜索功能.以前的壁纸是这样的: 但是,不支持移动端浏览,于是小笨趁着这周周 ...
- [C++STL教程]7.priority_queue优先队列入门学习!零基础都能听懂的教程
不知不觉C++STL教程系列已经第7期了.之前我们介绍过:vector, queue, stack, set, map等等数据结构. 今天我们来学习一个新的stl容器:priority_queue优先 ...
- MapReduce之简单的数据清洗----课堂测试
今天的课堂测试第一步是做简单的数据清洗,直到现在我才知道只是把文本文件的数据改成相应的格式,而我做的一直是寻找一条数据,并转换成相应的格式,但是呢,我感觉还是很高兴的,虽然没有按时完成任务,但也学到了 ...
- 人工智能机器学习底层原理剖析,人造神经元,您一定能看懂,通俗解释把AI“黑话”转化为“白话文”
按照固有思维方式,人们总以为人工智能是一个莫测高深的行业,这个行业的人都是高智商人群,无论是写文章还是和人讲话,总是讳莫如深,接着就是蹦出一些"高级"词汇,什么"神经网络 ...
- 一些随笔 No.1
耦合 耦合是一个设计与逻辑上的问题 例如一个软件有20个功能,删除任意一个功能对别的19个功能不造成影响,就是低耦合 如果删除一个功能后其他功能会失去完整性,那么就是高耦合 Difference be ...
- 使用HTMLform表单操作腾讯云DNS控制台
在使用中经常需要修改DNS记录,或者查询.删除操作.每次都得登录腾讯云控制台,腾讯云比较鸡肋的一点就是需要进行微信扫码登录,每次操作太不方便. 可以使用api接口进行操作腾讯云上的产品.所以使用HTM ...
- sql求每家店铺销量前三的sku, 附python解法
背景 有一张表: date store_id sku sales 2023-01-01 CK005 03045 50 date 代表交易日期,store_id代表门店编号,sku代表商品,sales代 ...
- 【迭代器设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
简介 迭代器模式(Iterator Pattern),是一种结构型设计模式.给数据对象构建一套按顺序访问集合对象元素的方式,而不需要知道数据对象的底层表示. 迭代器模式是与集合共存的,我们只要实现一个 ...
- 使用Go语言操作HDFS
HDFS(Hadoop分布式文件系统)是Hadoop生态系统的一部分,它是一个可扩展的分布式文件系统,被设计用于在大规模数据集上运行的应用程序 安装相关package: $ go get github ...