Linux设备树的驱动开发
概述
本文介绍了platform框架下的设备驱动开发流程和方法,主要包括设备树、驱动程序和应用程序的开发。以随机数驱动为例,实现了应用程序调用库函数,通过系统调用陷入内核,最后执行硬件驱动,获取真随机数的过程。
添加设备树节点
在soc节点下添加名为trng子节点,内容如下:
trng: trng@0x53030000 {compatible = "acme,trng";reg = <0x00 0x53030000 0x00 0x1000>;interrupts = <0x34 IRQ_TYPE_LEVEL_HIGH>;interrupt-parent = <&plic>;
};
编译设备树dts,生成相应的dtb文件:
make dtbs
使用新的dtb启动Linux内核。Linux启动成功之后查看是否有trng这个节点:
ls /proc/device-tree/soc
# trng@0x53030000
进入trng
目录,查看属性相关的文件:
/proc/device-tree/soc/trng@0x53030000# ls
compatible interrupts phandle
interrupt-parent name reg
编写设备驱动
Makefile
新建trng/driver
目录,并创建Makefile
,内如如下:
# 内核架构
ARCH := riscv
# 交叉工具链
CROSS_COMPILE := /path/to/riscv32-linux-
# 内核目录
KERNELDIR := /path/to/linux/linux-6.1
# 当前目录
PWD := $(shell pwd)
# 目标文件
obj-m := trng.o# 目标
build: kernel_modules# 编译模块
kernel_modules:$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules# 清理
clean:$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) clean
驱动
在trng/driver
下新建trng.c
文件,内容如下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>/* TRNG寄存器 */
#define TRNG_CTRL (0x0)#define TRNG_CTRL_CMD_MASK (0x07)#define TRNG_CTRL_CMD_RNG (0x01)#define TRNG_CTRL_CMD_SEED (0x02)
#define TRNG_STAT (0x4)#define TRNG_STAT_SEEDED BIT(9)
#define TRNG_MODE (0x8)#define TRNG_MODE_R256 BIT(3)
#define TRNG_ISTAT (0x14)#define TRNG_ISTAT_RAND_RDY BIT(0)#define TRNG_ISTAT_SEED_DONE BIT(1)
#define TRNG_RAND0 (0x20)#define TRNG_TIMEOUT (50000)#define DRIVER_NAME "trng"struct trng_dev {dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *dev; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */int irq; /* 中断号 */void __iomem *base; /* 基地址 */
};static int trng_init(void *base)
{int ret;unsigned int value;/* 模式 */value = readl(base + TRNG_MODE);value |= TRNG_MODE_R256;writel(value, base + TRNG_MODE); /* 播种 */value = readl(base + TRNG_CTRL);value &= ~TRNG_CTRL_CMD_MASK;value |= TRNG_CTRL_CMD_SEED;writel(value, base + TRNG_CTRL);/* 等待播种完成 */ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,value, (value & TRNG_ISTAT_SEED_DONE),10, TRNG_TIMEOUT);if (ret == 0) {value |= TRNG_ISTAT_SEED_DONE;writel(value, base + TRNG_ISTAT);}return ret;
}static int trng_generate_random(void *base, unsigned char *buf)
{int ret;unsigned int value;/* 启动生成随机数 */value = readl(base + TRNG_CTRL);value &= ~TRNG_CTRL_CMD_MASK;value |= TRNG_CTRL_CMD_RNG;writel(value, base + TRNG_CTRL);/* 等待随机数准备好 */ret = readl_relaxed_poll_timeout_atomic(base + TRNG_ISTAT,value, (value & TRNG_ISTAT_RAND_RDY),10, TRNG_TIMEOUT); if (ret) {return ret;}/* 清除准备好标志 */value = readl(base + TRNG_ISTAT);value &= ~TRNG_ISTAT_RAND_RDY;writel(value, base + TRNG_ISTAT);/* 读取随机数 */for (int i = 0; i < 8; i++) {*(unsigned int*)buf = readl(base + TRNG_RAND0 + i*4);buf += 4;}return 0;
}static irqreturn_t trng_irq_handler(int irq, void *dev_id)
{struct trng_dev *trng;trng = (struct trng_dev*)dev_id;dev_dbg(trng->dev, "TRNG interrupt received\n");return IRQ_HANDLED;
}static int trng_open(struct inode *inode, struct file *filp)
{int ret;struct trng_dev *trng;trng = container_of(inode->i_cdev, struct trng_dev, cdev);filp->private_data = trng;dev_dbg(trng->dev, "Open trng\n");ret = trng_init(trng->base);if (ret) {dev_err(trng->dev, "Failed to init trng, ret=%d\n", ret);return ret;}return 0;
}static int trng_release(struct inode *inode, struct file *filp)
{struct trng_dev *trng;trng = filp->private_data;dev_dbg(trng->dev, "Release trng\n");return 0;
}static ssize_t trng_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
{ int ret;unsigned char random[32];size_t copyed_len, len_to_copy;struct trng_dev *trng;trng = filp->private_data;dev_info(trng->dev, "Read trng\n");copyed_len = 0;while (len) {ret = trng_generate_random(trng->base, random);if (ret) {dev_err(trng->dev, "Failed to generate random, ret=%d\n", ret);return ret;}// print_hex_dump(KERN_INFO, "random: ", DUMP_PREFIX_NONE, 16, 1, random, sizeof(random), false);len_to_copy = (len < sizeof(random)) ? len : sizeof(random);ret = copy_to_user(buffer, random, len_to_copy);if (ret) {dev_err(trng->dev, "Failed to copy to user\n");return -EFAULT;}copyed_len += len_to_copy;buffer += len_to_copy;len -= len_to_copy;}return copyed_len;
} static ssize_t trng_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
{struct trng_dev *trng;trng = filp->private_data;dev_dbg(trng->dev, "Write trng\n");return len;
}static long trng_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct trng_dev *trng;trng = filp->private_data;dev_dbg(trng->dev, "Ioctl trng\n");switch (cmd) {default:dev_err(trng->dev, "Unknown ioctl = 0x%x\n", cmd);break;}return -ENOTTY;
}static int trng_mmap(struct file *filp, struct vm_area_struct *vma)
{struct trng_dev *trng;trng = filp->private_data;dev_dbg(trng->dev, "Mmap trng\n");return 0;
}static const struct file_operations trng_fops = {.owner = THIS_MODULE,.open = trng_open,.release = trng_release,.read = trng_read,.write = trng_write,.unlocked_ioctl = trng_ioctl,.mmap = trng_mmap,
};static int trng_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct trng_dev *trng;int ret;/* 分配内存 */trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL);if (!trng) {dev_err(dev, "Failed to allocate memory\n");return -ENOMEM;}/* 将设备的资源映射到内存空间 */trng->base = devm_platform_ioremap_resource(pdev, 0);if (IS_ERR(trng->base)) {dev_err(dev, "Failed to map device registers\n");return PTR_ERR(trng->base);}/* 获取设备的中断号 */trng->irq = platform_get_irq(pdev, 0);if (trng->irq <= 0) {dev_err(dev, "Failed to get irq %d\n", trng->irq);return trng->irq; }/* 请求中断 */ret = devm_request_irq(dev, trng->irq, trng_irq_handler, 0,DRIVER_NAME, trng);if (ret) {dev_err(dev, "Failed to request IRQ\n");return ret;}/* 申请设备号 */ret = alloc_chrdev_region(&trng->devid, 0, 1, DRIVER_NAME); if (ret < 0) { dev_err(dev, "Failed to allocate device number\n"); return ret; }trng->major = MAJOR(trng->devid); trng->minor = MINOR(trng->devid); /* 初始化cdev */trng->cdev.owner = THIS_MODULE;cdev_init(&trng->cdev, &trng_fops);/* 添加一个cdev */ret = cdev_add(&trng->cdev, trng->devid, 1);if (ret < 0) {dev_err(dev, "Failed to add cdev\n");unregister_chrdev_region(trng->devid, 1);return ret;} /* 创建类 */trng->class = class_create(THIS_MODULE, DRIVER_NAME);if (IS_ERR(trng->class)) {cdev_del(&trng->cdev);unregister_chrdev_region(trng->devid, 1);dev_err(dev, "Failed to create class\n");return PTR_ERR(trng->class);}/* 创建设备 */trng->dev = device_create(trng->class, NULL, trng->devid, NULL, DRIVER_NAME);if (IS_ERR(trng->dev)) {cdev_del(&trng->cdev);unregister_chrdev_region(trng->devid, 1);class_destroy(trng->class);dev_err(dev, "Failed to create device\n");return PTR_ERR(trng->dev);}/* 保存设备私有结构体 */platform_set_drvdata(pdev, trng);dev_info(dev, "TRNG platform driver probed\n");return 0;
}static int trng_remove(struct platform_device *pdev)
{struct trng_dev *trng;/* 获取设备私有结构体 */trng = platform_get_drvdata(pdev);/* 删除cdev */cdev_del(&trng->cdev);/* 释放设备号 */unregister_chrdev_region(trng->devid, 1);/* 删除设备 */device_destroy(trng->class, trng->devid);/* 删除类 */class_destroy(trng->class);/* 释放设备内存 */// devm_kfree(&pdev->dev, trng); // devm_kzalloc为设备分配的内存,在设备移除时会自动释放dev_info(&pdev->dev, "TRNG platform driver removed\n");return 0;
}static const struct of_device_id trng_of_match[] = {{ .compatible = "acme,trng" },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, trng_of_match);static struct platform_driver trng_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = trng_of_match,},.probe = trng_probe,.remove = trng_remove,
};module_platform_driver(trng_driver);MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("Trng driver");
MODULE_LICENSE("GPL");
使用platform平台驱动设备模型编写trng
的驱动程序。
-
当设备树中的节点与驱动匹配成功会执行
trng_probe
函数,完成驱动的加载。 -
当应用需要获取随机数时,读取这个
trng
设备,陷入内核调用函数trng_read
,进而调用函数trng_generate_random
完成从硬件获取随机数。 -
如果需要释放设备,会调用
trng_remove
函数卸载设备驱动。
执行make
编译驱动程序,编译成功生成trng.ko
。
Linux启动成功之后,可以挂载nfs
,将trng.ko
拷贝到trng
目录:
mkdir trng
mount -t nfs -o nolock xx.xx.xx.xx:/nfs/trng /root/trng
执行如下命令,加载设备驱动:
insmod trng.ko
# [ 177.821055] trng 53030000.trng: TRNG platform driver probed
如果设备驱动加载成功,可以在/dev
下找到设备:
ls /dev/trng
另外可以查看trng
的设备号:
cat /proc/devices
# 249 trng
如果需要卸载设备驱动,执行:
rmmod trng.ko
# [ 2947.495906] trng 53030000.trng: TRNG platform driver removed
应用App
Makefile
新建trng/app
目录,并创建Makefile
,内如如下:
# 交叉工具链
CROSS_COMPILE ?= /opt/andestech/nds32le-linux-glibc-v5d/bin/riscv32-linux-# 指定C编译器
CC := $(CROSS_COMPILE)gcc# 目标文件名
TARGET := trng# 源文件名
SRC := trng.c# 默认目标
all: $(TARGET)# 编译并链接
$(TARGET): $(SRC)$(CC) $(SRC) -o $(TARGET)# 清理
clean:rm -f $(TARGET)
应用
在trng/app
下新建trng.c
文件,内容如下:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h> #define TRNG_DEVICE "/dev/trng"static void hexdump(const char *name, const unsigned char *buffer, unsigned int len)
{printf("****************%s****************\n", name);for (unsigned int i = 0; i < len; i++) {printf("%02x ", buffer[i]);if ((i + 1) % 16 == 0) {printf("\n");}}if (len % 16 ) {printf("\n");}
}int main(int argc, char *argv[])
{uint8_t *buf = NULL;size_t num;int ret, fd;if (argc < 2) {printf("Usage: trng <num>\n");return -1;}num = atoi(argv[1]);buf = malloc(num);if (buf == NULL) {printf("Failed to malloc\n");return -1;}/* 打开设备 */fd = open(TRNG_DEVICE, O_RDONLY);if (fd < 0) {printf("Failed to open trng device\n");goto exit;}/* 读取随机数 */ret = read(fd, buf, num);if (ret < 0) {printf("Failed to read random, ret=%d\n", ret);goto exit;}hexdump("random", buf, num);
exit:close(fd);free(buf);return ret;
}
执行make
编译应用程序,编译成功生成trng
。
同理,将trng
应用拷贝到trng
目录,并执行:
./trng 16
# ****************random****************
# 6c 95 ea 3c a0 1f e8 c2 03 db 66 f6 19 4b 07 e3
# c0 96 a3 93 20 a9 68 c5 9f 1f a1 55 c0 9c 24 c9
# 5f 06 47 45 be 2c 21 b5 11 23 23 e6 36 94 3f d6
# 9a 30 68 91 da c4 6d ff af 46 26 c9 ab f8 79 7c
如果随机数获取成功,说明驱动和应用程序运行正常。
编译进内核
在开发前期阶段,一般将驱动编译成模块,方便调试。当驱动开发完成后,可以将其编译进内核。
驱动
在linux-6.1/drivers
下新建trng
目录,并创建Makefile
和Kconfig
文件,内容分别如下:
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the TRNG device drivers.
#obj-$(CONFIG_TRNG) := trng.o
# SPDX-License-Identifier: GPL-2.0-only
#
# TRNG device configuration
#config TRNGtristate "TRNG support"helpThis driver provides support for TRNG in SoCs.To compile this driver as a module, choose M here: the modulewill be called acme-trng.If unsure, say Y.
在linux/drivers/Makefile
中添加:
obj-$(CONFIG_TRNG) += trng/
在linux/drivers/Kconfig
中添加:
source "drivers/trng/Kconfig"
将trng.c
驱动文件拷贝到trng
目录,最终目录文件如下:
$ tree linux-6.1/drivers/trng/
├── Kconfig
├── Makefile
└── trng.c
内核配置
配置内核,输入命令:
make menuconfig
选择Device Drivers->TRNG support
,选择将trng
编译进内核,这里可以有三种选择:
- *:将该功能编译进内核
- 空:不编译该功能
- M:将该功能编译成内核中的模块
运行
编译Linux并启动,在启动日志中,如果打印如下,说明TRNG驱动运行正常:
[ 4.974450] trng 53030000.trng: TRNG platform driver probed
可以执行命令,获取随机数:
cat /dev/trng | hexdump -n 32
0000000 df01 f5bc de33 2509 8d16 7b5f 8868 8bea
0000010 40f3 00f2 97a4 324d 03c2 10c8 b943 3d6d
0000020
问题解决
-
加载KO报异常
trng: loading out-of-tree module taints kernel.
原因在于没有将此驱动模块加入到
Kconfig
导致。 -
使用函数
devm_kzalloc
为设备分配的内存,在设备移除时会自动释放,可以不进行显示释放devm_kfree
-
宏
container_of
用于从结构体的某个成员的地址反推出整个结构体的地址,尤其注意第一个参数必须为成员的地址,如果结构体成员为指针变量,需要取该指针变量的地址。
相关文章:

Linux设备树的驱动开发
概述 本文介绍了platform框架下的设备驱动开发流程和方法,主要包括设备树、驱动程序和应用程序的开发。以随机数驱动为例,实现了应用程序调用库函数,通过系统调用陷入内核,最后执行硬件驱动,获取真随机数的过程。 添…...

连锁?下沉?AI?2025年餐饮新活力!
如果要用几个词来形容 2024 年的餐饮业,这些词大概率会是「卷、难、惨」,用著名商业顾问刘润的话来说就是「卷到极致」。虽然餐饮人在社交平台上叫苦连天,但当我们查看餐饮大盘数据时发现,大盘在涨,与个体餐饮人的实感…...

Javascript中如何实现函数缓存?函数缓存有哪些应用场景?
今天要聊的一个很经典的问题——如何在JavaScript中实现函数缓存,以及它有哪些应用场景。 我们先来明确一下,函数缓存是什么。简单来说,函数缓存是将函数的运算结果存储起来,以便下次用到相同的输入时,可以直接返回结…...

子页面访问父页面
子页面访问父页面的方式主要依赖于页面之间的关系,特别是它们是否处于同一域、是否是嵌套在 <iframe> 中、或者通过弹出窗口打开。下面是几种常见的子页面访问父页面的方法: 1. 通过 window.parent 访问父页面(适用于嵌套的 iframe&am…...

芯片级IO (Pad) Ring IP Checklist
SoC top顶层数字后端实现都会涉及到IO Ring (PAD Ring)的设计。这里面包括VDD IO,VDDIO IO, Signal IO, Corner IO,Filler IO,IO power cut cell等等。 数字后端零基础入门系列 | Innovus零基础LAB学习Day2 数字IC后端实现TOP F…...

计算机毕业设计论文指导
计算机毕业设计论文指导 计算机毕业设计辅导一站式!太香了💪 [赞R][赞R][赞R]嗨喽!计算机专业的宝子们! 计算机毕设辅导专业靠谱的他来了!! 是不是还在为选题程序不会做而感到苦难? 论文没思路赶…...

Electron-Vue 开发下 dev/prod/webpack server各种路径设置汇总
背景 在实际开发中,我发现团队对于这几个路径的设置上是纯靠猜的,通过一点点地尝试来找到可行的路径,这是不应该的,我们应该很清晰地了解这几个概念,以下通过截图和代码进行细节讲解。 npm run dev 下的路径如何处理&…...

Vue.js前端框架教程9:Vue插槽slot用法
文章目录 插槽(Slots)无名插槽(默认插槽)具名插槽reference 插槽使用 v-slot 的缩写语法 插槽(Slots) 在 Vue 中,插槽(Slots)是一种组件内容分发的机制,允许…...

初学stm32 --- NVIC中断
目录 STM32 NVIC 中断优先级管理 NVIC_Type: ISER[8]: ICER[8]: ISPR[8]: ICPR[8]: IABR[8]: IP[240]: STM32 的中断分组: 中断优先级分组函数 NVIC_PriorityGroupConfig 中断初始化函…...

Jest 入门指南:从零开始编写 JavaScript 单元测试
前言 在前端开发中,单元测试已经成为确保代码质量和稳定性的关键步骤。Jest 作为由 Facebook 开发和维护的功能强大的 JavaScript 测试框架,以其易于配置、丰富的功能和开箱即用的特性,成为众多开发者的首选工具。本文旨在引导你从零开始&am…...

【Java Web】Axios实现前后端数据异步交互
目录 一、Promise概述 二、Promise基本用法 三、async和await关键字 四、Axios介绍 4.1 Axios基本用法 4.2 Axios简化用法之get和post方法 五、Axios拦截器 六、跨域问题处理 一、Promise概述 axios是代替原生的ajax实现前后端数据交互的一套新解决方案,而…...

React 第十七节 useMemo用法详解
概述 useMemo 是React 中的一个HOOK,用于根据依赖在每次渲染时候缓存计算结果; 大白话就是,只有依赖项发生变化时候,才会重新渲染为新计算的值,否则就还是取原来的值,有点类似 vue 中的 computed 计算属性…...

鸿蒙项目云捐助第十五讲云数据库的初步使用
鸿蒙项目云捐助第十五讲云数据库的初步使用 在华为云技术使用中,前面使用了云函数,接下来看一下华为云技术中的另外一个技术云数据库的使用。 一、云数据库的创建 这里使用华为云数据库也需要登录到AppGallery Connect平台中,点击进入到之…...

如何构建一个可信的联邦RAG系统。
今天给大家分享一篇论文。 题目是:C-RAG:如何构建一个可信的联邦检索RAG系统。 论文链接:https://arxiv.org/abs/2412.13163 论文概述 尽管大型语言模型 (LLM) 在各种应用中展现出令人印象深刻的能力,但它们仍然存在可信度问题ÿ…...

【深度学习之三】FPN与PAN网络详解
FPN与PAN:深度学习中的特征金字塔网络与路径聚合网络 在深度学习的领域里,特征金字塔网络(Feature Pyramid Networks,简称FPN) 和 路径聚合网络(Path Aggregation Network,简称PAN)…...

Qt学习笔记第71到80讲
第71讲 事件过滤器的方式实现滚轮按键放大 事件体系(事件派发 -> 事件过滤->事件分发->事件处理)中,程序员主要操作的是事件分发与事件处理。我们之前已经通过继承QTextEdit来重写事件实现Ctrl加滚轮的检测,还有一种处理…...

以管理员身份运行
同时按下Ctrl Shift Esc键打开任务管理器,在任务管理器的左上角,点击“文件”菜单,在下拉菜单中选择“新建任务” 在弹出的对话框中,输入您想要运行的程序的名称。如果您不确定程序的确切名称,可以点击“浏览”来找到…...

用 Python 实现井字棋游戏
一、引言 井字棋(Tic-Tac-Toe)是一款经典的两人棋类游戏。在这个游戏中,玩家轮流在 3x3 的棋盘上放置自己的标记,通常是 “X” 和 “O”,第一个在棋盘上连成一线(横、竖或斜)的玩家即为获胜者。…...

06 实现自定义AXI DMA驱动
为什么要实现自定义AXI DMA驱动 ZYNQ 的 AXI DMA 在 Direct Register DMA (即 Simple DMA)模式下可以通过 AXIS 总线的 tlast 提前结束传输,同时还可以在 BUFFLEN 寄存器中读取到实际传输的字节数,但是通过 Linux 的 DMA 驱动框架…...

SpringBoot集成ENC对配置文件进行加密
在线MD5生成工具 配置文件加密,集成ENC 引入POM依赖 <!-- ENC配置文件加密 --><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.2</ver…...

初学stm32 ——— 串口通信
目录 STM32的串口通信接口 UART异步通信方式特点: 串口通信过程 STM32串口异步通信需要定义的参数: USART框图: 常用的串口相关寄存器 串口操作相关库函数 编辑 串口配置的一般步骤 STM32的串口通信接口 UART:通用异步收发器USART&am…...

qwt 多Y轴 项目效果
项目场景: 在做一个半导体上位机软件项目实践中,需要做一个曲线展示和分析界面,上位机主题是用qt框架来开发,考虑到目前qt框架的两种图标库,一个是qcustomplot 一个是 qwt。之所以采用qwt ,根本原因是因为…...

Java中通过ArrayList扩展数组
在Java中,ArrayList 是一个动态数组实现,能够根据需要自动调整其大小。与传统的数组不同,ArrayList 不需要预先指定大小,并且提供了许多方便的方法来操作集合中的元素。下面将详细介绍如何使用 ArrayList 进行数组的扩展ÿ…...

Java:链接redis报错:NoSuchElementException: Unable to validate object
目录 前言报错信息排查1、确认redis密码设置是否有效2、确认程序配置文件,是否配置了正确的redis登录密码3、检测是否是redis持久化的问题4、确认程序读取到的redis密码没有乱码 原因解决 前言 一个已经上线的项目,生产环境的redis居然没有设置密码&…...

datasets库之load_dataset
目录 问题解决方案 问题 使用peft用lora微调blip2时用到了一个足球数据集,如下: 原始代码如下 dataset load_dataset("ybelkada/football-dataset", split"train")然而这需要梯子才能下载,服务器较难用VPN所以使用au…...

React Router常见面试题目
1. React Router 支持哪几种模式? React Router 支持以下两种主要模式: BrowserRouter (基于 HTML5 History API 的模式) 原理: 利用 history.pushState 和 history.replaceState 操作浏览器历史栈,无需重新加载页面。URL 看起来像传统 URL…...

sequelize-cli 封装登录接口
node ORM (sequelize)使用、查询、验证及express 基础框架的搭建及实例的使用 一、思路 第一步:肯定是用户要向接口传递邮箱、账号和密码了。 第二步:接口这边,先要验证。因为这里不是往数据库里存储数据,…...

使用 Elasticsearch 查询和数据同步的实现方法
在开发过程中,将数据从数据库同步到 Elasticsearch (ES) 是常见的需求之一。本文将重点介绍如何通过 Python 脚本将数据库中的数据插入或更新到 Elasticsearch,并基于多字段的唯一性来判断是否执行插入或更新操作。此外,我们还将深入探讨如何…...

QTday1作业设置简易登录界面
代码 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//创建一个标签QLabel *lab1 new QLabel(this);//重新设置大小lab1->resize(1925,1080);//用动图类QMovie实例化一个动图QMovie *mv new QMovie("C:\\Users\\MR\\Deskto…...

RC低通滤波器和LR低通滤波器,LC低通滤波器该怎么选择
这是RC低通滤波器利用电容两端的电压不能突变可以滤除高频噪声 这是LR低通滤波器利用流过电感的电流不能突变也可以滤除高频噪声 那么问题来了两个低通滤波器,该怎么选择呢还是随便选一个就好? RC电路:因为电流电阻会发热耗能,所…...