Ubuntu虚拟机中使用QEMU搭建ARM64环境

Ubuntu虚拟机中使用QEMU搭建ARM64环境

通过本实验学习如何编译一个 ARM64 版本的内核 image,并且在QEMU 上运行起来。

文章目录

  • Ubuntu虚拟机中使用QEMU搭建ARM64环境
  • 一、安装aarch64交叉编译工具
  • 二、安装QEMU
  • 三、制作根文件系统
    • 1、根文件系统简介
    • 2、BusyBox构建根文件系统
      • 1)下载BusyBox源码
      • 2)制定编译工具链
      • 3)配置编译BusyBox
      • 4)创建需要的目录
        • (a) etc目录更新
        • (b) dev目录下的必要文件
        • (c) lib目录下的必要文件
  • 四、编译内核源码
    • 1、下载源码
    • 2、指定编译工具
    • 3、将根文件系统放到源码根目录
    • 4、配置生成.config
    • 5、编译
  • 五、启动QEMU
    • 1、创建共享文件目录
    • 2、运行QEMU模拟器
    • 4、编译一个简单的内核模块并在QEMU上运行
  • 六、实验过程中遇到的一些问题及解决办法
    • 1、本地下载压缩包如何上传Ubuntu中?
    • 2、make编译BusyBox出现的报错
    • 3、根文件系统放到源码根目录时报错
    • 4、关于内核模块在QEMU中运行出现的问题
  • 参考链接

一、安装aarch64交叉编译工具

搭建QEMU的模拟环境首先需要下载安装对应架构的交叉编译工具链(这里是arm64架构):

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

image-20250307215602049

image-20250307215652241安装完成之后查看版本说明安装完成:

$ aarch64-linux-gnu-gcc -v

image-20250307215742797

二、安装QEMU

在终端中输入 sudo apt install qemu-system-arm安装

image-20250307215809308

安装完成后,输入 qemu-system-aarch64 --version 来查看 qemu版本

image-20250307215908572

三、制作根文件系统

1、根文件系统简介

Linux的根文件系统一般也叫做 rootfs,Linux的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

根文件系统的“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根” ,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中。

在构建根文件系统之前,先来看一下根文件系统里面都有些什么内容,根文件系统的目录名字为‘/’ ,就是一个斜杠。下面以Ubuntu为例,一些常用的子目录介绍如下表示:

image-20250307201933510

2、BusyBox构建根文件系统

1)下载BusyBox源码

BusyBox是一个集成了大量的Linux命令(如ls、mv、ifconfig 等命令)和工具的软件。借助BusyBox,进行配置和编译,就可以方便的构建一个嵌入Linux平台所需要的根文件系统。

可在BusyBox官网 https://busybox.net/ 下载源码。

将压缩包拖拽到Ubuntu虚拟机中的桌面或文件夹中,并解压

image-20250307222203393

tar -vxjf busybox-1.37.0.tar.bz2

解压后的文件如下:

image-20250307222336805

2)制定编译工具链

cd busybox-1.36.1
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
//检测是否配置成功
echo $ ARCH
echo $CROSS_COMPILE

image-20250307222735565

3)配置编译BusyBox

busybox中文字符支持:若直接编译busybox,使用串口工具时是不支持中文显示的,会显示为“?” ,可修改源码,取消 busybox对中文显示的限制

打开文件/libbb/printable_string.c,将函数printable_string()中的部分程序注释掉,修改后的函数内容如下:

/********** printable_string.c代码段 **********/
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
	char *dst;
	const char *s;

	s = str;
	while (1) {
		......
		if (c < ' ')
			break;
		/* 注释掉下面这个两行代码,禁止字符大于0X7F以后 break */
		/* if (c >= 0x7f)   
			 break; */
		s++;
	}

#if ENABLE_UNICODE_SUPPORT
	dst = unicode_conv_to_printable(stats, str);
#else
{
	char *d = dst = xstrdup(str);
	while (1) {
	unsigned char c = *d;
	if (c == '\0')
		break;
	/* 修改下面代码,禁止字符大于0X7F以后输出‘?’ */
	/* if (c < ' ' || c >= 0x7f) */
	if( c < ' ')
		*d = '?';
	d++;
}
......
#endif
	return auto_string(dst);
}

接着打开文件/libbb/unicode.c,修改如下内容:

/********** unicode.c代码段 **********/
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
{
	char *dst;
	unsigned dst_len;
	unsigned uni_count;
	unsigned uni_width;

	if (unicode_status != UNICODE_ON) {
		char *d;
		if (flags & UNI_FLAG_PAD) {
			d = dst = xmalloc(width + 1);
			......
				/* 修改下面一行代码 */
				/* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
				*d++ = (c >= ' ') ? c : '?';
				src++;
			}
			*d = '\0';
		} else {
			d = dst = xstrndup(src, width);
			while (*d) {
				unsigned char c = *d;
				/* 修改下面一行代码 */
				/* if (c < ' ' || c >= 0x7f) */
				if(c < ' ')
					*d = '?';
				d++;
			}
		}
		......
		return dst;
	}
......
return dst;
}

  • 配置busybox:有以下几种配置选项,一般使用默认配置即可

– defconfig:缺省配置,也就是默认配置选项
– allyesconfi:全选配置,即选中 busybox 的所有功能
– allnoconfig:最小配置

make defconfig  	#使用默认配置
make menuconfig		#打开图形化配置界面

在这里插入图片描述

设置Settings -> Build static binary (no shared libs)

image-20250307232958795

设置Settings -> Support Unicode,使能busybox的unicode编码以支持中文

image-20250307231300248

  • 编译busybox:配置好busybox以后就可以编译了,输入如下命令
make
make install CONFIG_PREFIX=/home/xlq/linux/rootfs
#CONFIG_PREFIX指定编译结果的存放目录

image-20250307233117424

编译完成以后,busybox的所有工具和文件就会被安装到rootfs目录中,如下图;rootfs目录下有bin、sbin和usr三个目录,以及linuxrc文件。

image-20250307233209439

4)创建需要的目录

cd  ~/linux/rootfs
mkdir dev etc lib sys proc tmp var home root mnt
(a) etc目录更新
  • 创建 profile 文件,添加下面内容
#!/bin/sh
export HOSTNAME=user
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

用nano profile打开新建的文件后,Ctrl+O打开写入后直接回车,再点击Ctrl+x退出终端

回到文件夹下会出现新建的一个profile空白文件,将上述内容复制粘贴到该空白文件下即可。

后续创建操作皆可如此

  • 创建 inittab 文件,添加下面内容
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
  • 创建 fstab 文件,添加下面内容,指定挂载的文件系统
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0
  • 创建init.d目录
mkdir  init.d
  • 在init.d下创建 rcS文件,添加下面内容
cd init.d

mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
  • 添加权限
chmod 777 rcS
  • 利用tree命令查看etc下目录结构如下所示

image-20250307235749530

image-20250307235813055

(b) dev目录下的必要文件
cd dev
sudo mknod console c 5 1

image-20250308000013336

输出下面命令检查是否创建了console的设备文件:

ls -l /dev/console

image-20250308000143214

© lib目录下的必要文件

为了支持动态编译的应用程序的执行,根文件系统需要支持动态库,所以我们添加arm64相关的动态库文件到lib下

cd lib
cp /usr/aarch64-linux-gnu/lib/*.so* -a .

检查当前目录是否包含了.so文件:

image-20250308000449715

四、编译内核源码

1、下载源码

下载Linux的内核源码点击下载

很多教程是直接使用命令行下载,但我最初直接采用命令行方式下载Ubuntu内存爆红并且下载失败了,后面改用直接从网站下载比较稳妥。

在这里插入图片描述

同BusyBox放到Ubuntu合适位置进行解压

tar xvf linux-6.13.5.tar.xz

2、指定编译工具

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

3、将根文件系统放到源码根目录

cd linux-6.13.5
sudo cp  ~/linux/rootfs rootfs_arm64 -a

image-20250308002220347

4、配置生成.config

make defconfig
make menuconfig

添加hotplug支持

    Device Drivers                                                                                                 
        -> Generic Driver Options                                                                                             
            -> Support for uevent helper                                                                                                                
                (/sbin/hotplug) path to uevent helper

添加initramfs支持

General setup --->
     
     [*]Initial RAM filesystem and RAM disk(initramfs/initrd) support
        (_install_arm64) Initramfs souce file(s)

Virtual address space配置

    Kernel Features  ---> 
      	Page size(4KB)  --->
        Virtual address space size(48-bit)--->

5、编译

make all -j8

五、启动QEMU

1、创建共享文件目录

​ 在内核源码目录下创建目录

mkdir kmodules

2、运行QEMU模拟器

​ 在内核源码目录下执行下面命令

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt  -m 1024 -smp 4 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8"  -nographic  --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none  -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount

— machine virt:使用virt机器类型。
— cpu cortex-a57:使用Cortex-A57 CPU模型。
— -m 1024:设置虚拟机内存大小为1024MB。
— -smp 4:设置虚拟机使用4个CPU核心。
— -kernel arch/arm64/boot/Image:指定Linux内核镜像的路径。
— --append “rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8”:指定内核启动参数,其中rdinit指定init程序的路径,root指定根文件系统的设备,rw表示以读写模式挂载根文件系统,console指定控制台设备,loglevel指定内核日志级别。
— -nographic:禁用图形界面,使用纯文本控制台。
— --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none:创建一个本地文件系统设备,其中id指定设备ID,path指定设备挂载的本地路径,security_model指定安全模型。
— -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount:将本地文件系统设备挂载到虚拟机中,其中fsdev指定设备ID,mount_tag指定设备挂载的标签。

启动成功后会打印下面内容,点击Enter进入控制台

image-20250308125559937

image-20250308125646865

退出QEMU模拟器

poweroff

4、编译一个简单的内核模块并在QEMU上运行

  • 在根目录下创建一个文件夹module_test,并编写一个简单的hello.c代码

    // 包含内核模块编程所需的头文件
    #include <linux/init.h>  // 包含模块初始化和退出函数的宏
    #include <linux/module.h>  // 包含模块相关的宏和函数
    #include <linux/kernel.h>  // 包含内核打印函数 printk 的头文件
    
    // 模块初始化函数
    // 当模块被加载到内核时,此函数会被调用
    static int __init test_init(void)
    {
        // 在内核日志中打印 "hello world!"
        printk("hello world!\n");
        
        // 返回 0 表示初始化成功
        return 0;
    }    
    
    // 模块退出函数
    // 当模块从内核中卸载时,此函数会被调用
    static void __exit test_exit(void)
    {
        // 在内核日志中打印 "hello exit!"
        printk("hello exit!\n");
    }
    
    // 注册模块的初始化函数
    // 当模块被加载时,test_init 函数会被调用
    module_init(test_init);
    
    // 注册模块的退出函数
    // 当模块被卸载时,test_exit 函数会被调用
    module_exit(test_exit);
    
    // 声明模块的许可证
    // GPL 是 GNU 通用公共许可证,表示这是一个开源模块
    MODULE_LICENSE("GPL");
    
  • 再编写Makefile文件

# 设置目标架构为 ARM64
export ARCH=arm64

# 设置交叉编译工具链前缀为 aarch64-linux-gnu-
export CROSS_COMPILE=aarch64-linux-gnu-

# 定义内核源码目录
KERNEL_DIR ?= /home/xlq/linux-6.13.5

# 定义要编译的内核模块目标文件
# obj-m 表示将 hello.c 编译为内核模块 hello.ko
obj-m := hello.o

# 默认目标:编译内核模块
modules:
	# 调用内核源码目录的 Makefile,编译当前目录下的模块
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules

# 清理目标:删除编译生成的文件
clean:
	# 调用内核源码目录的 Makefile,清理当前目录下的生成文件
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean

# 安装目标:将编译好的内核模块复制到指定目录
install:
	# 将当前目录下所有的 .ko 文件复制到内核源码目录的 kmodules 子目录中
	cp *.ko $(KERNEL_DIR)/kmodules
  • 编译module,拷贝到共享目录
make modules
make install 
  • 启动QEMU

    启动命令:

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt  -m 1024 -smp 4 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8"  -nographic  --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none  -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount
回车后进入控制台,以防万一可以检查一下挂载点是否成功
ls /mnt

image-20250308163114922

​ 可以成功看到.ko文件。

  • 在QEUM执行module的插入与卸载,可以看到成功执行并打印log
[xlq@xlq-virtual-machine ]$ cd mnt
[xlq@xlq-virtual-machine mnt]$ ls /mnt
hello.ko  kmod_mount
[xlq@xlq-virtual-machine mnt]$ insmod hello.ko
[ 3679.306752] hello: loading out-of-tree module taints kerne
[ 3679.322242] hello world!
[xlq@xlq-virtual-machine mnt]$ rmmod hello.ko
3732.515746] hello exit!

image-20250308164115326

六、实验过程中遇到的一些问题及解决办法

1、本地下载压缩包如何上传Ubuntu中?

1、本地电脑上下载的busybox和Linux内核源码在移动到Ubuntu虚拟机上时拖到到主文件夹即可

在这里插入图片描述

2、make编译BusyBox出现的报错

2、出现问题:在使用命令make编译BusyBox时,出现报错信息如下

libbb/hash_md5_sha.c:1316:35: error: 'sha1_process_block64_shaNI' undeclared (first use in this function);did you mean 'sha1 process block64'?

解决办法nano libbb/hash_md5_sha.c 进入函数中,查找sha1_process_block64_shaNI,将所有的sha1_process_block64_shaNI修改成sha1_process_block64。保存后退出,再使用make编译即可通过。

3、根文件系统放到源码根目录时报错

3、出现问题:在将根文件系统放到源码根目录的时候,参考命令为:cp ~linux/rootfts rootfs_arm64 -a,但出现报错“‘无法创建特殊文件’”,原因是因为普通用户没有足够的权限来创建,设备文件和特殊文件通常需要root权限来创建或修改。

解决办法:使用sudo来提升权限

sudo cp ~/linux/rootfs rootfs_arm64 -a

4、关于内核模块在QEMU中运行出现的问题

4、当我在编译一个简单内核模块hello.c在QEMU运行测试的过程中,出现了很大问题:

出现问题:通过编译生成的内核模块hello.ko文件在成功拷贝到共享目录kmodules下时,启动QEMU,在mnt文件夹下无法看到hello.ko文件。

问题排查:合理利用大模型提出问题,主要从下面几个点来排查问题

  • 共享目录未正确挂载
//检查是否执行了挂载命令
mount -t 9p -o trans=virtio kmod_mount /mnt
//如果没有挂载,需要先挂载
mkdir -p /mnt/kmod_mount
mount -t 9p -o trans=virtio kmod_mount /mnt/kmod_mount
cd /mnt/kmod_mount
ls
//检测挂载是否成功
mount | grep kmod_mount    //如果没有输出,说明挂载失败
  • 挂载点错误(共享目录挂载到了其他目录,而不是/mnt)
//检查挂载点
mount | grep kmod_mount
//输出示例
kmod_mount on /mnt type 9p (trans=virtio)
  • 共享目录路径出错

    本人觉得这个问题可能性不大,在上面执行make install命令后,可以回到内核源码目录下的kmodules文件下查看是否存在.ko文件

    如果路径错误,修改QEMU启动命令中的path参数。
    
  • 如果上述问题都没有可以再文件系统权限问题和内核未启用9p文件系统支持问题

解决办法:本人最初遇到的问题是挂载失败,重新挂载后还是无法看到挂载点下的.ko文件,进入kmodules文件下,正确的挂载应该出现一个kmod_mount的挂载标签,和一个.ko文件,如下(不要把.ko文件放到kmod_mount文件夹中

image-20250308163512591

回到QEMU中检查目录内容:

image-20250308163749586

参考链接

用BusyBox构建根文件系统

QEMU搭建ARM64+Linux系统

qemu搭建ARM Linux环境

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

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

相关文章

java 重点知识 — JVM存储模块与类加载器

1 jvm主要模块 方法区 存储了由类加载器从.class文件中解析的类的元数据&#xff08;类型信息、域信息、方法信息&#xff09;及运行时常量池&#xff08;引用符号及字面量&#xff09;。 所有线程共享&#xff1b;内存不要求连续&#xff0c;可扩展&#xff0c;可能发生垃圾回…

Windows 如何开启和使用FTP服务

在Windows 系统开启FTP的服务方式有很多种&#xff0c;最快速的就是使用Windows自身的FTP服务了。 Windows 搭建FTP服务的方式 在Windows 中搭建FTP的方式有很多种&#xff0c;有商用收费的&#xff0c;也有开源免费的&#xff0c;除此之外&#xff0c; Windows本身也内置了F…

ASP.NET Core 6 MVC 文件上传

概述 应用程序中的文件上传是一项功能&#xff0c;用户可以使用该功能将用户本地系统或网络上的文件上传到 Web 应用程序。Web 应用程序将处理该文件&#xff0c;然后根据需要对文件进行一些验证&#xff0c;最后根据要求将该文件存储在系统中配置的用于保存文件的存储中&#…

计算机毕业设计SpringBoot+Vue.js制造装备物联及生产管理ERP系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

yoloV5训练visDrone2019-Det无人机视觉下目标检测

一、visDrone2019数据集详解 visDrone2019数据集是无人机视角下最具挑战性的目标检测基准数据集之一&#xff0c;由天津大学机器学习与数据挖掘实验室联合其他研究机构共同构建。该数据集采集自中国14个不同城市&#xff0c;覆盖复杂城市场景、交通枢纽、密集人群等多种环境。…

Unity开发——CanvasGroup组件介绍和应用

CanvasGroup是Unity中用于控制UI的透明度、交互性和渲染顺序的组件。 一、常用属性的解释 1、alpha&#xff1a;控制UI的透明度 类型&#xff1a;float&#xff0c;0.0 ~1.0&#xff0c; 其中 0.0 完全透明&#xff0c;1.0 完全不透明。 通过调整alpha值可以实现UI的淡入淡…

C语言学习笔记-进阶(7)字符串函数3

1. strstr的使用和模拟实现 char * strstr ( const char * str1, const char * str2); Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1. &#xff08;函数返回字符串str2在字符串str1中第⼀次出现的位置&#x…

如何利用数字校园平台提升职业竞争力

现在我们来探讨如何借助数字校园平台来增强自身的职业竞争力。当今之时代&#xff0c;技术与数据堪称热门领域&#xff0c;略懂编程语言及数据分析&#xff0c;于求职之际&#xff0c;实能增添诸多优势&#xff01; 首先&#xff0c;咱们得说说编程语言。现在很多学校都有提供在…

使用 Arduino 的 WiFi 控制机器人

使用 Arduino 的 WiFi 控制机器人 这次我们将使用 Arduino 和 Blynk 应用程序制作一个 Wi-Fi 控制的机器人。这款基于 Arduino 的机器人可以使用任何支持 Wi-Fi 的 Android 智能手机进行无线控制。 为了演示 Wi-Fi 控制机器人,我们使用了一个名为“Blynk”的 Android 移动应…

动态ip和静态ip适用于哪个场景?有何区别

在数字化浪潮席卷全球的今天&#xff0c;IP地址作为网络世界的“门牌号”&#xff0c;其重要性不言而喻。然而&#xff0c;面对动态IP与静态IP这两种截然不同的IP分配方式&#xff0c;许多用户往往感到困惑&#xff1a;它们究竟有何区别&#xff1f;又分别适用于哪些场景呢&…

求最大公约数【C/C++】

大家好啊&#xff0c;欢迎来到本博客( •̀ ω •́ )✧&#xff0c;我将带领大家详细的了解最大公约数的思想与解法。 一、什么是公约数 公约数&#xff0c;也称为公因数&#xff0c;是指两个或多个整数共有的因数。具体来说&#xff0c;如果一个整数能被两个或多个整数整除&…

conda 配置新环境时package will be install 和 package will be download 的区别

install 和 download 的区别 package will be downloaded下的包&#xff1a;这一类显示的是需要从 conda 仓库或其他指定的源下载的软件包。这些软件包通常是 .tar.bz2、.tar.xz 或 .conda 格式的压缩包。这些包会被下载到本地缓存目录&#xff08;通常是 ~/.conda 或 C:\Users…

【2025小黑课堂】计算机二级WPS精选系列20G内容(可下载:真题+预测卷+软件+选择题)

2025年3月全国计算机等级考试即将于3月29日至31日举行。为了帮助广大考生高效备考&#xff0c;小编特意收集并整理了最新版&#xff08;备考2025年3月&#xff09;的小黑课堂计算机二级WPS 电脑题库软件&#xff0c;助力考生在考试中游刃有余&#xff0c;轻松通关&#xff01; …

C++编写Redis客户端

目录 安装redis-plus-plus库 ​编辑 编译Credis客户端 redis的通用命令使用 get/set exists del keys expire /ttl type string类型核心操作 set和get set带有超时时间 set带有NX string带有XX mset mget getrange和setrange incr和decr list类型核心操作…

①EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器

EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器https://item.taobao.com/item.htm?ftt&id798036415719 型号 1路总线EC网关 MS-A2-1011 2路总线EC网关 MS-A2-1021 4路总线EC网关 MS-A2-1041 EtherCAT 串口网关 EtherCAT 转 RS485 技术规格 …

C++ Primer 交换操作

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

TDengine 服务无法启动常见原因

taosd 是 TDengine 的核心服务进程&#xff0c;如果无法启动将导致整个数据库无法使用&#xff0c;了解常导致无法启动的原因&#xff0c;可以帮你快速解决问题。 1. 如何查找日志 无法启动的原因记录在日志中&#xff0c;日志文件默认在 /var/log/taos 的 taosdlog.0 或者 t…

一周学会Flask3 Python Web开发-SQLAlchemy连接Mysql数据库

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili app.py下&#xff0c;我们先配置数据库连接&#xff0c;然后写一个简单sql测试。 连接配置&#xff0c;包括用户名&#xff…

【NLP 32、文本匹配任务 —— 深度学习】

大劫大难以后&#xff0c;人不该失去锐气&#xff0c;不该失去热度&#xff0c;你镇定了却依旧燃烧&#xff0c;你平静了却依旧浩荡&#xff0c;致那个从绝望中走出来的自己&#xff0c;共勉 —— 25.1.31 使用深度学习在文本匹配任务上主要有两种方式&#xff1a;① 表示型 ②…

打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手

DeepSeek AI 聊天助手集成指南 先看完整效果&#xff1a; PixPin_2025-02-19_09-15-59 效果图&#xff1a; 目录 项目概述功能特点环境准备项目结构组件详解 ChatContainerChatInputMessageBubbleTypeWriter 核心代码示例使用指南常见问题 项目概述 基于 Vue 3 TypeScrip…