【QEMU系统分析之启动篇(十一)】

系列文章目录

第十一章 QEMU系统仿真的加速器初始化分析


文章目录

  • 系列文章目录
    • 第十一章 QEMU系统仿真的加速器初始化分析
  • 前言
  • 一、QEMU是什么?
  • 二、QEMU系统仿真的启动分析
    • 1.系统仿真的初始化代码
    • 2.主循环数据初始化
      • configure_accelerators()
      • phase_advance(PHASE_ACCEL_CREATED)
  • 总结


前言

本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的启动过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。
本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。


一、QEMU是什么?

QEMU 是一个通用且开源的机器模拟器和虚拟机。
其官方主页是:https://www.qemu.org/


二、QEMU系统仿真的启动分析

1.系统仿真的初始化代码

QEMU 作为系统仿真工具,其入口代码在 system/main.c 文件中,初始化函数 qemu_init() 的实现在 system/vl.c 文件中,在完成 QEMU 虚拟机配置项处理和应用后,系统将配置虚拟机的加速器。本篇文章将完成以下代码部分的分析。

2.主循环数据初始化

这部分代码在 system/vl.c 文件中,实现如下:

void qemu_init(int argc, char **argv)
{
.../** Note: uses machine properties such as kernel-irqchip, must run* after qemu_apply_machine_options.*/configure_accelerators(argv[0]);phase_advance(PHASE_ACCEL_CREATED);
...
}

代码首先通过函数 qemu_disable_default_devices() 将所有默认设备的设置复位。

configure_accelerators()

此函数在 /system/vl.c 文件中,定义如下:

static void configure_accelerators(const char *progname)
{bool init_failed = false;qemu_opts_foreach(qemu_find_opts("icount"),do_configure_icount, NULL, &error_fatal);if (QTAILQ_EMPTY(&qemu_accel_opts.head)) {char **accel_list, **tmp;if (accelerators == NULL) {/* Select the default accelerator */bool have_tcg = accel_find("tcg");bool have_kvm = accel_find("kvm");if (have_tcg && have_kvm) {if (g_str_has_suffix(progname, "kvm")) {/* If the program name ends with "kvm", we prefer KVM */accelerators = "kvm:tcg";} else {accelerators = "tcg:kvm";}} else if (have_kvm) {accelerators = "kvm";} else if (have_tcg) {accelerators = "tcg";} else {error_report("No accelerator selected and"" no default accelerator available");exit(1);}}accel_list = g_strsplit(accelerators, ":", 0);for (tmp = accel_list; *tmp; tmp++) {/** Filter invalid accelerators here, to prevent obscenities* such as "-machine accel=tcg,,thread=single".*/if (accel_find(*tmp)) {qemu_opts_parse_noisily(qemu_find_opts("accel"), *tmp, true);} else {init_failed = true;error_report("invalid accelerator %s", *tmp);}}g_strfreev(accel_list);} else {if (accelerators != NULL) {error_report("The -accel and \"-machine accel=\" options are incompatible");exit(1);}}if (!qemu_opts_foreach(qemu_find_opts("accel"),do_configure_accelerator, &init_failed, &error_fatal)) {if (!init_failed) {error_report("no accelerator found");}exit(1);}if (init_failed && !qtest_chrdev) {error_report("falling back to %s", current_accel_name());}if (icount_enabled() && !tcg_enabled()) {error_report("-icount is not allowed with hardware virtualization");exit(1);}
}


函数 do_configure_icount() 定义如下:

static int do_configure_icount(void *opaque, QemuOpts *opts, Error **errp)
{icount_configure(opts, errp);return 0;
}

而函数 icount_configure() 在 TCG 加速器中定义如下:

void icount_configure(QemuOpts *opts, Error **errp)
{const char *option = qemu_opt_get(opts, "shift");bool sleep = qemu_opt_get_bool(opts, "sleep", true);bool align = qemu_opt_get_bool(opts, "align", false);long time_shift = -1;if (!option) {if (qemu_opt_get(opts, "align") != NULL) {error_setg(errp, "Please specify shift option when using align");}return;}if (align && !sleep) {error_setg(errp, "align=on and sleep=off are incompatible");return;}if (strcmp(option, "auto") != 0) {if (qemu_strtol(option, NULL, 0, &time_shift) < 0|| time_shift < 0 || time_shift > MAX_ICOUNT_SHIFT) {error_setg(errp, "icount: Invalid shift value");return;}} else if (icount_align_option) {error_setg(errp, "shift=auto and align=on are incompatible");return;} else if (!icount_sleep) {error_setg(errp, "shift=auto and sleep=off are incompatible");return;}icount_sleep = sleep;if (icount_sleep) {timers_state.icount_warp_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL_RT,icount_timer_cb, NULL);}icount_align_option = align;if (time_shift >= 0) {timers_state.icount_time_shift = time_shift;icount_enable_precise();return;}icount_enable_adaptive();/** 125MIPS seems a reasonable initial guess at the guest speed.* It will be corrected fairly quickly anyway.*/timers_state.icount_time_shift = 3;/** Have both realtime and virtual time triggers for speed adjustment.* The realtime trigger catches emulated time passing too slowly,* the virtual time trigger catches emulated time passing too fast.* Realtime triggers occur even when idle, so use them less frequently* than VM triggers.*/timers_state.vm_clock_warp_start = -1;timers_state.icount_rt_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL_RT,icount_adjust_rt, NULL);timer_mod(timers_state.icount_rt_timer,qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT) + 1000);timers_state.icount_vm_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,icount_adjust_vm, NULL);timer_mod(timers_state.icount_vm_timer,qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +NANOSECONDS_PER_SECOND / 10);
}

如果没有设定加速器,默认查找是否支持 TCG 或 KVM 加速器,如果有则配置默认加速器,实现代码如下:

        if (accelerators == NULL) {/* Select the default accelerator */bool have_tcg = accel_find("tcg");bool have_kvm = accel_find("kvm");if (have_tcg && have_kvm) {if (g_str_has_suffix(progname, "kvm")) {/* If the program name ends with "kvm", we prefer KVM */accelerators = "kvm:tcg";} else {accelerators = "tcg:kvm";}} else if (have_kvm) {accelerators = "kvm";} else if (have_tcg) {accelerators = "tcg";} else {error_report("No accelerator selected and"" no default accelerator available");exit(1);}}

函数 do_configure_accelerator() 定义如下:

static int do_configure_accelerator(void *opaque, QemuOpts *opts, Error **errp)
{bool *p_init_failed = opaque;const char *acc = qemu_opt_get(opts, "accel");AccelClass *ac = accel_find(acc);AccelState *accel;int ret;bool qtest_with_kvm;if (!acc) {error_setg(errp, QERR_MISSING_PARAMETER, "accel");goto bad;}qtest_with_kvm = g_str_equal(acc, "kvm") && qtest_chrdev != NULL;if (!ac) {if (!qtest_with_kvm) {error_report("invalid accelerator %s", acc);}goto bad;}accel = ACCEL(object_new_with_class(OBJECT_CLASS(ac)));object_apply_compat_props(OBJECT(accel));qemu_opt_foreach(opts, accelerator_set_property,accel,&error_fatal);/** If legacy -singlestep option is set, honour it for TCG and* silently ignore for any other accelerator (which is how this* option has always behaved).*/if (opt_one_insn_per_tb) {/** This will always succeed for TCG, and we want to ignore* the error from trying to set a nonexistent property* on any other accelerator.*/object_property_set_bool(OBJECT(accel), "one-insn-per-tb", true, NULL);}ret = accel_init_machine(accel, current_machine);if (ret < 0) {if (!qtest_with_kvm || ret != -ENOENT) {error_report("failed to initialize %s: %s", acc, strerror(-ret));}goto bad;}return 1;bad:*p_init_failed = true;return 0;
}

函数 accel_find() 定义如下:

/* Lookup AccelClass from opt_name. Returns NULL if not found */
AccelClass *accel_find(const char *opt_name)
{char *class_name = g_strdup_printf(ACCEL_CLASS_NAME("%s"), opt_name);AccelClass *ac = ACCEL_CLASS(module_object_class_by_name(class_name));g_free(class_name);return ac;
}

数据结构 struct AccelClass 定义如下:

struct AccelState {/*< private >*/Object parent_obj;
};typedef struct AccelClass {/*< private >*/ObjectClass parent_class;/*< public >*/const char *name;int (*init_machine)(MachineState *ms);
#ifndef CONFIG_USER_ONLYvoid (*setup_post)(MachineState *ms, AccelState *accel);bool (*has_memory)(MachineState *ms, AddressSpace *as,hwaddr start_addr, hwaddr size);
#endifbool (*cpu_common_realize)(CPUState *cpu, Error **errp);void (*cpu_common_unrealize)(CPUState *cpu);/* gdbstub related hooks */int (*gdbstub_supported_sstep_flags)(void);bool *allowed;/** Array of global properties that would be applied when specific* accelerator is chosen. It works like MachineClass.compat_props* but it's for accelerators not machines. Accelerator-provided* global properties may be overridden by machine-type* compat_props or user-provided global properties.*/GPtrArray *compat_props;
} AccelClass;

函数 accel_init_machine() 定义如下:

int accel_init_machine(AccelState *accel, MachineState *ms)
{AccelClass *acc = ACCEL_GET_CLASS(accel);int ret;ms->accelerator = accel;*(acc->allowed) = true;ret = acc->init_machine(ms);if (ret < 0) {ms->accelerator = NULL;*(acc->allowed) = false;object_unref(OBJECT(accel));} else {object_set_accelerator_compat_props(acc->compat_props);}return ret;
}

通过 accel_init_machine() 将目标机器和加速器关联在一起。

加速器的 init_machine() 函数根据每个加速器不同,有不同的实现,WHPX 的定义如下:

/** Partition support*/static int whpx_accel_init(MachineState *ms)
{struct whpx_state *whpx;int ret;HRESULT hr;WHV_CAPABILITY whpx_cap;UINT32 whpx_cap_size;WHV_PARTITION_PROPERTY prop;UINT32 cpuidExitList[] = {1, 0x80000001};WHV_CAPABILITY_FEATURES features = {0};whpx = &whpx_global;if (!init_whp_dispatch()) {ret = -ENOSYS;goto error;}whpx->mem_quota = ms->ram_size;hr = whp_dispatch.WHvGetCapability(WHvCapabilityCodeHypervisorPresent, &whpx_cap,sizeof(whpx_cap), &whpx_cap_size);if (FAILED(hr) || !whpx_cap.HypervisorPresent) {error_report("WHPX: No accelerator found, hr=%08lx", hr);ret = -ENOSPC;goto error;}hr = whp_dispatch.WHvGetCapability(WHvCapabilityCodeFeatures, &features, sizeof(features), NULL);if (FAILED(hr)) {error_report("WHPX: Failed to query capabilities, hr=%08lx", hr);ret = -EINVAL;goto error;}hr = whp_dispatch.WHvCreatePartition(&whpx->partition);if (FAILED(hr)) {error_report("WHPX: Failed to create partition, hr=%08lx", hr);ret = -EINVAL;goto error;}/** Query the XSAVE capability of the partition. Any error here is not* considered fatal.*/hr = whp_dispatch.WHvGetPartitionProperty(whpx->partition,WHvPartitionPropertyCodeProcessorXsaveFeatures,&whpx_xsave_cap,sizeof(whpx_xsave_cap),&whpx_cap_size);/** Windows version which don't support this property will return with the* specific error code.*/if (FAILED(hr) && hr != WHV_E_UNKNOWN_PROPERTY) {error_report("WHPX: Failed to query XSAVE capability, hr=%08lx", hr);}if (!whpx_has_xsave()) {printf("WHPX: Partition is not XSAVE capable\n");}memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));prop.ProcessorCount = ms->smp.cpus;hr = whp_dispatch.WHvSetPartitionProperty(whpx->partition,WHvPartitionPropertyCodeProcessorCount,&prop,sizeof(WHV_PARTITION_PROPERTY));if (FAILED(hr)) {error_report("WHPX: Failed to set partition processor count to %u,"" hr=%08lx", prop.ProcessorCount, hr);ret = -EINVAL;goto error;}/** Error out if WHP doesn't support apic emulation and user is requiring* it.*/if (whpx->kernel_irqchip_required && (!features.LocalApicEmulation ||!whp_dispatch.WHvSetVirtualProcessorInterruptControllerState2)) {error_report("WHPX: kernel irqchip requested, but unavailable. ""Try without kernel-irqchip or with kernel-irqchip=off");ret = -EINVAL;goto error;}if (whpx->kernel_irqchip_allowed && features.LocalApicEmulation &&whp_dispatch.WHvSetVirtualProcessorInterruptControllerState2) {WHV_X64_LOCAL_APIC_EMULATION_MODE mode =WHvX64LocalApicEmulationModeXApic;printf("WHPX: setting APIC emulation mode in the hypervisor\n");hr = whp_dispatch.WHvSetPartitionProperty(whpx->partition,WHvPartitionPropertyCodeLocalApicEmulationMode,&mode,sizeof(mode));if (FAILED(hr)) {error_report("WHPX: Failed to enable kernel irqchip hr=%08lx", hr);if (whpx->kernel_irqchip_required) {error_report("WHPX: kernel irqchip requested, but unavailable");ret = -EINVAL;goto error;}} else {whpx->apic_in_platform = true;}}/* Register for MSR and CPUID exits */memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));prop.ExtendedVmExits.X64MsrExit = 1;prop.ExtendedVmExits.X64CpuidExit = 1;prop.ExtendedVmExits.ExceptionExit = 1;if (whpx_apic_in_platform()) {prop.ExtendedVmExits.X64ApicInitSipiExitTrap = 1;}hr = whp_dispatch.WHvSetPartitionProperty(whpx->partition,WHvPartitionPropertyCodeExtendedVmExits,&prop,sizeof(WHV_PARTITION_PROPERTY));if (FAILED(hr)) {error_report("WHPX: Failed to enable MSR & CPUIDexit, hr=%08lx", hr);ret = -EINVAL;goto error;}hr = whp_dispatch.WHvSetPartitionProperty(whpx->partition,WHvPartitionPropertyCodeCpuidExitList,cpuidExitList,RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32));if (FAILED(hr)) {error_report("WHPX: Failed to set partition CpuidExitList hr=%08lx",hr);ret = -EINVAL;goto error;}/** We do not want to intercept any exceptions from the guest,* until we actually start debugging with gdb.*/whpx->exception_exit_bitmap = -1;hr = whpx_set_exception_exit_bitmap(0);if (FAILED(hr)) {error_report("WHPX: Failed to set exception exit bitmap, hr=%08lx", hr);ret = -EINVAL;goto error;}hr = whp_dispatch.WHvSetupPartition(whpx->partition);if (FAILED(hr)) {error_report("WHPX: Failed to setup partition, hr=%08lx", hr);ret = -EINVAL;goto error;}whpx_memory_init();printf("Windows Hypervisor Platform accelerator is operational\n");return 0;error:if (NULL != whpx->partition) {whp_dispatch.WHvDeletePartition(whpx->partition);whpx->partition = NULL;}return ret;
}

函数 init_whp_dispatch() 在 /target/i386/whpx/whpx-all.c 文件中,定义如下:

bool init_whp_dispatch(void)
{if (whp_dispatch_initialized) {return true;}if (!load_whp_dispatch_fns(&hWinHvPlatform, WINHV_PLATFORM_FNS_DEFAULT)) {goto error;}if (!load_whp_dispatch_fns(&hWinHvEmulation, WINHV_EMULATION_FNS_DEFAULT)) {goto error;}assert(load_whp_dispatch_fns(&hWinHvPlatform,WINHV_PLATFORM_FNS_SUPPLEMENTAL));whp_dispatch_initialized = true;return true;
error:if (hWinHvPlatform) {FreeLibrary(hWinHvPlatform);}if (hWinHvEmulation) {FreeLibrary(hWinHvEmulation);}return false;
}

函数 load_whp_dispatch_fns() 定义如下:

/** Load the functions from the given library, using the given handle. If a* handle is provided, it is used, otherwise the library is opened. The* handle will be updated on return with the opened one.*/
static bool load_whp_dispatch_fns(HMODULE *handle,WHPFunctionList function_list)
{HMODULE hLib = *handle;#define WINHV_PLATFORM_DLL "WinHvPlatform.dll"#define WINHV_EMULATION_DLL "WinHvEmulation.dll"#define WHP_LOAD_FIELD_OPTIONAL(return_type, function_name, signature) \whp_dispatch.function_name = \(function_name ## _t)GetProcAddress(hLib, #function_name); \
#define WHP_LOAD_FIELD(return_type, function_name, signature) \whp_dispatch.function_name = \(function_name ## _t)GetProcAddress(hLib, #function_name); \if (!whp_dispatch.function_name) { \error_report("Could not load function %s", #function_name); \goto error; \} \
#define WHP_LOAD_LIB(lib_name, handle_lib) \if (!handle_lib) { \handle_lib = LoadLibrary(lib_name); \if (!handle_lib) { \error_report("Could not load library %s.", lib_name); \goto error; \} \} \
switch (function_list) {case WINHV_PLATFORM_FNS_DEFAULT:WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib)LIST_WINHVPLATFORM_FUNCTIONS(WHP_LOAD_FIELD)break;case WINHV_EMULATION_FNS_DEFAULT:WHP_LOAD_LIB(WINHV_EMULATION_DLL, hLib)LIST_WINHVEMULATION_FUNCTIONS(WHP_LOAD_FIELD)break;case WINHV_PLATFORM_FNS_SUPPLEMENTAL:WHP_LOAD_LIB(WINHV_PLATFORM_DLL, hLib)LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL(WHP_LOAD_FIELD_OPTIONAL)break;}*handle = hLib;return true;error:if (hLib) {FreeLibrary(hLib);}return false;
}

WHPX 提供的函数类型列表如下:

typedef enum WHPFunctionList {WINHV_PLATFORM_FNS_DEFAULT,WINHV_EMULATION_FNS_DEFAULT,WINHV_PLATFORM_FNS_SUPPLEMENTAL
} WHPFunctionList;

phase_advance(PHASE_ACCEL_CREATED)

接下来,将机器状态置为 PHASE_ACCEL_CREATED,实现代码如下:

    phase_advance(PHASE_ACCEL_CREATED);

函数 phase_advance() 定义如下:

void phase_advance(MachineInitPhase phase)
{assert(machine_phase == phase - 1);machine_phase = phase;
}

机器状态 machine_phase 的数据类型定义如下:

typedef enum MachineInitPhase {/* current_machine is NULL.  */PHASE_NO_MACHINE,/* current_machine is not NULL, but current_machine->accel is NULL.  */PHASE_MACHINE_CREATED,/** current_machine->accel is not NULL, but the machine properties have* not been validated and machine_class->init has not yet been called.*/PHASE_ACCEL_CREATED,/** machine_class->init has been called, thus creating any embedded* devices and validating machine properties.  Devices created at* this time are considered to be cold-plugged.*/PHASE_MACHINE_INITIALIZED,/** QEMU is ready to start CPUs and devices created at this time* are considered to be hot-plugged.  The monitor is not restricted* to "preconfig" commands.*/PHASE_MACHINE_READY,
} MachineInitPhase;

至此,完成了对象字典中与虚拟机相关的加速器的配置项处理和应用。


总结

以上分析了 QEMU 系统仿真在启动过程中,QEMU 系统仿真对目标虚拟机的加速器配置项所做的处理和应用。

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

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

相关文章

科技感十足特效源码

源码介绍 科技感十足特效源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 源码截图 源码下载 科技感十足特效源码

Linux:冯诺依曼体系结构、操作系统、初识进程

文章目录 1.冯诺依曼体系结构总线与数据传输通路为什么有内存这个部分计算机存储结构 2.操作系统(Operator System)2.1 概念2.2 设计OS的目的2.3 理解“管理”先描述再组织 2.4 用户使用系统调用和库函数&#xff08;lib&#xff09;概念 总结 3.初识进程3.1 基本事实与引入3.2…

外贸常用邮件模板-客户投诉要如何处理?

外贸业务人员在与国外客户长期合作的过程当中&#xff0c;很难完全避免所有业务环节都万无一失&#xff0c;在整个订单的开发执行和成交售后过程当中&#xff0c;一旦出现某些问题导致客户投诉&#xff0c;将会在很大程度上影响我们和客户之间的关系以及未来的长期合作。 因此…

如何编写一个高性能的Web服务器

编写一个高性能的Web服务器需要考虑多个方面&#xff0c;包括架构设计、并发处理、资源优化、安全性等。以下是一些关键步骤和策略&#xff0c;帮助你编写一个高性能的Web服务器&#xff1a; 选择合适的编程语言和技术栈&#xff1a; 选择一个适合Web开发的编程语言&#xff0c…

ZYNQ之嵌入式开发05——串口中断、定时器中断、QSPI和SD卡读写测试实验

文章目录 UART串口中断实验定时器中断实验PS-XADC实验QSPI Flash读写测试SD卡读写文本文档 UART串口中断实验 UART控制器是一个全双工异步收发器&#xff0c;支持可编程的波特率和IO信号格式&#xff0c;具有独立的TX和RX数据路径&#xff0c;每个路径有一个64字节的FIFO&…

设计模式学习笔记 - 开源实战五(中):如何利用职责链与代理模式实现Mybatis Plugin

概述 上篇文章对 Mybatis 框架做了简单的背景介绍&#xff0c;并通过对比各种 ORM 框架&#xff0c;学习了代码的易用性、性能、灵活性之间的关系。一般来讲&#xff0c;框架提供的高级功能越多&#xff0c;那性能损耗就越大&#xff1b;框架使用起来越简单&#xff0c;那灵活…

59岁郑浩南罕见与索爆女儿合体,曾自曝婚变暗示妻子出轨人财两空

59岁的郑浩南&#xff0c;拍过不少脍炙人口的电影&#xff0c;尤其是在《古惑仔》中饰演奸角司徒浩南&#xff0c;近作有《黑社会》的「加钱哥」&#xff0c;虽然将近「登六」&#xff0c;却保养得宣&#xff0c;操出一身健硕肌肉。 跟前妻鲍爱玲离婚后&#xff0c;郑浩南独自在…

即插即用Mamba模块全新突破!无缝集成,无痛涨点

Mamba入局图像复原了&#xff01;基于Mamba的图像复原基准模型MambaIR性能超越SwinIR&#xff0c;达成新SOTA&#xff01; MambaIR是一种引入通道注意力和局部增强的即插即用Mamba模块。这类高效、创新的模块在写论文时可以帮助我们简化模型的构建过程&#xff0c;通过将这些模…

Android 11 bindService 流程分析

我们可以使用bindService来跨进程通信&#xff0c;其使用方法如下 Intent intent new Intent("xxx"); intent.setPackage("xxx"); boolean result bindService(intent,new ServiceConn(),BIND_AUTO_CREATE);private class ServiceConn implements Servi…

使用lua时一个愚蠢的错误

之前看luaL_openlibs()&#xff0c;感觉直接调打开库的函数好像也没差别&#xff0c;所以将 LUALIB_API void luaL_openlibs (lua_State *L) {const luaL_Reg *lib lualibs;for (; lib->func; lib) {lua_pushcfunction(L, lib->func);lua_pushstring(L, lib->name);…

学习100个Unity Shader (15) ---透明+双面渲染

文章目录 效果shader理解参考 效果 shader Shader "Example/AlphaBlendBothSided" {Properties{_Color ("Main Tint", Color) (1, 1, 1, 1)_MainTex ("Texture", 2D) "white" {}_AlphaScale ("Alpha Scale", Range(0, 1)…

leetcode多个测试用例之间相互影响导致提交失败

背景 在做一道easy题&#xff0c;二叉树的中序遍历&#xff0c;我提交的代码如下 from typing import (Optional,List )# Definition for a binary tree node. class TreeNode:def __init__(self, val0, leftNone, rightNone):self.val valself.left leftself.right right…

无脑入单向无头链表的实现| ArrayList和LinkedList的区别

1. ArrayList的缺陷 上节课已经熟悉了ArrayList的使用&#xff0c;并且进行了简单模拟实现。通过源码知道&#xff0c;ArrayList底层使用数组来存储元素。 由于其底层是一段连续空间&#xff0c;当 在 ArrayList 任意位置插入或者删除元素时&#xff0c;就需要将后序元素整体往…

掼蛋入门口诀

1、有王打单张&#xff0c;无王打一夯 2、想使坏&#xff0c;三不带 3、情况不明&#xff0c;对子先行 4、两个小单张&#xff0c;不打不健康 5、顺子打到头&#xff0c;对手没想头 6、双贡出单张&#xff0c;头游响当当 7、逢五出两张&#xff0c;逢六出三张 8、炸五不炸四&am…

pytest测试基础

assert 验证关键字 需要pahton版本大于3.6&#xff0c;因为有个工具pip3;因为做了映射&#xff0c;所以下面命令pip3即pip pip install -U pytest -U参数可选&#xff0c;是如果已安装可更新。 如果上述demo变化 通过验证代码&#xff0c;测试环境没问题。…

详细谈电脑ip、域名、内网、外网、localhost、127.0.0.1、网关等通讯基础知识(易懂)

1. ip地址与域名的定义以及其关系 ip地址的定义&#xff1a; IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;又译为网际协议地址。 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一…

ChatGPT记忆功能终于上线了, OpenAI 官方:用得越久越聪明!

原文 ChatGPT记忆功能终于上线了, OpenAI 官方&#xff1a;用得越久越聪明&#xff01; Aitrainee | 公众号&#xff1a;AI进修生 &#x1f31f; 记得今年2月份OpenAI发布过ChatGPT上线记忆功能的消息&#xff0c;我记得当时还弹出过这个窗口给我&#xff0c;但是仅仅体验了几…

Git--分布式版本控制系统

目录 一、理解分布式版本控制系统二、远程仓库三、克隆远程仓库四、向远程仓库推送五、拉取远程仓库六、配置Git七、给命令配置别名八、创建标签九、操作标签 一、理解分布式版本控制系统 我们⽬前所说的所有内容&#xff08;⼯作区&#xff0c;暂存区&#xff0c;版本库等等&a…

在智慧城市的建设中智能电表发挥什么作用

在智慧城市的建设中&#xff0c;智能电表扮演着至关重要的角色。智慧城市是一个利用信息技术手段提升城市运行效率和质量的新型城市模式&#xff0c;旨在通过信息和通信技术的应用&#xff0c;提高城市管理、公共服务、环境保护等方面的质量和效率&#xff0c;促进城市的可持续…

基于MSP430F249的电子钟仿真(源码+仿真)

目录 1、前言 2、仿真 3、程序 资料下载地址&#xff1a;基于MSP430F249的电子钟仿真(源码仿真&#xff09; 1、前言 基于MSP430F249的电子钟仿真&#xff0c;数码管显示时分秒&#xff0c;并可以通过按键调节时间。 2、仿真 3、程序 #include <MSP430x24x.h> #def…