QEMU源码全解析42 —— Machine(12)

接前一篇文章:QEMU源码全解析41 —— Machine(11)

本文内容参考:

《趣谈Linux操作系统》 —— 刘超,极客时间

《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社

特此致谢!

上一回针对于“Machine”系列第3~10篇文章开始梳理其脉络,梳理了type_init()这一条线,本文讲梳理type_register()这一条线,并对整个流程进行总结。

先再次贴出

DEFINE_I440FX_MACHINE(v8_1, "pc-i440fx-8.1", NULL,pc_i440fx_8_1_machine_options);

的相关代码:

static void pc_init_v8_1(MachineState *machine)
{void (*compat)(MachineState *m) = (NULL);if (compat) {compat(machine);}pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \TYPE_I440FX_PCI_DEVICE);
}static void pc_machine_v8_1_class_init(ObjectClass *oc, void *data)
{MachineClass *mc = MACHINE_CLASS(oc);pc_i440fx_8_1_machine_options(mc);mc->init = pc_init_v8_1;
}
static const TypeInfo pc_machine_type_v8_1 = {.name       = "pc-i440fx-8.1" TYPE_MACHINE_SUFFIX,.parent     = TYPE_PC_MACHINE,.class_init = pc_machine_v8_1_class_init,
};
static void pc_machine_init_v8_1(void)
{type_register(&pc_machine_type_v8_1);
}
type_init(pc_machine_init_v8_1)

type_register函数在qom/object.c中,代码如下:

TypeImpl *type_register(const TypeInfo *info)
{assert(info->parent);return type_register_internal(info);
}

type_register_internal函数就在上边,代码如下:

static TypeImpl *type_register_internal(const TypeInfo *info)
{TypeImpl *ti;ti = type_new(info);type_table_add(ti);return ti;
}

 type_new函数也在qom/object.c中,代码如下:

static TypeImpl *type_new(const TypeInfo *info)
{TypeImpl *ti = g_malloc0(sizeof(*ti));int i;g_assert(info->name != NULL);if (type_table_lookup(info->name) != NULL) {fprintf(stderr, "Registering `%s' which already exists\n", info->name);abort();}ti->name = g_strdup(info->name);ti->parent = g_strdup(info->parent);ti->class_size = info->class_size;ti->instance_size = info->instance_size;ti->instance_align = info->instance_align;ti->class_init = info->class_init;ti->class_base_init = info->class_base_init;ti->class_data = info->class_data;ti->instance_init = info->instance_init;ti->instance_post_init = info->instance_post_init;ti->instance_finalize = info->instance_finalize;ti->abstract = info->abstract;for (i = 0; info->interfaces && info->interfaces[i].type; i++) {ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);}ti->num_interfaces = i;return ti;
}

type_table_add函数也在qom/object.c中,代码如下:

static void type_table_add(TypeImpl *ti)
{assert(!enumerating_types);g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}

每一个Module既然要模拟某种设备,那么就应该定义一种类型TypeImpl来表示这个设备。这其实是一种面向对象编程的思路,只不过这里用的是纯C语言的实现,因此需要变相实现一下类和对象。

static void pc_machine_init_v8_1(void)
{type_register(&pc_machine_type_v8_1);
}
type_init(pc_machine_init_v8_1)

pc_machine_init_v8_1函数会注册pc_machine_type_v8_1,可以认为这样就动态定义了一个类。

static const TypeInfo pc_machine_type_v8_1 = {.name       = "pc-i440fx-8.1" TYPE_MACHINE_SUFFIX,.parent     = TYPE_PC_MACHINE,.class_init = pc_machine_v8_1_class_init,
};

这个类的名字是“"pc-i440fx-8.1" TYPE_MACHINE_SUFFIX”,即"pc-i440fx-8.1-machine";这个类有父类TYPE_PC_MACHINE(即"generic-pc-machine");这个类的初始化应该调用函数pc_machine_v8_1_class_init。

这里的调用链为:

pc_machine_init_v8_1

        type_register

                type_register_internal

static void pc_machine_init_v8_1(void)
{type_register(&pc_machine_type_v8_1);
}
​static TypeImpl *type_register_internal(const TypeInfo *info)
{TypeImpl *ti;ti = type_new(info);type_table_add(ti);return ti;
}

在type_register_internal函数中,会根据pc_machine_type_v8_1这个TypeInfo ,创建一个TypeImpl来表示这个新注册的类,也就是说,TypeImpl才是真正想要声明的那个class。分别来看一下TypeInfo结构和TypeImpl结构的定义:

struct TypeInfo的定义在include/qom/object.h中,如下:

struct TypeInfo
{const char *name;const char *parent;size_t instance_size;size_t instance_align;void (*instance_init)(Object *obj);void (*instance_post_init)(Object *obj);void (*instance_finalize)(Object *obj);bool abstract;size_t class_size;void (*class_init)(ObjectClass *klass, void *data);void (*class_base_init)(ObjectClass *klass, void *data);void *class_data;InterfaceInfo *interfaces;
};

struct TypeImpl的定义在qom/object.c中,如下:

struct TypeImpl
{const char *name;size_t class_size;size_t instance_size;size_t instance_align;void (*class_init)(ObjectClass *klass, void *data);void (*class_base_init)(ObjectClass *klass, void *data);void *class_data;void (*instance_init)(Object *obj);void (*instance_post_init)(Object *obj);void (*instance_finalize)(Object *obj);bool abstract;const char *parent;TypeImpl *parent_type;ObjectClass *class;int num_interfaces;InterfaceImpl interfaces[MAX_INTERFACES];
};

由定义可见,TypeInfo结构和TypeImpl结构还是很相近的。

在QEMU中,有一个全局的哈希表type_table,用来存放所有定义的类。在type_new函数中,先从type_table表中根据名字查找某个类(此处是pc_machine_type_v8_1)。如果找到,说明这个类已经被注册过了,报错并终止当前进程;如果没有找到,则说明这是一个新的类,那么就将TypeInfo里边的信息填到TypeImpl中。

static TypeImpl *type_new(const TypeInfo *info)
{TypeImpl *ti = g_malloc0(sizeof(*ti));int i;g_assert(info->name != NULL);if (type_table_lookup(info->name) != NULL) {fprintf(stderr, "Registering `%s' which already exists\n", info->name);abort();}ti->name = g_strdup(info->name);ti->parent = g_strdup(info->parent);ti->class_size = info->class_size;ti->instance_size = info->instance_size;ti->instance_align = info->instance_align;ti->class_init = info->class_init;ti->class_base_init = info->class_base_init;ti->class_data = info->class_data;ti->instance_init = info->instance_init;ti->instance_post_init = info->instance_post_init;ti->instance_finalize = info->instance_finalize;ti->abstract = info->abstract;for (i = 0; info->interfaces && info->interfaces[i].type; i++) {ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);}ti->num_interfaces = i;return ti;
}

 之后,type_register_internal函数会调用type_table_add函数,将这个类注册到全局的表中。

​static TypeImpl *type_register_internal(const TypeInfo *info)
{TypeImpl *ti;ti = type_new(info);type_table_add(ti);return ti;
}

到这里,class_init还没有被调用,也即这个类还处于纸面的状态。

此处与Java中的反射机制有些类似。在Java中,对于一个类,首先写代码的时候要写一个class xxx的定义,编译好后就放在.class文件中,这也是处于纸面的状态。然后,Java中会有一个Class对象,用于读取和表示这个纸面上的class xxx,从而生成真正的对象。

在QEMU中,也会有相类似的过程。class_init会生成XXXClass,相当于Java中的Class xxx;TypeImpl中还会有一个instance_init函数,相当于构造函数,用于根据XXXClass生成Object,这就相当于Java反射中最终创建的对象。和构造函数对应的还有instance_finalize,相当于析构函数。

分析完了type_register这一支,正式开始解析主流程。

在QEMU的老版本中,主函数main中直接调用select_machine函数,而在新版本中,则是如下调用流程:

main()

        qemu_main()

                qemu_init()

                        qemu_create_machine

                                select_machine()

qemu_create_machine函数在softmmu/vl.c中,代码如下:

static void qemu_create_machine(QDict *qdict)
{MachineClass *machine_class = select_machine(qdict, &error_fatal);object_set_machine_compat_props(machine_class->compat_props);current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));object_property_add_child(object_get_root(), "machine",OBJECT(current_machine));object_property_add_child(container_get(OBJECT(current_machine),"/unattached"),"sysbus", OBJECT(sysbus_get_default()));if (machine_class->minimum_page_bits) {if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {/* This would be a board error: specifying a minimum smaller than* a target's compile-time fixed setting.*/g_assert_not_reached();}}cpu_exec_init_all();page_size_init();if (machine_class->hw_version) {qemu_set_hw_version(machine_class->hw_version);}/** Get the default machine options from the machine if it is not already* specified either by the configuration file or by the command line.*/if (machine_class->default_machine_opts) {QDict *default_opts =keyval_parse(machine_class->default_machine_opts, NULL, NULL,&error_abort);qemu_apply_legacy_machine_options(default_opts);object_set_properties_from_keyval(OBJECT(current_machine), default_opts,false, &error_abort);qobject_unref(default_opts);}
}

其中第1行代码就是select_machine函数,代码片段如下:

    MachineClass *machine_class = select_machine(qdict, &error_fatal);

顾名思义,select_machine函数的作用是选择一个MachineClass,其可能由用户指定,如果用户未指定,则采用系统默认。如果是后者,QEMU最新版本号对应的机器类型为默认设置。由于笔者的源码为qemu-8.1.4,因此默认机器类型是pc-i440fx-8.1-machine。而这就对应于:

static const TypeInfo pc_machine_type_v8_1 = {.name       = "pc-i440fx-8.1" TYPE_MACHINE_SUFFIX,.parent     = TYPE_PC_MACHINE,.class_init = pc_machine_v8_1_class_init,
};static void pc_machine_init_v8_1(void)
{type_register(&pc_machine_type_v8_1);
}

select_machine函数同样在softmmu/vl.c中,代码如下:

static MachineClass *select_machine(QDict *qdict, Error **errp)
{const char *optarg = qdict_get_try_str(qdict, "type");GSList *machines = object_class_get_list(TYPE_MACHINE, false);MachineClass *machine_class;Error *local_err = NULL;if (optarg) {machine_class = find_machine(optarg, machines);qdict_del(qdict, "type");if (!machine_class) {error_setg(&local_err, "unsupported machine type");}} else {machine_class = find_default_machine(machines);if (!machine_class) {error_setg(&local_err, "No machine specified, and there is no default");}}g_slist_free(machines);if (local_err) {error_append_hint(&local_err, "Use -machine help to list supported machines\n");error_propagate(errp, local_err);}return machine_class;
}

如上面所讲,在select_machine函数中,有两种方式可以生成MachineClass:一种方式是调用find_machine函数,通过解析QEMU命令行参数生成MachineClass,即用户指定方式;另一种方式是通过find_default_machine函数找一个默认的MachineClass,即系统默认方式。

无论是用户指定还是系统默认方式,都得先调用object_class_get_list函数获得一个MachineClass列表,然后在里边找。代码片段如下:

    GSList *machines = object_class_get_list(TYPE_MACHINE, false);

object_class_get_list函数在qom/object.c中,代码如下:

GSList *object_class_get_list(const char *implements_type,bool include_abstract)
{GSList *list = NULL;object_class_foreach(object_class_get_list_tramp,implements_type, include_abstract, &list);return list;
}

object_class_foreach函数在同文件中,代码如下:

void object_class_foreach(void (*fn)(ObjectClass *klass, void *opaque),const char *implements_type, bool include_abstract,void *opaque)
{OCFData data = { fn, implements_type, include_abstract, opaque };enumerating_types = true;g_hash_table_foreach(type_table_get(), object_class_foreach_tramp, &data);enumerating_types = false;
}

在全局表type_table_get()中,对于每一项TypeImpl,都执行object_class_foreach_tramp。object_class_foreach_tramp函数在qom/object.c中。代码如下:

static void object_class_foreach_tramp(gpointer key, gpointer value,gpointer opaque)
{OCFData *data = opaque;TypeImpl *type = value;ObjectClass *k;type_initialize(type);k = type->class;if (!data->include_abstract && type->abstract) {return;}if (data->implements_type && !object_class_dynamic_cast(k, data->implements_type)) {return;}data->fn(k, data->opaque);
}

在object_class_foreach_tramp函数中,会调用type_initialize函数,该函数在同文件中,代码如下:

static void type_initialize(TypeImpl *ti)
{TypeImpl *parent;if (ti->class) {return;}ti->class_size = type_class_get_size(ti);ti->instance_size = type_object_get_size(ti);/* Any type with zero instance_size is implicitly abstract.* This means interface types are all abstract.*/if (ti->instance_size == 0) {ti->abstract = true;}if (type_is_ancestor(ti, type_interface)) {assert(ti->instance_size == 0);assert(ti->abstract);assert(!ti->instance_init);assert(!ti->instance_post_init);assert(!ti->instance_finalize);assert(!ti->num_interfaces);}ti->class = g_malloc0(ti->class_size);parent = type_get_parent(ti);if (parent) {type_initialize(parent);GSList *e;int i;g_assert(parent->class_size <= ti->class_size);g_assert(parent->instance_size <= ti->instance_size);memcpy(ti->class, parent->class, parent->class_size);ti->class->interfaces = NULL;for (e = parent->class->interfaces; e; e = e->next) {InterfaceClass *iface = e->data;ObjectClass *klass = OBJECT_CLASS(iface);type_initialize_interface(ti, iface->interface_type, klass->type);}for (i = 0; i < ti->num_interfaces; i++) {TypeImpl *t = type_get_by_name(ti->interfaces[i].typename);if (!t) {error_report("missing interface '%s' for object '%s'",ti->interfaces[i].typename, parent->name);abort();}for (e = ti->class->interfaces; e; e = e->next) {TypeImpl *target_type = OBJECT_CLASS(e->data)->type;if (type_is_ancestor(target_type, t)) {break;}}if (e) {continue;}type_initialize_interface(ti, t, t);}}ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,object_property_free);ti->class->type = ti;while (parent) {if (parent->class_base_init) {parent->class_base_init(ti->class, ti->class_data);}parent = type_get_parent(parent);}if (ti->class_init) {ti->class_init(ti->class, ti->class_data);}
}

在type_initialize函数中(最后一部分),会调用class_init,将纸面上的class也即TypeImpl变为ObjectClass。ObjectClass是所有类的祖先,MachineClass是它的子类。在这里,肯定可以找到之前注册过的TypeImpl,并调用其class_init函数。

因而,pc_machine_##suffix##class_init(本例中是pc_machine_v8_1_class_init)会被调用。

static void pc_machine_v8_1_class_init(ObjectClass *oc, void *data)
{MachineClass *mc = MACHINE_CLASS(oc);pc_i440fx_8_1_machine_options(mc);mc->init = pc_init_v8_1;
}

在此函数中,pc_i440fx_machine_options才真正由pc_i440fx_8_1machine_options调用,从而初始化MachineClass。

pc_i440fx_8_1machine_options函数在hw/i386/pc_piix.c中,代码如下:

static void pc_i440fx_8_1_machine_options(MachineClass *m)
{pc_i440fx_machine_options(m);m->alias = "pc";m->is_default = true;
}

pc_i440fx_machine_options函数就在上边,代码如下:

static void pc_i440fx_machine_options(MachineClass *m)
{PCMachineClass *pcmc = PC_MACHINE_CLASS(m);pcmc->pci_root_uid = 0;pcmc->default_cpu_version = 1;m->family = "pc_piix";m->desc = "Standard PC (i440FX + PIIX, 1996)";m->default_machine_opts = "firmware=bios-256k.bin";m->default_display = "std";m->default_nic = "e1000";m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
}

pc_machine_v8_1_class_init函数接下来将MachineClass的init函数设置为pc_init##suffix。

    mc->init = pc_init_v8_1;

因此,在select_machine函数执行完毕后,就有一个MachineClass了。

MachineClass的定义在include/qemu/typedefs.h中,如下:

typedef struct MachineClass MachineClass;

struct MachineClass的定义在include/hw/boards.h中,如下:

struct MachineClass {/*< private >*/ObjectClass parent_class;/*< public >*/const char *family; /* NULL iff @name identifies a standalone machtype */char *name;const char *alias;const char *desc;const char *deprecation_reason;void (*init)(MachineState *state);void (*reset)(MachineState *state, ShutdownCause reason);void (*wakeup)(MachineState *state);int (*kvm_type)(MachineState *machine, const char *arg);BlockInterfaceType block_default_type;int units_per_default_bus;int max_cpus;int min_cpus;int default_cpus;unsigned int no_serial:1,no_parallel:1,no_floppy:1,no_cdrom:1,no_sdcard:1,pci_allow_0_address:1,legacy_fw_cfg_order:1;bool is_default;const char *default_machine_opts;const char *default_boot_order;const char *default_display;const char *default_nic;GPtrArray *compat_props;const char *hw_version;ram_addr_t default_ram_size;const char *default_cpu_type;bool default_kernel_irqchip_split;bool option_rom_has_mr;bool rom_file_has_mr;int minimum_page_bits;bool has_hotpluggable_cpus;bool ignore_memory_transaction_failures;int numa_mem_align_shift;const char **valid_cpu_types;strList *allowed_dynamic_sysbus_devices;bool auto_enable_numa_with_memhp;bool auto_enable_numa_with_memdev;bool ignore_boot_device_suffixes;bool smbus_no_migration_support;bool nvdimm_supported;bool numa_mem_supported;bool auto_enable_numa;bool cpu_cluster_has_numa_boundary;SMPCompatProps smp_props;const char *default_ram_id;HotplugHandler *(*get_hotplug_handler)(MachineState *machine,DeviceState *dev);bool (*hotplug_allowed)(MachineState *state, DeviceState *dev,Error **errp);CpuInstanceProperties (*cpu_index_to_instance_props)(MachineState *machine,unsigned cpu_index);const CPUArchIdList *(*possible_cpu_arch_ids)(MachineState *machine);int64_t (*get_default_cpu_node_id)(const MachineState *ms, int idx);ram_addr_t (*fixup_ram_size)(ram_addr_t size);
};

回到qemu_create_machine函数中,

static void qemu_create_machine(QDict *qdict)
{MachineClass *machine_class = select_machine(qdict, &error_fatal);object_set_machine_compat_props(machine_class->compat_props);current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));object_property_add_child(object_get_root(), "machine",OBJECT(current_machine));object_property_add_child(container_get(OBJECT(current_machine),"/unattached"),"sysbus", OBJECT(sysbus_get_default()));if (machine_class->minimum_page_bits) {if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {/* This would be a board error: specifying a minimum smaller than* a target's compile-time fixed setting.*/g_assert_not_reached();}}cpu_exec_init_all();page_size_init();if (machine_class->hw_version) {qemu_set_hw_version(machine_class->hw_version);}/** Get the default machine options from the machine if it is not already* specified either by the configuration file or by the command line.*/if (machine_class->default_machine_opts) {QDict *default_opts =keyval_parse(machine_class->default_machine_opts, NULL, NULL,&error_abort);qemu_apply_legacy_machine_options(default_opts);object_set_properties_from_keyval(OBJECT(current_machine), default_opts,false, &error_abort);qobject_unref(default_opts);}
}

在select_machine函数执行完毕后,即获得了一个MachineClass之后,接下来来到以下代码片段:

    current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));

MACHINE函数的代码在include/hw/boards.h中,如下:

static inline G_GNUC_UNUSED MachineState *MACHINE(const void *obj)
{return OBJECT_CHECK(MachineState, obj, TYPE_MACHINE);
}

OBJECT_CHECK宏展开后的函数代码如下:

static inline G_GNUC_UNUSED MachineState *MACHINE(const void *obj)
{return ((MachineState*)object_dynamic_cast_assert(OBJECT(obj), ("machine"), __FILE__, __LINE__, __func__));
}

现在要回过头来看qemu_create_machine函数(softmmu/vl.c中)调用MACHINE函数时,传递给它的实参:object_new(object_class_get_name(OBJECT_CLASS(machine_class)))。

现在object_new_with_class以及object_new函数成为了关注焦点。这两个函数都在qom/object.c中,代码分别如下:

Object *object_new(const char *typename)
{TypeImpl *ti = type_get_by_name(typename);return object_new_with_type(ti);
}
Object *object_new_with_class(ObjectClass *klass)
{return object_new_with_type(klass->type);
}

可以看到,甭管是哪一个函数,最终都会调用到object_new_with_type函数。

object_new_with_type函数也在qom/object.c中,代码如下:

static Object *object_new_with_type(Type type)
{Object *obj;size_t size, align;void (*obj_free)(void *);g_assert(type != NULL);type_initialize(type);size = type->instance_size;align = type->instance_align;/** Do not use qemu_memalign unless required.  Depending on the* implementation, extra alignment implies extra overhead.*/if (likely(align <= __alignof__(qemu_max_align_t))) {obj = g_malloc(size);obj_free = g_free;} else {obj = qemu_memalign(align, size);obj_free = qemu_vfree;}object_initialize_with_type(obj, size, type);obj->free = obj_free;return obj;
}

回到object_new函数中。TypeImpl的instance_init会被调用,创建一个对象。current_machine就是这个对象,其类型是MachineState。

​Object *object_new(const char *typename)
{TypeImpl *ti = type_get_by_name(typename);return object_new_with_type(ti);
}

至此,兜兜转转一大圈,相关体系结构的对象才创建完毕。整体流程如下图所示(图片援引《趣谈Linux系统》50 | 计算虚拟化之CPU(上):如何复用集团的人力资源?):

欲知后事如何,且看下回分解。

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

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

相关文章

springboot外出务工人员信息管理系统源码和论文

网络的广泛应用给生活带来了十分的便利。所以把疫情防控期间某村外出务工人员信息管理与现在网络相结合&#xff0c;利用java技术建设疫情防控期间某村外出务工人员信息管理系统&#xff0c;实现疫情防控期间某村外出务工人员信息的信息化。则对于进一步提高疫情防控期间某村外…

前端Vue v-for 的使用

目录 ​编辑 简介 使用方式 基本使用 v-for"(item, index)中item和index作用 示例 迭代对象 示例 结果 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入…

《骑马与砍杀》背包系统实现

一、 效果展示 二、 源代码 1&#xff0c;ItemManager.cs ​​//逻辑简介&#xff1a;该脚本作用是生成背包格子和加载背包物品&#xff0c;获得被拖拽物品和目标物品 //配置&#xff1a;1&#xff0c;需要Scroll View来存放背包物品。 //2&#xff0c;将要作为模板的物品名字…

Java项目:基于SSM框架实现的企业员工岗前培训管理系统(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm821基于ssm框架实现的企业员工岗前培训管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格…

Qt SQLite3数据库加密 QtCipherSqlitePlugin

在客户端软件开发过程中&#xff0c;基本都会涉及到数据库的开发。QT支持的数据库也有好几种&#xff08;QSQLITE, QODBC, QODBC3, QPSQL, QPSQL7&#xff09;&#xff0c;SQLite就是其中之一&#xff0c;但这个 SQLite 是官方提供的开源版本&#xff0c;没有加密功能的。如果对…

如何在 Ubuntu 中安装 Microsoft Edge 浏览器

微软终于聪明了一回&#xff0c;也学会了「打不过就加入」。Microsoft Edge 浏览器的 Linux 稳定版已经于 2020 年 10 月 23 日发布&#xff0c;并提供给 Linux 发行版使用。除了官方 Edge APT 源以外&#xff0c;还提供了.deb和.rpm格式的安装包。 Microsoft Edge 基于 Chrom…

使用WAF防御网络上的隐蔽威胁之反序列化攻击

​ 什么是反序列化 反序列化是将数据结构或对象状态从某种格式转换回对象的过程。这种格式通常是二进制流或者字符串&#xff08;如JSON、XML&#xff09;&#xff0c;它是对象序列化&#xff08;即对象转换为可存储或可传输格式&#xff09;的逆过程。 反序列化的安全风险 反…

计算机毕业设计 | vue+springboot 超市账单管理系统(附源码)

1&#xff0c;绪论 1.1 开发背景 世界上第一个购物中心诞生于美国纽约&#xff0c;外国人迈克尔库伦开设了第一家合作商店&#xff0c;为了更好地吸引大量客流量&#xff0c;迈克尔库伦精心设计了低价策略&#xff0c;通过大量进货把商品价格压低&#xff0c;通过商店一次性集…

世微AP5151芯片低压差 线性降压 恒流驱动IC 台灯 指示灯 矿灯

概述 AP5151 是一种低压差、线性降压、 固定输出电流的 LED 恒流驱动器。 除 LED 外&#xff0c;AP5151 无需外接其它元 器件即可构成一个恒流输出的 LED 驱动 电路。 AP5151 内置过热保护功能&#xff0c;可有效 保护芯片&#xff0c;避免结温超过 120oC 时因过热 而造成损坏…

宏景-eHR-frcodeaddtreeservlet接口存在SQL注入

指纹特征 FOFA&#xff1a;icon_hash"947874108" || body<div class"hj-hy-all-one-logo" || app"HJSOFT-HCM" 漏洞复现 POST /templates/attestation/../../servlet/FrCodeAddTreeServlet HTTP/1.1 Host: User-Agent: Mozilla/5.0 (Windo…

数字艺术展厅有什么好处,搭建数字艺术展厅要注意什么

引言&#xff1a; 数字艺术展厅是一种利用数字科技手段搭建的艺术展览空间&#xff0c;通过数字化展示艺术品&#xff0c;能够为观众带来全新的艺术体验。那么数字艺术展厅有什么好处&#xff0c;搭建数字艺术展厅要注意什么呢&#xff1f; 一、数字艺术展厅的好处 1.创新艺术…

PID校正

一、Introduction to PID Control PID控制是一种应用非常广泛的控制算法。小到控制一个元件的温度&#xff0c;大到控制无人机的飞行姿态和飞行速度等等&#xff0c;都可以使用PID控制。PID(proportion integration differentiation)其实就是指比例&#xff0c;积分&#xff0…