【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程

文章目录

  • 注册杂项设备
  • 驱动模块传参
  • 注册字符设备

开发环境: windows + ubuntu18.04 + 迅为rk3568开发板

注册杂项设备

相较于字符设备,杂项设备有以下两个优点:

  • 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时,只需使用子设备号进行区分即可。
  • 使用简单:相比如普通的字符设备驱动, misc驱动只需要将基本信息通过结构体传递给相应处理函数即可。

在linxu系统中可使用 cat /proc/misc 命令查看系统中的杂项设备。注册杂项设备的步骤:

  • 1.填充设备操作集结构体struct file_operations

  • 2.填充杂项设备结构体struct miscdevice;

  • 3.使用函数misc_register注册杂项设备;

  • 4.使用函数misc_deregister卸载杂项设备;

上面三步可使用下面函数直观用表达,即:

static struct file_operations xxx_fops{.owner = THIS_MODULE, .read = xxx_read, ....
};
struct miscdevice xxx_dev{.minor = MISC_DYNAMIC_MINOR, .name = "xxx", .fops = &xxx_fops
};
static int __init xxx_init(void) //驱动入口函数
{int ret;printk(KERN_EMERG "xxx_init\r\n");ret = misc_register(&xxx_dev);//注册杂项设备if(ret<0){printk( "misc_register failed\r\n");return -1;}printk( "misc_register ok\r\n");return 0;
}
static void __exit xxx_exit(void) //驱动出口函数
{printk(KERN_EMERG "xxx_exit\r\n");misc_deregister(&xxx_dev); //卸载杂项设备
}
module_init(xxx_init); //注册入口函数
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

具体实现注册一个杂项设备的示例代码如下:

#include <linux/kernel.h>
#include <linux/init.h>              //初始化头文件
#include <linux/module.h>            //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>        //注册杂项设备头文件
#include <linux/fs.h>                //注册设备节点的文件结构体
#include <linux/uaccess.h>// 打开杂项设备
int _open(struct inode *inode,struct file*file)
{printk(KERN_EMERG"hello misc");return 0;
}// 关闭杂项设备
int close(struct inode * inode, struct file *file)
{printk(KERN_EMERG"close");return 0;
}// 读取杂项设备中的数据
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] = "kernel";if(copy_to_user(buff,kbuff,strlen(kbuff)) != 0) // 将内核中的数据给应用{printk("copy_to_user error\r\n");return -1;}	printk(KERN_EMERG"copy_to_user is successful\r\n");return size;
}// 写入数据到杂项设备中
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] ;if(copy_from_user(kbuff,buff,size)!= 0) // 从应用那儿获取数据{printk("copy_from_user error\r\n");return -1;}	printk(KERN_EMERG"copy_from_user data:%s\r\n",kbuff);return size;
}// 设备文件描述集  
struct file_operations misc_fops ={.owner = THIS_MODULE,.open = misc_open,.release = close,.read = misc_read,.write = misc_write
};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc", // 杂项设备名   注册成功后会在 /dev目录下显示.fops = &misc_fops
};// 驱动的入口函数
static int __init misc_init(void)
{int ret = 0;ret = misc_register(&misc_dev);if(ret < 0)printk(KERN_EMERG"misc register is error\r\n.");elseprintk(KERN_EMERG"misc register is seccussful\r\n.");return 0;
}//驱动的出口函数
static void __exit misc_exit(void)
{misc_deregister(&misc_dev);printk(KERN_EMERG"baibai\r\n");
}module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");

编译传送到开发板上后,先试用insmod +驱动名.ko挂载驱动,其结果为:
在这里插入图片描述

上述驱动代码的测试代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char *argv[])
{char filePath[] = "/dev/hello_misc";// 打开文件int fd = open(filePath,O_RDWR);if(fd < 0){printf("opening is failed.\n");return -1;}elseprintf("opening is successful.\n");// 读取char buff1[32],buff2[32] = "hello this is app";read(fd,buff1,sizeof(buff1));printf("buff1 is %s.\n",buff1);// 写入write(fd,buff2,sizeof(buff2));	close(fd);return 0;
}

使用./+程序名运行测试代码后,得结果如下:
在这里插入图片描述


驱动模块传参

总所周知,应用程序传参是通过shell终端传,只要将main函数按照下面格式书写即可完成传参操作

int main(int argc ,char *argv[])
{return 0;
}

相比之下,驱动模块传递参数需要借助其他函数完成传参操作:

1. 传递单个参数给内核

module_param(name, type, perm)

参数解释:

  • name:参数名,既是外部参数名,又是内部参数名。

  • type:参数的数据类型,可取int、charp等。

  • perm:访问权限。八进制,如:0777。0表示该参数在文件系统中不可见。

注意:传递字符作为参数时数据类为charp,而不是char.

2.传递数组给内核

module_param_array(name, type, nump, perm)
  • module_param_array(name,type,nump,perm)
  • name:数组参数名,既是外部参数,又是内部参数
  • type:参数的数据类型
  • nump:终端传给数组的实际元素个数(指针变量)
  • perm:访问权限,0644。0表示该参数在文件系统中不可见

3.传递字符串给内核

module_param_string(name, string, len, perm)
  • name:参数名,外部参数名
  • string:内部参数名(内部字符数组名)
  • len:数组长度
  • perm:访问权限,0644。0表示该参数在文件系统中不可见

传递参给内核的作用:

  • 1.设置驱动的相关参数,如:设备数量、设备缓冲区大小等等。
  • 2.可进行安全校验,放置驱动被他人盗用。

说明
参数传递的适用时机为 加载驱动到内核时,命令形式为:insmod +驱动名.ko +参数1名=参数值 参数2名=参数值 ……。例如下面示例中使用insmod file.ko date=12传递参数:

#include <linux/module.h>
#include <linux/init.h>// 保存参数
static int date;
static int date1;
static int data[5];
static int count;
static char str[32];
static char *strData;// 传递单个参数
module_param(date,int,S_IRUGO|S_IWUSR); // 可读可写
module_param(date1,int,S_IRUGO); // 可读可写
module_param(strData,charp,S_IRUGO); // 可读// 传递多个参数
module_param_array(data,int,&count,S_IRUGO); // 可读
module_param_string(str,str,sizeof(str),S_IRUGO); // 可读// 驱动的入口函数
static int __init dev_init(void)
{int i=0;if(strcmp(str,"myTest")!=0){printk("dev_init error\r\n");return -1;}printk("---------------------------------------\r\n");printk(KERN_EMERG"dev_init is successful!\r\n");for(i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("str:%s  strData:%s\r\n",str,strData);printk("date:%d  count:%d\r\n",date,count);data[1] = 111;date = 10;date1 = 111;return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{int i=0;for( i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("date:%d  count:%d\r\n",date,count);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");

注册字符设备

步骤一:驱动初始化,需要申请设备号,初始化并且注册cdev结构体,初始化硬件;

可使用动态申请或静态申请设备号,其中动态申请设备号一般在235-255,静态申请则一般由用户手动输入。

静态申请的函数原型为:

 int register_chrdev_region(dev_t, unsigned, const char *);

参数含义:

  • from: 自定义的 dev_t 类型设备号。
  • count: 申请设备的数量。
  • name: 申请的设备名称。
    函数返回值:申请成功返回 0,申请失败返回负数。

动态申请的函数原型为:

 int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

参数含义:

  • dev : 会将申请完成的设备号保存在 dev 变量中。
  • baseminor: 次设备号可申请的最小值。
  • count: 申请设备的数量。
  • name: 申请的设备名称。
    函数返回值:申请成功返回 0,申请失败返回负

Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,cdev 记录了字符设备号、内核对象、文件操作 file_operations 结构体(设备的打开、读写、关闭等操作接口)等信息:

struct cdev {struct kobject kobj; //内嵌的内核对象.struct module *owner; //该字符设备所在的内核模块的对象指针. const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.struct list_head list; //用来将已经向内核注册的所有字符设备形成链表. dev_t dev; //字符设备的设备号,由主设备号和次设备号构成. unsigned int count; //隶属于同一主设备号的次设备号的个数. 
};

初始化设备描述集cdev结构体的函数原型为:

void cdev_init(struct cdev *, const struct file_operations *);

参数含义:

  • 参数1:表示是抽象设备结构体;
  • 参数2:表示文件操作集;

注册设备到内驱使用下面函数

int cdev_add(struct cdev *, dev_t,  unsigned);

参数含义:

  • 参数1:为要添加的 struct cdev 类型的结构体
  • 参数2:为申请的字符设备号
  • 参数3:为和该设备关联的设备编号的数量
    若函数在内核中添加成功返回 0,添加失败返回负数。

步骤二:构建设备文件操作描述集file_operations

也就是read、write、open、close等函数。

步骤三:生成并且添加设备节点

  • 手动添加设备节点:也就是在加载驱动到内核时添加,即mknod + 路径/设备名 +设备类型 + 主设备号 + 次设备号
  • 自动添加设备节点:初始化内核时,使用函数自动添加。即先试用函数class_create创建一个类,在使用函数device_create创建并且添加设备节点。

class_create函数说明:

#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \ __class_create(owner, name, &__key); \
})

函数作用:
用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进 Linux 内核系统。

参数含义:

  • owner:指向函数即将创建的这个 struct class 的模块。一般为 THIS_MODULE。
  • name:代表即将创建的 struct class 变量的名字。
    返回值:struct class * 类型的类。

device_create函数说明:


struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt,...);

函数作用:
用来在 class 类中下创建一个设备属性文件,udev 会自动识别从而进行设备节点的创建。

参数含义:

  • cls:指定所要创建的设备所从属的类。
  • parent:指定该设备的父设备,如果没有就指定为 NULL。
  • devt:指定创建设备的设备号。
  • drvdata:被添加到该设备回调的数据,没有则指定为 NULL。
  • fmt:添加到系统的设备节点名称。

返回值:struct device * 类型结构体的设备

步骤四:注销字符设备驱动

  • 步骤一:使用函数unregister_chrdev_region释放设备号
void unregister_chrdev_region(dev_t, unsigned)

该函数只有一个参数,为要删除设备的设备号,并且函数无返回值。

  • 步骤二:使用函数cdev_del步骤二:删除设备操作集卸载cdev
void cdev_del(struct cdev *);

该函数只有一个参数,为要删除的 struct cdev 类型的结构体,并且函数无返回值。

  • 步骤三:使用函数device_destroy卸载设备
void device_destroy(struct class *cls, dev_t devt);

用来删除 cls 类中的devt设备属性文件,udev 会自动识别从而进行设备节点的删除。

  • 步骤四:使用函数class_destroy删除类class
 void class_destroy(struct class *cls);

该函数只有一个参数,为要删除的类,并且函数无返回值。

示例代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>#define DEVICE_NUMBER 1 // 设备数量
#define DEVICE_SNAME "schrdev" // 静态申请时设备名
#define DEVICE_ANAME "achrdev" // 动态申请时设备名
#define DEVICE_MINOR_NUM 0 // 次设备号起始地址
#define DEVICE_CLASS_NAME "myTestClass" // 类名
#define DEVICE_MYNAME "mytest"// 打开设备
int chrdev_open(struct inode*inode,struct file*file)
{printk("chrdev_open is opened\r\n");return 0;
}// 保存设备号 其中前12位为主设备号 后20位为次设备号
static dev_t dev_num;// 定义主设备号 次设备号
static int major_num,minor_num; // 设备信息描述集
struct cdev cdev;// 类描述集
struct class *cls;// 设备描述集
struct device*device;// 传递单个参数
module_param(major_num,int,S_IRUGO); // 可读
module_param(minor_num,int,S_IRUGO); // 可读// 文件操作集
struct file_operations chrdev_opr = {.owner = THIS_MODULE,.open = chrdev_open
};// 驱动的入口函数
static int __init dev_init(void)
{int ret;/* 步骤一:申请设备号 */// 有传递主设备号就静态申请if(major_num){	dev_num = MKDEV(major_num, minor_num);//将主设备号 次设备号合并成设备号//参数分别表示 设备号 设备数量 设备名称ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if(ret < 0){printk("dev_init error\r\n");return -1;}}	// 否则就动态申请else{// 参数:设备号 次设备号起始地址 设备数量 设备名称ret = alloc_chrdev_region(&dev_num , DEVICE_MINOR_NUM, DEVICE_NUMBER, DEVICE_ANAME);if(ret < 0){printk("dev_init error\r\n");return -1;}// 获取主设备号 次设备号major_num = MAJOR(dev_num);minor_num = MINOR(dev_num);}printk("---------------------------------------\r\n");printk("dev_num:%d major_num:%d minor_num:%d\r\n",dev_num,major_num,minor_num);/* 步骤二:初始化设备 */// 初始化cdevcdev.owner = THIS_MODULE;// 初始化设备  cdev_init(&cdev, &chrdev_opr);/* 步骤三:注册设备到内核 *///添加(注册)到内核 参数: 设备   设备号  设备数量cdev_add(&cdev, dev_num, DEVICE_NUMBER);/* 步骤四:先创建类再自动创建添加设备名称*/// 创建类 参数1: 类的归属   参数2: 类名cls = class_create(THIS_MODULE,DEVICE_CLASS_NAME);// 创建设备 参数1: 归属到类   参数2:设备的父设备 参数3:设备号 参数4:添加到设备的回调数据 参数5:设备名device = device_create(cls,NULL,dev_num,NULL,DEVICE_MYNAME);printk("auto add device name\r\n");return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{/* 步骤一:注销设备号 参数:设备号   设备数量 */ unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);/* 步骤二:删除设备操作集 */cdev_del(&cdev);/* 步骤三:删除设备*/device_destroy(cls,dev_num);/* 步骤四: 删除类*/class_destroy( cls);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");

使用加载驱动到内核insmod file.ko命令,得结果
在这里插入图片描述

在测试驱动前需要使用mknod /dev/mytest c 236 0创建设备文件,命令格式为:mknod + /路径/设备名称 +设备类型+主设备号 + 次设备号

然后就可写一个应用程序来测试我们的字符驱动是否成功注册,测试源码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char* argv[])
{char name[] = "/dev/mytest";int fd = open(name,O_RDONLY); // 仅读if(fd < 0){printf("open is failed\n");return -1;}close(fd);return 0;
}

执行测试程序后结果如下:
在这里插入图片描述

注意:在运行测试程序时,一定要先创建驱动文件,否则就会报段错误,具体如下:
在这里插入图片描述


杂项设备与字符设备的比较

  • 杂项设备的主设备号固定为10,而字符设备的主设备号需要创建。
  • 杂项设备的创建相对简单,只需要填充设备操作集结构体、杂项设备结构体再注册即可。而字符设备需要经过 申请设备号、初始化并且注册cdev结构体、初始化硬件、构建设备文件操作描述集、生成并且添加设备节点。
  • 杂项设备与字符设备都需要构建文件操作描述集。(应用层调用驱动的核心)

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

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

相关文章

【分布式技术】监控平台zabbix自定义模板、设置邮件报警、导入模板

目录 案例&#xff1a;监控当前登录人数&#xff0c;超过3人触发报警发送邮件 第一步&#xff1a;自定义模板 1、明确想要获取监控数据的命令和脚本 ​编辑 2、在被监控主机上&#xff0c;修改zabbix agent2的配置文件或者在zabbix agent2的配置文件目录中添加以.conf结尾…

三棋先手必胜证明

目录 创作原因 游戏规则 初始状态图 证明过程 先手必胜的证明 失败的博弈树&#xff08;三个多小时的成果&#xff09; 创作原因 这个棋不是网上流行的成三棋&#xff0c;我也不知道这个棋叫什么。由于这个棋是&#xff08;横竖斜&#xff09;连成三个就获胜&#xff0c;…

GBASE南大通用数据库如何检索单行

SELECT 语句返回的行集是它的活动集。单个 SELECT 语句返回单个行。您可使用嵌入式 SELECT 语句来从数据库将单个行检索到主变量内。然而&#xff0c;当 SELECT 语句返回多行数 据时&#xff0c;程序必须使用游标来一次检索一行。在 检索多行 中讨论“多行”选择操作。 要检索单…

虹软人脸识别白屏

1.修改jdk为1.8 2.编译版本ndk修改 ndk {abiFilters armeabi-v7a, arm64-v8a} 3.local.properties增加ndk路径 ndk.dirH\:\\Android\\SDK\\ndk\\21.1.6352462 4.最重要的一步:检查依赖库是否存在前一定要先检查有么有读取本地文件权限,下面的代码即使是放到点击事件的回调…

[AutoSar]BSW_OS 01 Autosar OS入门(一)

目录 关键词平台说明一、Autosar OS 的位置二、Autosar OS 与OSEK三、TASK 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector芯片厂商TI编程语言C&#xff0c;C编译器HighTec (GCC) 一、Autosar OS 的位置 如在[AutoSar]基础部分 a…

RuoYi-Vue-Plus 5.X登录前流程及解密

一&#xff1a;问题 1. 前端传给后端的是一个加密字符串&#xff0c;后端controller层login接口怎么就直接解密了呢&#xff1f; 2. 中间经过什么步骤到达的登录接口呢&#xff1f; 二&#xff1a;个人分析 首先考虑的是拦截器、过滤器、切面AOP&#xff1b; 1. 使用全文搜…

面向对象的三大特性

个人主页&#xff1a;告别&#xff0c;今天 个人专栏&#xff1a;java趣味之旅 ​​​​​​​本专栏旨在分享学习网络编程的学习心得和复习总结&#xff0c;欢迎大家在评论区交流讨论 1. 封装 1.1 封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象…

计算机msvcp140.dll丢失如何解决,分享3个简单有效的方法

在计算机系统运行过程中&#xff0c;用户有时会遇到一个常见的错误提示——msvcp140.dll文件缺失&#xff0c;这一问题的发生往往会导致部分软件无法正常启动或运行。“针对计算机系统中出现的msvcp140.dll缺失问题&#xff0c;小编将详尽阐述并探讨5种有效的解决策略。每一种方…

基于springboot+html的汽车销售管理系统设计与实现

基于springboothtml的汽车销售管理系统 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 前言 随着汽车市场的快速发展&#xff0c;汽车销售企业面临着越来越大的管理…

【电路电子学】7天速通攻略+笔记

7天是 看视频记笔记刷题的总时长&#xff0c;时间紧迫的同学可以看情况进行缩减。个人认为做题&#xff0c;尤其是解析齐全的题最重要&#xff01; 我校所用教材 《电路与电子学基础》唐胜安 复习总流程 所用材料&#xff08;都可自行找到免费资源&#xff09; 视频知识点讲…

R2机器人加载棋盘与棋子模型,对urdf、sdf的解释(区分srdf)

1、概述 urdf、sdf、srdf文件都属于xml的规范格式&#xff0c;解释分别如下&#xff1a;urdf(unified robot description format)叫做"统一机器人描述格式"&#xff0c;主要目的就是提供一种尽可能通用的机器人描述规范&#xff0c;这样对于机器人的描述就可以互相移…

恭喜:ChatGPT之父与相恋多年的男友结婚,并希望早日生娃。。。

OpenAI CEO Sam Altman与伴侣Oliver Mulherin海边私密婚礼&#xff1a;爱情、事业与人工智能领域的交织 婚礼主持人是奥特曼的兄弟杰克奥尔特曼 壹.媒体流传 在科技界掀起波澜的OpenAI首席执行官萨姆奥尔特曼&#xff08;Sam Altman&#xff09;&#xff0c;近期与他长久以来的…

Opencv实验合集——实验九:姿势估计

在上一章节(相机校准)&#xff0c;你已经找到了相机矩阵&#xff0c;畸变系数等等参数。给出一个图案图像&#xff0c;我们便可以利用上面的信息用于计算其姿势&#xff0c;或者物体在空间中位于何处&#xff0c;比如如何旋转&#xff0c;如何移动等等问题。对于一个平面物体&a…

Java - FFM API 实现扫雷助手

文章目录 前言环境思路实现扫雷常量高度/宽度/雷数地图基址 屏幕坐标 效果资源 前言 使用 FFM API 实现扫雷助手. 环境 Win11 JDK 21 思路 读取扫雷地图数据判断该数据是否为雷模拟鼠标点击重复上面操作遍历地图直至完成 确定了思路&#xff0c;那么就要确认 windows 系…

Rust-模式解构

match 首先&#xff0c;我们看看使用match的最简单的示例&#xff1a; exhaustive 有些时候我们不想把每种情况一一列出&#xff0c;可以用一个下划线来表达“除了列出来的那些之外的其他情况”&#xff1a; 下划线 下划线还能用在模式匹配的各种地方&#xff0c;用来表示…

Java+Mysql存储过程生成订单序列号

Mysql存储过程生成唯一订单号 直接上代码&#xff01;&#xff01; 1.创建存储过程 CREATE DEFINERrootlocalhost PROCEDURE getOrderSerialNo(# 前缀in orderPrefix varchar(64), # 返回结果out result int) BEGIN# 当前流水号declare curOrderNo int;# 默认值为0declare e…

uniapp小程序超出一行显示...并展示更多按钮

注意:全部标签需要浮动在父盒子右边哦 循环获取所有需要展示数据标签的高度 this.goods this.goods.map(item > ({...item,showBtn: false}));this.$nextTick(() > {uni.createSelectorQuery().in(this).selectAll(".cart-info").boundingClientRect((data)…

yolov7中断训练后继续训练

1、训练指令 &#xff08;1&#xff09;添加resume参数&#xff0c;参数值改为true &#xff08;2&#xff09;weights参数&#xff0c;参数值改为中断前上次训练权重 中断后继续训练命令&#xff1a; python.exe train.py --weights runs/train/exp9/weights/last.pt --re…

SpringBoot3 WebFlux 可观测最佳实践

前言 链路追踪是可观测性软件系统的一个非常好的工具。它使开发人员能够了解应用程序中和应用程序之间不同交互发生的时间、地点和方式。同时让观测复杂的软件系统变得更加容易。 从Spring Boot 3开始&#xff0c;Spring Boot 中用于链路追踪的旧 Spring Cloud Sleuth 解决方…

Unity2022.3打包Android后从AB包加载场景发现丢失大量脚本问题

问题 这两天遇到一个问题&#xff0c;在VR项目打包Android的时候&#xff0c;加载场景后&#xff0c;Timeline工作不正常&#xff0c;找不到原因。 现象 看到有很多警告&#xff0c;丢失脚本的Log。 因为场景本身也有一些丢失的脚本所以没在意&#xff0c;但是又不是所有脚本…