【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_ONLY
    void (*setup_post)(MachineState *ms, AccelState *accel);
    bool (*has_memory)(MachineState *ms, AddressSpace *as,
                       hwaddr start_addr, hwaddr size);
#endif
    bool (*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.mfbz.cn/a/571752.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 泛型编程篇(一) 模板初阶

目录 〇、为什么需要模板&#xff1f; 一、函数模板 1. 函数模板概念 2. 函数模板格式 3. 函数模板的原理 4. 隐式实例化和显示实例化 5. 无法推导模板类型的情况 a. 只设置一个模板&#xff0c;但两个不同的参数类型使用模板 b. 函数体中使用了模板 6. 同名普通函数和模板函…

字符串型漏洞注入

SQL注入是一种严重的安全漏洞&#xff0c;它允许攻击者在不受控制的情况下执行任意的SQL代码。这种攻击通常发生在Web应用程序没有正确验证或转义用户输入的情况下。字符串型注入是SQL注入的一种形式&#xff0c;它涉及到在应用程序的输入框中插入恶意的字符串&#xff0c;以干…

K8S--常用的命令

原文网址&#xff1a;K8S--常用的命令-CSDN博客 简介 本文介绍K8S常用的命令。 官网 简略用法&#xff1a;https://kubernetes.io/zh-cn/docs/reference/kubectl/ 详细用法&#xff08;有示例&#xff09;&#xff1a;https://kubernetes.io/docs/reference/generated/kub…

MINIO安装的方法(WindowsLiunx)

2 minio安装教程 注&#xff1a;官方中文文档&#xff1a;MinIO对象存储 Windows — MinIO中文文档 | MinIO Windows中文文档 Liunx 安装方&#xff1a;MinIO对象存储 Linux — MinIO中文文档 | MinIO Linux中文文档 2.1 下载地址 https://dl.min.io/server/minio/…

Quarto Dashboards 教程 1:Overview

「写在前面」 学习一个软件最好的方法就是啃它的官方文档。本着自己学习、分享他人的态度&#xff0c;分享官方文档的中文教程。软件可能随时更新&#xff0c;建议配合官方文档一起阅读。推荐先按顺序阅读往期内容&#xff1a; 1.quarto 教程 1&#xff1a;Hello, Quarto 2.qu…

基于python+django+mysql农业生产可视化系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

Tiny11作者开源:利用微软官方镜像制作独属于你的Tiny11镜像

微软对Windows 11的最低硬件要求包括至少4GB的内存、双核处理器和64GB的SSD存储。然而&#xff0c;这些基本要求仅仅能保证用户启动和运行系统&#xff0c;而非流畅使用 为了提升体验&#xff0c;不少用户选择通过精简系统来减轻硬件负担&#xff0c;我们熟知的Tiny11便是其中…

【linux】Linux第一个小程序-进度条

1. 预备知识&#xff1a;回车和换行 回车&#xff08;Carriage Return&#xff0c;CR&#xff09;&#xff1a; 在早期的机械打字机中&#xff0c;回车指的是将打字机的打印头移回到行首的操作&#xff0c;这样打印头就可以开始新的一行的打印。在ASCII编码中&#xff0c;回车用…

【VueUse】重新定义状态管理在 Vue 中的体验

在 Vue 生态系统中&#xff0c;状态管理一直是开发者们关注的焦点之一。而随着 VueUse 的出现&#xff0c;我们迎来了一种全新的方式来处理状态管理&#xff0c;它让我们能够以更简单、更灵活的方式来管理应用程序的状态。 在本文中我们将深入探讨 VueUse 中与状态管理相关的内…

第一篇【传奇开心果系列】Python深度学习库技术点案例示例:深度解读深度学习在自动驾驶领域的应用

传奇开心果博文系列 系列博文目录Python深度学习库技术点案例示例系列 博文目录前言一、深度学习在自动驾驶方面的应用介绍二、目标检测和识别示例代码三、路况感知示例代码四、行为预测示例代码五、路径规划示例代码六、自动驾驶控制示例代码七、感知融合示例代码八、高精度地…

基于Springboot的点餐平台

基于SpringbootVue的点餐平台的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 菜品信息 菜品资讯 购物车 后台登录 用户管理 菜品分类管理 菜品信息管理 …

npm、yarn与pnpm详解

&#x1f525; npm、yarn与pnpm详解 &#x1f516; 一、npm &#x1f50d; 简介&#xff1a; npm是随Node.js一起安装的官方包管理工具&#xff0c;它为开发者搭建了一个庞大的资源库&#xff0c;允许他们在这个平台上搜索、安装和管理项目所必需的各种代码库或模块。 &#…

【网页在线小游戏源码】

网页在线小游戏源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 index.html <!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <meta id"viewport" na…

零代码编程:下载的PDF文件自动批量重命名

从arxiv或者一些其他网站下载的PDF文件&#xff0c;标题只是创建时间&#xff0c;看不出里面内容是什么&#xff0c;要一个个打开看非常麻烦。 可以在GPT4中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个编写关于重命名PDF文件的Python脚本的任务&…

Llama-7b-Chinese本地推理

Llama-7b-Chinese 本地推理 基础环境信息&#xff08;wsl2安装Ubuntu22.04 miniconda&#xff09; 使用miniconda搭建环境 (base) :~$ conda create --name Llama-7b-Chinese python3.10 Channels:- defaults Platform: linux-64 Collecting package metadata (repodata.js…

机器学习和深度学习-- 李宏毅(笔记与个人理解)Day22

Day 22 Transformer seqence to seqence 有什么用呢&#xff1f; Encoder how Block work 仔细讲讲Residual 的过程&#xff1f; 重构 Decoder - AutoRegressive Mask 由于是文字接龙&#xff0c;所以无法考虑右边的 info 另一种decoder Encoder to Decoder – Cross Attend…

❤mac使用Idea工具

❤mac使用Idea工具 1、安装 直接跳过&#xff0c;文章有 &#xff08;点击跳转&#xff09; 给自己的mac系统上安装java环境 2、使用 快捷键 Command , 系统首选项 设置Idea连接数据库 打开右侧的database&#xff08;或菜单里&#xff09;连接数据库&#xff0c;根据提…

opencv绘制线段------c++

绘制线段 bool opencvTool::drawLines(std::string image_p, std::vector<cv::Point> points) {cv::Mat ima cv::imread(image_p.c_str()); // 读取图像&#xff0c;替换为你的图片路径 cv::Scalar red cv::Scalar(0, 0, 255); // Red color int thickness 2;// 遍…

Vue3 超前版发布,全面拥抱 JSX/TSX

拥抱 JSX/TSX? 我们都知道 Vue 一直主流是使用 template 模板来进行页面的编写。而就在最近,Vue3 的超前项目 Vue Macros 中,发布了 defineRender、setupComponent、setupSFC 这些新的 API,这代表了,以后 Vue3 有可能可以全面拥抱 JSX/TSX 了!! 说这个新 API 之前,我…

2024江苏省考申论新说刷题系统班

2024江苏省考申论新说刷题系统班&#xff0c;针对江苏省考特色&#xff0c;精准指导考生刷题备考。课程系统全面&#xff0c;深入剖析申论题型&#xff0c;提供实战演练机会。通过科学刷题&#xff0c;考生能迅速提升申论能力&#xff0c;为江苏省考成功上岸打下坚实基础。 下…
最新文章