在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)的部分相关源码阅读

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

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

相关文章

如何有效防爬虫?一文讲解反爬虫策略

企业拥抱数字化技术的过程中&#xff0c;网络犯罪分子的“战术”也更难以觉察&#xff0c;并且这些攻击越来越自动化和复杂&#xff0c;也更加难以觉察。在众多攻击手段中&#xff0c;网络爬虫是企业面临的主要安全挑战。恶意爬虫活动可能导致数据滥用、盗窃商业机密等问题&…

MySQL之索引结构

索引概述 索引是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c;这样就可以在这些数据结构上实现…

Ubuntu使用docker-compose安装chatGPT

ubuntu环境搭建专栏&#x1f517;点击跳转 Ubuntu系统环境搭建&#xff08;十五&#xff09;——使用docker-compose安装chatGPT Welcome to the AI era! 使用docker compose安装 在/usr/local文件夹下创建chatgpt mkdir chatgpt创建docker-compose.yaml vim docker-compos…

springcloud OpenFeign服务接口调用

文章目录 代码下载地址OpenFeign简介OpenFeign使用步骤测试 OpenFeign超时控制超时设置&#xff0c;故意设置超时演示出错情况服务提供方8001故意写暂停程序服务消费方80添加超时方法PaymentFeignService服务消费方80添加超时方法OrderFeignController测试YML文件里需要开启Ope…

考研C语言刷编程题篇之分支循环结构基础篇(一)

目录 第一题 第二题 方法一&#xff1a;要循环两次&#xff0c;一次求阶乘&#xff0c;一次求和。 注意&#xff1a;在求和时&#xff0c;如果不将sum每次求和的初始值置为1&#xff0c;那么求和就会重复。 方法二&#xff1a; 第三题 方法一&#xff1a;用数组遍历的思想…

Windows给docker设置阿里源

windows环境搭建专栏&#x1f517;点击跳转 Windows系统的docker设置阿里源 文章目录 Windows系统的docker设置阿里源1.获得镜像加速器2.配置docker 由于我们生活在中国大陆&#xff0c;所以外网的访问总是那么慢又困难&#xff0c;用docker拉取几兆的小镜象还能忍受&#xff…

数据结构排序二叉树(下)

哎,调了几天深度学习模型,今天来更新排序二叉树 文章目录 前言 一、排序二叉树的结构定义 二、在排序二叉树添加数据 三、定义创建排序二叉树函数 四、查找一棵二叉排序树中的结点x的所在层数 五、删除二叉排序树中T关键字x的节点 六、查找二叉排序树中的所有小于key的关…

Mysql:重点且常用的操作和理论知识整理 ^_^

目录 1 基础的命令操作 2 DDL 数据库定义语言 2.1 数据库操作 2.2 数据表操作 2.2.1 创建数据表 2.2.2 修改和删除数据表 2.2.3 添加外键 3 DML 数据库操作语言 3.1 插入语句(INSERT) 3.2 修改语句(UPDATE) 3.3 删除语句 3.3.1 DELETE命令 3.3.2 TRUNCATE命令 4 …

探索C++中std::string的弱点:你可能未曾注意到的缺点

C中std::string的弱点&#xff1a;你可能未曾注意到的缺点 一、背景二、性能方面的局限三、可变性带来的问题四、内存管理和指针操作五、Unicode和多字节字符集的支持六、其他替代方案七、总结 一、背景 C中std::string是一个非常重要的类&#xff0c;用于表示和处理字符串数据…

前端开发必备 HTML的常用标签(二)

目录 一、HTML语言 二、水平线标签 三、字体样式标签 四、注释和特殊符号 一、HTML语言 HTML&#xff08;Hypertext Markup Language&#xff09;是一种标记语言&#xff0c;用于创建网页的结构和内容。它由一系列的标签组成&#xff0c;这些标签定义了网页中各个元素的结…

如何防护网站存在的sql注入攻击漏洞

SQL注入攻击是最危险的Web漏洞之一&#xff0c;危害性极大&#xff0c;造成的后果不堪设想&#xff0c;因此受到了大家的高度重视。那么你知道SQL注入攻击防范方法有哪些吗? SQL注入是一种网站的攻击方法。它将SQL代码添加到网站前端GET POST参数中&#xff0c;并将其传递给my…

MSPM0L1306例程学习-UART部分(2)

MSPM0L1306例程学习系列 1.背景介绍 写在前边的话&#xff1a; 这个系列比较简单&#xff0c;主要是围绕TI官网给出的SDK例程进行讲解和注释。并没有针对模块的具体使用方法进行描述。所有的例程均来自MSPM0 SDK的安装包&#xff0c;具体可到官网下载并安装: https://www.ti…

【 Qt 快速上手】-②- Qt 环境搭建

文章目录 1. Qt 开发工具概述1.1 Qt Creator 介绍1.2 Visual Studio 介绍1.3 Eclipse 介绍 2. Qt SDK 的下载与安装2.1 Qt SDK 的下载2.2 Qt SDK 的安装2.3 验证 Qt SDK 安装是否成功2.4 Qt 环境变量配置 1. Qt 开发工具概述 Qt 开发环境需要安装三个部分&#xff1a; C编译器…

从零开始,自己搭建一个autonomous mobile robot做gazebo仿真(1):mobile robot建模与添加差速控制器

这样一个简单的mobile robot模型 首先写xacro文件&#xff0c;创建 link joint transmission <?xml version"1.0"?> <robot xmlns:xacro"http://www.ros.org/wiki/xacro" name"whill_modelc" ><xacro:property name"PI&q…

JS-元素尺寸与位置

通过js的方式&#xff0c;得到元素在页面中的位置 获取宽高 元素.offsetWidth 元素.offsetHeight 1&#xff09;获取元素的自身宽高、包括元素自身设置的宽高paddingborder 2&#xff09;获取出来的是数值&#xff0c;方便计算 3&#xff09;注意&#xff1a;获取的是可视…

(2023版)斯坦福CS231n学习笔记:DL与CV教程 (14) | 强化学习(Robot Learning)

前言 &#x1f4da; 笔记专栏&#xff1a;斯坦福CS231N&#xff1a;面向视觉识别的卷积神经网络&#xff08;23&#xff09;&#x1f517; 课程链接&#xff1a;https://www.bilibili.com/video/BV1xV411R7i5&#x1f4bb; CS231n: 深度学习计算机视觉&#xff08;2017&#xf…

【Unity】URP报错Object reference not set to an instance of an object

使用URP之后&#xff0c;Unity报错&#xff1a;显示不正常 NullReferenceException: Object reference not set to an instance of an object UnityEngine.Rendering.Universal.UniversalAdditionalCameraData.get_cameraStack () (at Library/PackageCache/com.unity.render-p…

富士康在印度受挫,在郑州建设新能源汽车工厂,还是中国制造可靠

日前消息指富士康宣布在郑州建设新能源汽车工厂&#xff0c;此前它一直推动印度制造&#xff0c;如此做法形成了鲜明对比&#xff0c;这显示出富士康在印度多番努力之后&#xff0c;终于还是认清了现实&#xff0c;印度难以担起富士康的事业。 此前富士康大举向印度转移的是手机…

可视化k8s页面(Kubepi)

Kubepi是一个简单高效的k8s集群图形化管理工具&#xff0c;方便日常管理K8S集群&#xff0c;高效快速的查询日志定位问题的工具 随便在哪个节点部署&#xff0c;我这里在主节点部署 docker pull kubeoperator/kubepi-server docker run --privileged -itd --restartunless-st…

探索curl的高级应用:HTTP请求的大师级技巧

探索curl的高级应用&#xff1a;HTTP请求的大师级技巧 引言高级用法概览1. HTTP请求与响应处理2. 身份验证与安全3. 进阶技巧4. Cookie管理与会话保持5. 脚本自动化 HTTP请求与响应处理1. 自定义请求头2. 发送数据3. 处理响应 身份验证与安全1. 基本认证2. 摘要认证3. HTTPS安全…