在WIN从零开始在QMUE上添加一块自己的开发板(一)

文章目录

  • 一、前言
  • 二、源码编译
    • (一)安装Msys2
    • (二)配置GCC工具链
    • (三)安装QEMU构建依赖
    • (四)下载编译QEMU源码
  • 二、QUME编程基础
    • (一)QOM机制
    • (二)将 TypeInfo 注册 TypeImpl
    • (三)测试
    • (四)从结果中的反思
  • 参考资料

一、前言

笔者这篇博客作为平时学习时的笔记记录,如有不对还望指正,本博客大量借鉴资料,笔者只是拾人牙慧的小屁孩。
QEMU是一种通用的开源计算机仿真器和虚拟器。而QUME内置支持了一些开发板,我们可以基于这些内置的板子来做操作系统等软件的配置,但是实际市面上很多板子QUME中是没有提供支持的,这需要我们根据QUME的源码自定义一些开发板,然后再重新编译。

二、源码编译

笔者是在Win系统上利用Msys2进行的QUME源码编译。

(一)安装Msys2

打开 https://www.msys2.org/ ,下载最新Msys2的安装包并安装。

MSYS2的安装
完成安装后,我们先进行更新源。
(笔者的安装路径为:C:\msys64
进入目录C:\msys64\etc\pacman.d

  • 在文件mirrorlist.msys的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch

  • 在文件mirrorlist.mingw32的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686

  • 在文件mirrorlist.mingw64的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/mingw/x86_64

然后我们启动 MSYS2 终端(MSYS2 MINGW64),进行更新:

pacman -Syu
pacman -Su

(二)配置GCC工具链

pacman -Sy mingw-w64-x86_64-toolchain

(三)安装QEMU构建依赖

pacman -Sy mingw-w64-x86_64-meson mingw-w64-x86_64-ninja \mingw-w64-x86_64-python \mingw-w64-x86_64-python-sphinx \mingw-w64-x86_64-python-sphinx_rtd_theme \mingw-w64-x86_64-autotools \mingw-w64-x86_64-tools-git \mingw-w64-x86_64-cc \mingw-w64-x86_64-angleproject \mingw-w64-x86_64-capstone \mingw-w64-x86_64-curl \mingw-w64-x86_64-cyrus-sasl \mingw-w64-x86_64-expat \mingw-w64-x86_64-fontconfig \mingw-w64-x86_64-freetype \mingw-w64-x86_64-fribidi \mingw-w64-x86_64-gcc-libs \mingw-w64-x86_64-gdk-pixbuf2 \mingw-w64-x86_64-gettext \mingw-w64-x86_64-glib2 \mingw-w64-x86_64-gmp \mingw-w64-x86_64-gnutls \mingw-w64-x86_64-graphite2 \mingw-w64-x86_64-gst-plugins-base \mingw-w64-x86_64-gstreamer \mingw-w64-x86_64-gtk3 \mingw-w64-x86_64-harfbuzz \mingw-w64-x86_64-jbigkit \mingw-w64-x86_64-lerc \mingw-w64-x86_64-libc++ \mingw-w64-x86_64-libdatrie \mingw-w64-x86_64-libdeflate \mingw-w64-x86_64-libepoxy \mingw-w64-x86_64-libffi \mingw-w64-x86_64-libiconv \mingw-w64-x86_64-libidn2 \mingw-w64-x86_64-libjpeg-turbo \mingw-w64-x86_64-libnfs \mingw-w64-x86_64-libpng \mingw-w64-x86_64-libpsl \mingw-w64-x86_64-libslirp \mingw-w64-x86_64-libssh \mingw-w64-x86_64-libssh2 \mingw-w64-x86_64-libtasn1 \mingw-w64-x86_64-libthai \mingw-w64-x86_64-libtiff \mingw-w64-x86_64-libunistring \mingw-w64-x86_64-libunwind \mingw-w64-x86_64-libusb \mingw-w64-x86_64-libwebp \mingw-w64-x86_64-libwinpthread-git \mingw-w64-x86_64-lz4 \mingw-w64-x86_64-lzo2 \mingw-w64-x86_64-nettle \mingw-w64-x86_64-openssl \mingw-w64-x86_64-opus \mingw-w64-x86_64-orc \mingw-w64-x86_64-p11-kit \mingw-w64-x86_64-pango \mingw-w64-x86_64-pixman \mingw-w64-x86_64-SDL2 \mingw-w64-x86_64-SDL2_image \mingw-w64-x86_64-snappy \mingw-w64-x86_64-spice \mingw-w64-x86_64-usbredir \mingw-w64-x86_64-xz \mingw-w64-x86_64-zlib \mingw-w64-x86_64-zstd

(四)下载编译QEMU源码

mkdir qemu
cd qemu/

下载QUME的版本为8.2.0
QEMU源码

源码下载与编译:
(这里需要管理员权限打开Msys2)

wget https://download.qemu.org/qemu-8.2.0.tar.xz
tar xvJf qemu-8.2.0.tar.xz
cd qemu-8.2.0/
./configure
make -j8

编译完成后会生成一个./build目录

 cd build/make install

之后我们测试一下——查看QEMU的版本号:

Whisky@LAPTOP-ILRB6MKK MINGW64 ~/qemu/qemu-8.2.0/build
$ ./qemu-img -V
qemu-img version 8.2.0
Copyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers

启动QEMU:
这里以riscv32为例
启动QEMU
至此我们已经编译完了QUME的源码了。

二、QUME编程基础

QEMU是一款开源的模拟器及虚拟机监管器(Virtual Machine Monitor, VMM),通过动态二进
制翻译来模拟CPU,并提供一系列的硬件模型,使guest os认为自己和硬件直接打交道,其实
是同QEMU模拟出来的硬件打交道,QEMU再将这些指令翻译给真正硬件进行操作。

(一)QOM机制

QOM——The QEMU Object Model
QEMU提供了一套面向对象编程的模型——QOM,即QEMU Object Module,几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。
QEMU对象模型提供了一个注册用户可创建类型并从这些类型实例化对象的框架。
其实也就是一种OOP IN C(C上实现面对对象)。
一段面对对象的程序代码(C++语言)

class MyClass {
public:int a;void set_A(int a);
}

切换为C语言也就是:

struct MyClass {int a;void (*set_A)(MyClass *this, int a);
}

当然,这只是一个例子。
在QUME中,我们通常一个对象的初始化分为四步:

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

QOM模型的实现代码位于qom/文件夹下的文件中,这涉及了几个结构TypeImpl, ObjectClass, ObjectTypeInfo。看了下它们的定义都在/include/qom/object.h可以找到,只有TypeImpl的具体结构是在/qom/object.c中。

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

(二)将 TypeInfo 注册 TypeImpl

struct TypeInfo
{const char *name;const char *parent;size_t instance_size;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;
};

其中的重点有:

  1. Name :包含了自己的名字name和parent的名字的parent
  2. Class(针对ObjectClass) : ObjectClass的信息包括,class_sizeclass_data,class相关函数:class_base_initclass_initclass_finalize等。
    这些函数都是为了初始化,释放结构体ObjectClass。
  3. Instance(针对的是Object): 对象Object信息包括:instance_size,instance相关函数:instance_post_initinstance_initinstance_finalize
    这些函数都是为了初始化,释放结构体Object。
  4. 其他信息:abstract是否为抽象。interface数组。

一般是定义一个TypeInfo,然后调用 type_register(TypeInfo) 或者 type_register_static(TypeInfo) 函数(使用type_register_static比较多),就会生成相应的TypeImpl实例,将这个TypeInfo注册到全局的TypeImpl的hash表中。
我们来看一个例程:

#define TYPE_MY_DEVICE "my-device"static void my_device_class_init(ObjectClass *oc, void *data)
{
}
static void my_device_init(Object *obj)
{
}typedef struct MyDeviceClass
{DeviceClass parent;void (*init) (MyDevice *obj);
} MyDeviceClass;typedef struct MyDevice
{DeviceState parent;int reg0, reg1, reg2;
}MyDevice;static const TypeInfo my_device_info = {.name = TYPE_MY_DEVICE,.parent = TYPE_DEVICE,.instance_size = sizeof(MyDevice),.instance_init = my_device_init,.class_size = sizeof(MyDeviceClass),.class_init = my_device_class_init,
};static void my_device_register_types(void)
{type_register_static(&my_device_info);
}
type_init(my_device_register_types)

当然,其中的代码

static void my_device_register_types(void)
{type_register_static(&my_device_info);
}
type_init(my_device_register_types)

也可以简化为

DEFINE_TYPES(my_device_infos)

举个实际的例子

  1. 定义设备
/* SOC state定义 */
#define TYPE_NUCLEI_HBIRD_SOC "riscv.nuclei.hbird.soc"
#define RISCV_NUCLEI_HBIRD_SOC(obj) \
OBJECT_CHECK(NucleiHBSoCState, (obj), TYPE_NUCLEI_HBIRD_SO
C)
typedef struct NucleiHBSoCState
{/*< private >*/SysBusDevice parent_obj;/*< public >*/
} NucleiHBSoCState;/* Machine state定义 */
#define TYPE_HBIRD_FPGA_MACHINE MACHINE_TYPE_NAME("hbird_fpga")
#define HBIRD_FPGA_MACHINE(obj) \
OBJECT_CHECK(NucleiHBState, (obj), TYPE_HBIRD_FPGA_MACHINE)
typedef struct
{/*< private >*/SysBusDevice parent_obj;/*< public >*/NucleiHBSoCState soc;
} NucleiHBState;
  1. SOC设备注册
static void nuclei_soc_init(Object *obj)
{qemu_log(">>nuclei_soc_init \n");
}
static void nuclei_soc_realize(DeviceState *dev, Error **errp)
{qemu_log(">>nuclei_soc_realize \n");
}
static void nuclei_soc_class_init(ObjectClass *oc, void *data)
{qemu_log(">>nuclei_soc_class_init \n");DeviceClass *dc = DEVICE_CLASS(oc);dc->realize = nuclei_soc_realize;dc->user_creatable = false;
}static const TypeInfo nuclei_soc_type_info = {.name = TYPE_NUCLEI_HBIRD_SOC,.parent = TYPE_DEVICE,.instance_size = sizeof(NucleiHBSoCState),.instance_init = nuclei_soc_init,.class_init = nuclei_soc_class_init,
};
static void nuclei_soc_register_types(void)
{
type_register_static(&nuclei_soc_type_info);
}
type_init(nuclei_soc_register_types)

可以看见我们是在nuclei_soc_class_init设定了实例的成员函数实现nuclei_soc_realize
这里是需要理清的关系。

  1. Machine设备注册
static void nuclei_board_init(MachineState *machine)
{NucleiHBState *s = HBIRD_FPGA_MACHINE(machine);qemu_log(">>nuclei_board_init \n");/* Initialize SOC */object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_HBIRD_SOC);qdev_realize(DEVICE(&s->soc), NULL, &error_abort);
}
static void nuclei_machine_instance_init(Object *obj)
{qemu_log(">>nuclei_machine_instance_init \n");
}
static void nuclei_machine_class_init(ObjectClass *oc, void *data)
{qemu_log(">>nuclei_machine_class_init \n");MachineClass *mc = MACHINE_CLASS(oc);mc->desc = "Nuclei HummingBird Evaluation Kit";mc->init = nuclei_board_init;
}static const TypeInfo nuclei_machine_typeinfo = {.name = MACHINE_TYPE_NAME("hbird_fpga"),.parent = TYPE_MACHINE,.class_init = nuclei_machine_class_init,.instance_init = nuclei_machine_instance_init,.instance_size = sizeof(NucleiHBState),
};
static void nuclei_machine_init_register_types(void)
{type_register_static(&nuclei_machine_typeinfo);
}
type_init(nuclei_machine_init_register_types)
  1. 修改编译文件

hw/riscv/Kconfig:

config NUCLEI_N
bool
select MSI_NONBROKEN
select UNIMP

hw/riscv/meson.build:

riscv_ss = ss.source_set()
riscv_ss.add(files('boot.c'), fdt)
riscv_ss.add(files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
...
riscv_ss.add(when: 'CONFIG_NUCLEI_N', if_true: files('nuclei_n.c'))hw_arch += {'riscv': riscv_ss}

configs\devices\riscv32-softmmu\default.mak:

...
CONFIG_NUCLEI_N=y

编译参数:

./configure --target-list=riscv32-softmmu
make -j16

(三)测试

编译完成后,我们进行安装(Msys2在管理员权限下运行)

make install

当然,为了方便我们测试,也可以编写脚本,然后不混用build文件夹,保证我们自己平时也能使用qume纯净版:

build.sh:

# 获取当前脚本文件所在的目录
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)if [ ! -d "$SHELL_FOLDER/output/qemu" ]; then  
./configure --prefix=$SHELL_FOLDER/output/qemu  --target-list=riscv32-softmmu
fi  
make -j8
make install
cd ..

run.sh:

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
$SHELL_FOLDER/output/qemu/qemu-system-riscv32.exe \
-M hbird_fpga 

安装完成后
我们开始测试。
先看看板子的列表:

./qemu-system-riscv32.exe -M ?

得到的板子列表中有我们刚刚编写的板子:

Supported machines are:
hbird_fpga           Nuclei HummingBird Evaluation Kit
none                 empty machine
opentitan            RISC-V Board compatible with OpenTitan
sifive_e             RISC-V Board compatible with SiFive E SDK
sifive_u             RISC-V Board compatible with SiFive U SDK
spike                RISC-V Spike board (default)
virt                 RISC-V VirtIO board

我们直接运行这块板子:

./qemu-system-riscv32.exe -M hbird_fpga
>>nuclei_soc_class_init
>>nuclei_machine_class_init
>>nuclei_machine_instance_init
>>nuclei_board_init
>>nuclei_soc_init
>>nuclei_soc_realize

测试结果

(四)从结果中的反思

ObjectClass的初始化
在测试结果中,我们还可以回味整个QUME的运行流程。
首先在我们注册TypeInfo时,其类的构造函数会在其创建其类的时候执行,也就是在TypeImpl的hash表已经有了之后,下一步要初始化每个type的时候。(这一步可以看成是class的初始化,可以理解成每一个type对应了一个class,接下来会初始化class)
main函数中的module_call_init(MODULE_INIT_QOM);调用了MODULE_INIT_QOM类型的ModuleTypeList中的所有ModuleEntry中的init()函数,也就是第一步type_init的第一个参数XXX_register_types函数指针。(__attribute__((constructor))的修饰让type_initmain之前执行,type_init的参数是XXX_register_types函数指针,将函数指针传递到ModuleEntryinit函数指针,最后就是将这个ModuleEntry插入到ModuleTypeList)那接下来就是XXX_register_types函数的操作了,就是一个个创建完TypeImpl的哈希表。

如果这里有看不懂,可以深究QEMU 的一些基础知识及QOM(Qemu Object Model)的部分相关源码阅读。

之后main函数会调用machine_class = select_machine();在里面的调用链中将会有ti->class_init初始化的实现。
所以,会首先看见

>>nuclei_soc_class_init
>>nuclei_machine_class_init

实例化 Instance(Object)
其次,我们发现main函数接下来调用了qemu_opts_foreach,循环查找参数(options):

qemu_opts_foreach(qemu_find_opts("device"),default_driver_check, NULL, NULL);
qemu_opts_foreach(qemu_find_opts("device"),device_help_func, NULL, NULL)
...
qemu_opts_foreach(qemu_find_opts("device"),device_init_func, NULL, &error_fatal);

前二者default_driver_checkdevice_help_func参数的qemu_opts_foreach输出driver的help信息,还有那些option什么的。
重点在device_init_func参数的qemu_opts_foreach,在其中调用了qdev_device_add。而在qdev_device_add里面,重要的一行是调用了dev = DEVICE(object_new(driver));,而且上一行有个注释——/* create device */
DEVICE是一个宏,实际是OBJECT_CHECK,主要是是看看obj是否是TYPE_DEVICE的一个实例:

#define DEVICE(obj) OBJECT_CHECK(DeviceState, (obj), TYPE_DEVICE)
#define OBJECT_CHECK(type, obj, name) \((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \__FILE__, __LINE__, __func__))

更重要的是object_new(driver),它利用object_new_with_type进行实例:
它调用type_initialize,在其中调用parentclass_base_init进行初始化,最后调用自己class_init进行初始化。
其次调用object_init_with_type函数首先判断ti是否有parent(即type->parent != NULL),有parent就会递归调用object_init_with_type,最终就是调用ti->instance_init函数。

所以,再接着是

>>nuclei_machine_instance_init

之后又因为我们在nuclei_machine_class_init中赋值mc->init = nuclei_board_init;,所以执行ti->instance_init

>>nuclei_board_init

当然我们知道,在nuclei_board_init里面,我们进行了SOC的实例化:

	object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_HBIRD_SOC);qdev_realize(DEVICE(&s->soc), NULL, &error_abort);

所以最后:

>>nuclei_soc_init
>>nuclei_soc_realize

参考资料

  1. 如何在 Windows 10/11 上构建 QEMU
  2. 在Windows上编译QEMU
  3. 从源码构建Qemu
  4. [完结]从零开始的RISC-V模拟器开发·第一季·2021春季
  5. QEMU 的一些基础知识及QOM(Qemu Object Model)的部分相关源码阅读

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

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

相关文章

LabVIEW振动筛螺栓松动故障诊断

LabVIEW振动筛螺栓松动故障诊断 概述&#xff1a;利用LabVIEW解决振动筛螺栓松动的故障诊断问题。通过集成的方法&#xff0c;不仅提高了故障检测的准确性&#xff0c;还优化了维护流程&#xff0c;为类似的机械设备故障提供了可靠的解决方案。 由于工作条件复杂&#xff0c;…

Linux系统安装NFS服务器

NFS是一种网络文件系统&#xff0c;英文全称Network File System&#xff0c;通过NFS可以让不同的主机系统之间共享文件或目录。通过NFS&#xff0c;用户可以直接在本地NFS客户端读写NFS服务端上的文件&#xff0c;是非常好的共享存储工具。本篇文章将介绍如何在CentOS7上安装N…

android 开发 W/TextToSpeech: speak failed: not bound to TTS engine

问题 笔者使用TTS(TextToSpeech)对于文本内容进行语音播报&#xff0c;控制台报错 android 开发 speak failed:not bound to TTS engine详细问题 笔者核心代码&#xff1a; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.…

[嵌入式软件][入门篇][仿真平台] STM32F103实现LED、按键

上一篇&#xff1a;[嵌入式软件][入门篇] 搭建在线仿真平台(STM32) 文章目录 一、点亮LED灯(1) 简介(2) 示例代码(3) 仿真效果&#xff08;闪烁&#xff09; 二、按键检测(1) 简介1. 按键原理2. 检测按键端口3. 消抖 (2) 示例代码1 &#xff08;按下点亮&#xff0c;松开熄灭&a…

一个简单的Web程序(详解创建一个Flask项目后自带的一个简单的Web程序)

程序代码截图如下&#xff1a; 1.应用初始化 在创建 Flask 程序时&#xff0c;通常需要先创建一个应用实例进行应用初始化。 from flask import Flask # 应用的初始化 app Flask(__name__) 上述代码中&#xff0c;使用 Flask 类创建了一个应用实例 app。 __name__ 参数用…

mp4文件可以转成mp3音频吗

现在是个非常流行刷短视频一个年代&#xff0c;刷短视似乎成了人们休闲娱乐的一种方式&#xff0c;在日常刷短视频过程中&#xff0c;肯定会有很多同学被短视频 bgm 神曲洗脑&#xff0c;比如很多被网红翻唱带火的歌曲&#xff0c;例如其中"不负人间”&#xff0c;就是其中…

Python 散点图的绘制(Seaborn篇-03)

Python 散点图的绘制(Seaborn篇-03)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

BuildRoot配置RTL8822CE WIFIBT模块(BT部分)

BuildRoot配置RTL8822CE WIFI&BT模块&#xff08;WIFI部分&#xff09;-CSDN博客 WIFI部分更新一下&#xff1a; ①、rkwifibt.mk 查看了output/rockchip_rk3399_tinkerboard2/build/的两个目录都有wifi相关的ko(后面make clean之后剩下linux-headers-custom路径的)&…

使用函数计算,数禾如何实现高效的数据处理?

作者&#xff1a;邱鑫鑫&#xff0c;王彬&#xff0c;牟柏旭 公司背景和业务 数禾科技以大数据和技术为驱动&#xff0c;为金融机构提供高效的智能零售金融解决方案&#xff0c;服务银行、信托、消费金融公司、保险、小贷公司等持牌金融机构&#xff0c;业务涵盖消费信贷、小…

菜鸟关于做前、后端的整理(html、js),以及疑问

涉及到后端的接口py&#xff0c;前端html和js 这三部分就按照如下格式放到server项目主路径下&#xff0c;这样后端机可以作为一个前端server main.pystaticmain.jsmain.htmlhtml 首先是html要设定网页的显示 <!DOCTYPE html> <html> <head><title>…

电力能源实景三维可视化合集,智慧电网数字孪生

电力能源是现代社会发展和运行的基石&#xff0c;渗透于工业、商业、农业、家庭生活等方方面面&#xff0c;它为经济、生活质量、环境保护和社会发展提供了巨大的机会和潜力。图扑软件应用自研 HT for Web 强大的渲染引擎&#xff0c;助力现代化的电力能源数字孪生场景&#xf…

【Vue】vue项目中Uncaught runtime errors:怎样关闭

vue项目中Uncaught runtime errors:怎样关闭 一、背景描述二、报错原因三、解决方案3.1 只显示错误信息不全屏覆盖3.2 取消全屏覆盖 四、参考资料 一、背景描述 项目本来运行的好好&#xff0c;换了个新的浏览器&#xff0c;新的Chrome浏览器版本号是116.0.5845.97&#xff08…

视频增强修复Topaz Video AI

Topaz Video AI是一款强大的视频增强软件&#xff0c;利用人工智能技术对数千个视频进行训练&#xff0c;结合多个输入视频的帧信息来提高素材的分辨率。该软件可将视频的分辨率提高到最高8K&#xff0c;并保持真实的细节和运动一致性。同时&#xff0c;它还能自动修复视频中的…

HCIA-HarmonyOS设备开发认证-序

序 最近涉及到HarmonyOS鸿蒙系统设备开发&#xff0c;在网络上已经有很多相关资料&#xff0c;视频教程&#xff0c;我也移植了公司的一个stm32G474板卡&#xff0c;运行LiteOS-m L0系统。 一面看资料一面移植&#xff0c;遇到不少坑&#xff0c;当看到运行的LOGO时&#xff0…

protobuf学习日记 | 认识protobuf中的类型

目录 前言 一、标量数据类型 二、protobuf中的 “数组” 三、特殊类型 1、枚举类型 &#xff08;1&#xff09;类型讲解 &#xff08;2&#xff09;升级通讯录 2、Any类型 &#xff08;1&#xff09;类型讲解 &#xff08;2&#xff09;升级通讯录 3、oneof类型 …

LeetCode、2300. 咒语和药水的成功对数【中等,排序+二分】

文章目录 前言LeetCode、2300. 咒语和药水的成功对数【中等&#xff0c;排序二分】题目及类型思路及代码 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域…

elementUI+el-upload 上传、下载、删除文件以及文件展示列表自定义为表格展示

Upload 上传组件的使用 官方文档链接使用el-upload组件上传文件 具体参数说明&#xff0c;如何实现上传、下载、删除等功能获取文件列表进行file-list格式匹配代码 文件展示列表自定义为表格展示 使用的具体参数说明文件大小展示问题&#xff08;KB/MB&#xff09;文件下载代码…

【GitHub项目推荐--微软开源的可视化工具】【转载】

说到数据可视化&#xff0c;大家都很熟悉了&#xff0c;设计师、数据分析师、数据科学家等&#xff0c;都需要用各种方式各种途径做着数据可视化的工作.....当然许多程序员在工作中有时也需要用到一些数据可视化工具&#xff0c;如果工具用得好&#xff0c;就可以把原本枯燥凌乱…

svg矢量图标在wpf中的使用

在wpf应用程序开发中&#xff0c;为支持图标的矢量缩放&#xff0c;及在不同分辨率下界面中图标元素的矢量无损缩放&#xff0c;所以常常用到svg图标&#xff0c;那么如果完 美的将svg图标运用到wpf日常的项目开发中呢&#xff0c;这里分享一下我的个人使用经验和详细步骤。 步…

二叉树的基础概念及遍历

二叉树(Binary Tree)的基础 1、树的概念 1、树的概念 树是一种非线性的数据结构&#xff0c;是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合&#xff0c;将它称为树&#xff0c;是因为在形状上像一颗倒着的树&#xff0c;如下图所示就是一颗二叉…