当前位置: 首页 > article >正文

rk3588 驱动开发(一)字符设备开发

3.字符设备驱动开发

3.1 什么是字符设备驱动

字符设备:就是一个个字节,按照字节流进行读写操作的设备,读写是按照先后顺序的。
举例子:IIC 按键 LED SPI LCD 等
Linux 应用程序调用驱动程序流程:
Linux中驱动加载成功后会在/dev 中生成一个/dev/xxx(xxx是驱动文件名字)文件 应用程序对/dev/xxx进行open write read close 等操作
在这里插入图片描述

在这里插入图片描述

应用程序open函数调用c库函数,c库中的open函数调用系统内核态的open函数,然后调用驱动中的open函数。

3.2字符设备驱动开发

3.2.1 驱动模块的加载和卸载

驱动运行方式有两种:
1、将驱动编译到内核中启动时自动运行驱动程序
2、在 Linux 内核启动以后使用“modprobe”或者“insmod”命令加载驱动模块

这里有两个函数:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

使用modprobe时module_init函数被调用

使用rmmod时module_exit函数被调用

入口函数:

static int __init xxx_init(void)
{/* 入口函数具体内容 */return 0;
}

出口函数:

static void __exit xxx_exit(void)
{
/*出口函数内容*/
}

insmod和modprobe的关系:

insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
modprobe 就不会存在这个问题,modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此 modprobe 命令相比 insmod 要智能一些。
modprobe 命令主要智能在提供了模块的依赖性分
析、错误检查、错误报告等功能,推荐使用 modprobe 命令来加载驱动。modprobe 命令默认会去/lib/modules/<kernel-version>目录中查找模块,kernel-version为内核版本号。

驱动模块卸载
驱动模块的卸载使用命令“rmmod”即可,比如要卸载 drv.ko,使用如下命令即可:
rmmod drv.ko
也可以使用“modprobe -r”命令卸载驱动,比如要卸载 drv.ko,命令如下:
modprobe -r drv
使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。所以对于模块的卸载,还是推荐使用 rmmod 命令。

3.2.2 字符设备注册与注销

当驱动模块加载成功后,需要注册字符设备。当驱动模块卸载成时,需要将字符设备注销。
注册和注销设备驱动函数原型如下:

static inline int register_chrdev(unsigned int major, 
const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, 
const char *name)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,
name:设备名字,指向一串字符串。
fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major:要注销的设备对应的主设备号。
name:要注销的设备对应的设备名。

查看已使用的设备号:cat /proc/devices

在这里插入图片描述

3.2.3 实现设备的具体操作函数

file_operation结构体就是具体的操作函数,如open read write release

29 static struct file_operations test_fops = {
30 .owner = THIS_MODULE, 
31 .open = chrtest_open,
32 .read = chrtest_read,
33 .write = chrtest_write,
34 .release = chrtest_release,
35 };

指定初始化器(designated initializer),.成员名 = 值 的语法格式在 C99 标准中被引入。

3.2.4 添加LICENSE和作者信息

LICENSE 是必须添加的,
否则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加
使用如下两个函数:
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
GPL(GNU General Public License,GNU通用公共许可证)是一种开源软件许可协议,由自由软件基金会(FSF)制定,旨在保护软件自由的使用、修改和再发布权利。
GPL 是“你用了我的代码,你就得开源你的代码” 的开源协议。

3.3设备号

3.3.1什么是设备号

设备号(Device Number)是 Linux 内核中用于标识一个设备的数字标识,它由两个部分组成:

🧩 1. 主设备号(Major Number)
表示这个设备属于哪一个驱动程序。
也就是说,哪个内核驱动模块来处理这个设备的请求。

🧩 2. 次设备号(Minor Number)
表示由同一个驱动程序管理的多个设备中的哪一个。
相当于:驱动负责整条生产线,次设备号表示哪个产品。
Linux 提供了一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,定义
如下:

13 typedef u32 __kernel_dev_t;
......
16 typedef __kernel_dev_t dev_t;

可以看出 dev_t 是 u32 类型的,也就是 unsigned int,所以 dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主设备号和次设备号两部分,其中高 12位为主设备号,低 20 位为次设备号。因此 Linux 系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。

在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:

7 #define MINORBITS 20
8 #define MINORMASK ((1U << MINORBITS) - 1)
9 
10 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
11 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
12 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

第 7 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。
第 8 行,宏 MINORMASK 表示次设备号掩码。
第 10 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
第 11 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
第 12 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备
号。

3.3.2 设备号的分配

设备号分配为:静态分配和动态分配

1、静态分配
cat /proc/devices 查看当前设备中使用的设备号如果没有被使用的设备号就能注册
2、动态分配
在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
dev:保存申请到的设备号。
baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count:要申请的设备号数量。
name:设备名字。

注销字符设备之后要释放掉设备号,设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)

此函数有两个参数:
from:要释放的设备号。
count:表示从 from 开始,要释放的设备号数量。

3.4 chrdevbase 字符设备驱动开发实验

我们就以 chrdevbase 这个虚拟设备为
例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备,是为了方便讲解字符设备的开发而引入的一个虚拟设备。chrdevbase 设备有两个缓冲区,一个读缓冲区,一个写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。
驱动文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>#define CHRDEVBASE_MAJOR	200				/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/** @description	: 驱动入口函数 * @param 		: 无* @return 		: 0 成功;其他 失败*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}/* * 将上面两个函数指定为驱动的入口和出口函数 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("123");
MODULE_INFO(intree, "Y");

驱动程序书写流程:
1、写入口函数 出口函数

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

2、实现入口函数出口函数
入口函数中注册字符设备
出口函数注销字符设备

static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}

3、实现file_operations 结构体 和open read write release函数

/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};

APP文件

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"static char usrdata[] = {"usr data!"};/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd  = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{/*  读取成功,打印出读取成功的数据 */printf("read data:%s\r\n",readbuf);}}if(atoi(argv[2]) == 2){/* 向设备驱动写数据 */memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

Makefile

KERNELDIR := /home/lk/rk3588_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)
# obj-m 是内核模块编译规则中的一个特殊变量。
# obj-m 定义了要生成的模块目标文件(即 .ko 文件)。
# obj-m 表示编译时将 chrdevbase.o 作为模块(module)对象,最终会生成 chrdevbase.ko。
# chrdevbase.o# chrdevbase.o 是将 chrdevbase.c 文件编译为目标文件(.o 文件)的名称。
# 生成的目标文件会自动链接成内核模块 chrdevbase.ko。
obj-m := chrdevbase.o
# make 会首先检查 kernel_modules 目标。
# 如果 kernel_modules 目标没有生成或需要更新,make 会执行 kernel_modules 的命令。
# 执行完 kernel_modules 后,build 目标就算完成了。
build : kernel_modules# kernel_modules# 定义一个名为 kernel_modules 的目标。
# 当执行 make kernel_modules 时,会触发后面的命令。# $(MAKE)# $(MAKE) 是一个特殊的变量,表示 make 命令本身。
# 使用 $(MAKE) 而不是直接调用 make 可以在嵌套调用时保持参数一致性。
# -C $(KERNELDIR)# -C 选项表示切换到 $(KERNELDIR) 目录下执行命令。
# $(KERNELDIR) 是一个变量,通常指定为 Linux 内核源码的构建目录。
# 在内核源码目录中调用 make 会使用内核的构建系统。
# M=$(CURRENT_PATH)# M= 选项告诉内核构建系统,当前模块的源代码位于 $(CURRENT_PATH) 目录下。
# modules# modules 是内核构建系统的一个目标,表示要构建模块(.ko 文件)。
# 当传入 modules 目标时,内核会根据 obj-m 定义的模块进行编译。
# 总结  使用make buil 就会检查kernel_modules是否存在或者更新 ,kernel_modules会执行$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
#也就是 make 内核路径 当前文件路径 生成modules即obj-m 对应的 chrdevbase.o生成chrdevbase.ko文件
kernel_modules :$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

Makefile 用法 根据配置
KERNELDIR := /home/lk/rk3588_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
运行build 然后运行kernel_modules 后的
其中:= 是简单赋值 :是目标依赖
M= 是 Make 运行参数,不是 Makefile 自身定义变量的语法
-C:切换目录到内核源码目录
modules:告诉它“我要构建模块”

相关文章:

rk3588 驱动开发(一)字符设备开发

3.字符设备驱动开发 3.1 什么是字符设备驱动 字符设备&#xff1a;就是一个个字节&#xff0c;按照字节流进行读写操作的设备&#xff0c;读写是按照先后顺序的。 举例子&#xff1a;IIC 按键 LED SPI LCD 等 Linux 应用程序调用驱动程序流程&#xff1a; Linux中驱动加载成功…...

AbMole| 体内相互作用筛选揭示肝脏对癌症转移的限制

癌症转移&#xff0c;作为导致患者死亡的主要原因之一&#xff0c;其复杂机制一直是医学界研究的热点。肝脏&#xff0c;作为癌症转移的常见靶器官&#xff0c;其微环境对癌症细胞的生长和转移具有重要影响。然而&#xff0c;肝脏如何限制癌症转移的具体机制尚不完全清楚。 来…...

STM32移植文件系统FATFS——片外SPI FLASH

一、电路连接 主控芯片选型为&#xff1a;STM32F407ZGT6&#xff0c;SPI FLASH选型为&#xff1a;W25Q256JV。 采用了两片32MB的片外SPI FLASH&#xff0c;电路如图所示。 SPI FLASH与主控芯片的连接方式如表所示。 STM32F407GT6W25Q256JVPB3SPI1_SCKPB4SPI1_MISOPB5SPI1_MOSI…...

2025年第十六届蓝桥杯省赛JavaB组真题回顾

第16届蓝桥杯省赛已经结束了&#xff0c;第一次参加也是坐牢了4个小时&#xff0c;现在还是来总结一下吧&#xff08;先声明以下的解法&#xff0c;大家可以当作一种思路来看&#xff0c;解法不一定是正解&#xff0c;只是给大家提供一种能够正常想到的思路吧&#xff09; 试题…...

Android 接口定义语言 (AIDL)

目录 1. 本地进程调用(同一进程内)2. 远程进程调用(跨进程)3 `oneway` 关键字用于修改远程调用的行为Android 接口定义语言 (AIDL) 与其他 IDL 类似: 你可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。 在 Android 上,一个进…...

c# 数据结构 链表篇 有关双向链表的一切

本人能力有限,如有不足还请斧正 目录 0.双向链表的好处 1.双向链表的分类 2.不带头节点的标准双向链表 节点类:有头有尾 链表类:也可以有头有尾 也可以只有头 增 头插 尾插 删 查 改 遍历 全部代码 3.循环双向链表 节点类 链表类 增 头插 尾插 删 查 遍历…...

Debian服务器挂载外部存储设备的完整指南

在 Debian 系统中挂载外部存储设备(如 U 盘、移动硬盘、SSD)是服务器运维和桌面使用中非常常见的操作。本文将为你详细拆解从识别设备、格式化到手动/自动挂载的全过程&#xff0c;适合新手到进阶用户参考。 一、准备阶段&#xff1a;插入存储设备并识别 1. 插入外部设备后查看…...

660 中值定理

文章目录 前言168169170总结 前言 background music: 《代替》 张叶蕾 660 上面没有专门的中值定理章节&#xff0c;我蒙了。不过应该可以找一下。就是证明题&#xff0c;标志性应该还行。找一下。然后做一下。660 的题质量应该还是非常高的。但是积分中值定理&#xff0c;还有…...

Elasticsearch:AI 助理 - 从通才到专才

作者&#xff1a;来自 Elastic Thorben Jndling 在 AI 世界中&#xff0c;关于构建针对特定领域定制的大型语言模型&#xff08;large language models - LLM&#xff09;的话题备受关注 —— 不论是为了更好的安全性、上下文理解、专业能力&#xff0c;还是更高的准确率。这个…...

数据结构——布隆过滤器

目录 一、什么是布隆过滤器&#xff1f; 二、布隆过滤器的原理 三、布隆过滤器的特点 一、什么是布隆过滤器&#xff1f; 布隆过滤器是一种空间效率高、适合快速检索的数据结构&#xff0c;用于判断一个元素是否可能存在于一个集合中。它通过使用多个哈希函数和一个位数组来…...

时间的重构:科技如何重塑人类的时间感知与存在方式

时间是人类认知的基石&#xff0c;也是科技发展的终极命题。从石英钟到量子计时器&#xff0c;从日晷到区块链时间戳&#xff0c;技术不断重构着我们对时间的理解与利用。然而&#xff0c;当人工智能、量子计算和脑机接口等前沿技术开始挑战时间的线性本质时&#xff0c;我们不…...

悄悄话识别、 打电话识别、攀高识别三种识别算法

在摄像头正对场景下,悄悄话识别(唇语识别)、打电话识别和攀高识别是三种典型的行为检测技术。以下从技术原理、算法模型、应用场景及挑战等方面进行详细分析: 一、悄悄话识别(唇语识别) 技术原理 唇语识别通过分析嘴唇的几何特征(形状、开合程度、运动轨迹)和动态变化…...

docker多架构镜像构建

docker多架构镜像构建 Docker 多架构镜像构建&#xff08;Multi-Architecture Image Build&#xff09;允许你为不同平台&#xff08;如 linux/amd64, linux/arm64, linux/arm/v7 等&#xff09;构建和推送统一的镜像标签&#xff0c;解决在不同硬件架构之间部署的问题。 Doc…...

机器学习常用算法总结

1. 概述 机器学习的定义是对于某类任务T和性能度量P&#xff0c;如果一个计算机程序在T上其性能P随着经验E而自我完善&#xff0c;那么我们就称这个系统从经验E中学习&#xff0c;机器学习是人工智能的一种方法&#xff0c;它通过在大量数据中学习隐藏的规则&#xff0c;模式和…...

软件架构设计:MVC、MVP、MVVM、RIA 四大风格优劣剖析

MVC、MVP、MVVM 和 RIA 都是软件架构中常见的设计风格&#xff0c;以下是对它们的详细介绍&#xff1a; 一、MVC 架构风格&#xff08;Model - View - Controller&#xff09; 1.简介&#xff1a;MVC 架构风格将软件应用程序分为三个核心部分&#xff0c;通过这种划分来分离不…...

Android12 自定义系统服务

在Android中可以通过两种方式创建系统服务: 由SystemServer启动的系统服务,使用SystemServer线程资源,适合轻量级的服务,比如各种XMS服务;占用独立进程,在系统启动时,由init进程拉起,比如SurfaceFlinger;本文采用的是第一种方式。 自定义AssistantManagerService 参…...

Flink SQL SavePoint最佳实践

以下是 Flink SQL Savepoint 最佳实践&#xff0c;涵盖配置、触发、恢复及注意事项&#xff0c;高效管理作业状态&#xff1a; 一、Savepoint 的配置与触发 1. 基础配置 存储路径&#xff1a;在 flink-conf.yaml 中全局设置 Savepoint 存储目录&#xff0c;避免每次手动指定路…...

【STM32】在FreeRTOS下使用硬件SPI收发数据出现的时序耦合问题(WK2124芯片为例)

问题 STM32中在Freertos使用SPI通讯芯片 WK2124进行SPI转4串口时&#xff0c;接收数据为一个任务&#xff0c;发送数据为一个任务&#xff0c;切接受任务优先级更高实测发现收发一段时间&#xff08;约几分钟&#xff09;外扩芯片会死锁导致WK2124复位。 分析 首先&#xff…...

关于香橙派OrangePi 5 Ultra 这个开源板子,开发Android

我下载了它资料中的开源Android13 系统SDK&#xff0c; 这个SDK连个git 都没有&#xff0c;把这种代码释放能称为开源吗&#xff1f;&#xff1f; 并且也就是说你买了这个板子&#xff0c;里面是没有任何关于RK3588的开发文档&#xff0c;如果你没玩过其他RK平台&#xff0c;估…...

ubuntu启动 Google Chrome 时默认使用中文界面,设置一个永久的启动方式

方法 &#xff1a;通过桌面快捷方式设置 编辑 Chrome 的桌面快捷方式&#xff1a; 找到您的 Google Chrome 快捷方式文件。如果是通过菜单启动&#xff0c;通常会在以下路径找到与 Chrome 相关的 .desktop 文件&#xff1a; sudo vim /usr/share/applications/google-chrome.d…...

字节跳动开源 Godel-Rescheduler:适用于云原生系统的全局最优重调度框架

背景 在云原生调度中&#xff0c;一次调度往往无法解决所有问题&#xff0c;需要配合重调度来优化资源分配和任务摆放。传统的重调度框架主要集中在识别异常节点或任务&#xff0c;并通过迁移或删除来解决。然而&#xff0c;这些框架往往只能解决局部问题&#xff0c;无法提供…...

WINUI——Background小结

在 ​​WinUI/UWP XAML​​ 中&#xff0c;Background&#xff08;或其他颜色属性&#xff09;支持 ​​多种颜色表示方式​​&#xff0c;包括以下三种主流格式&#xff1a; ​​1. RGB 十六进制&#xff08;不透明&#xff09;​​ ​​格式​​&#xff1a;#RRGGBB​​特点…...

Oracle数据库数据编程SQL<01. 课外关注:数据库查重方法全面详解>

查重是数据库管理和数据分析中的常见需求&#xff0c;以下是各种查重方法的全面总结&#xff0c;涵盖不同场景和技术手段。 更多Oracle学习内容请查看&#xff1a;Oracle保姆级超详细系列教程_Tyler先森的博客-CSDN博客 目录 一、基础SQL查重方法 1. 使用GROUP BY和HAVING …...

开源技术如何助力中小企业实现财务管理自主化?

中小企业的数字化困境与开源机遇 国际数据公司&#xff08;IDC&#xff09;研究显示&#xff0c;全球67%的中小企业因高昂的软件成本和僵化的功能设计&#xff0c;未能有效推进数字化转型。传统商业软件常面临三大矛盾&#xff1a; 功能冗余与核心需求缺失&#xff1a;标准化系…...

边缘计算与隐私计算的融合:构建数据经济的“隐形护盾“

在数据成为核心生产要素的今天&#xff0c;边缘计算与隐私计算的交汇正在重塑技术生态。这并非简单的技术叠加&#xff0c;而是一场关于数据主权、算力分配与信任机制的深度博弈。本文将从"数据流动的拓扑学"视角&#xff0c;探讨二者融合如何重构数字社会的基础设施…...

【大模型实战篇】--阿里云百炼搭建MCP Agent

MCP协议&#xff08;Model Communication Protocol&#xff0c;模型通信协议&#xff09;是大语言模型&#xff08;LLM&#xff09;与外部系统或其他模型交互时的一种标准化通信框架&#xff0c;旨在提升交互效率、安全性和可扩展性。 目录 1.阿里云百炼--MCP 1.1.MCP 服务接…...

基于PySide6与pycatia的CATIA智能倒角工具开发全解析

引言&#xff1a;工业设计中的倒角革命 在机械设计领域&#xff0c;倒角操作是零件加工前的必要工序。传统手动操作效率低下且易出错本文基于PySide6pycatia技术栈&#xff0c;提出一种支持批量智能倒角、参数动态校验、跨层级操作的自动化方案&#xff0c;其核心突破体现在&a…...

css 二维码始终显示在按钮的正下方,并且根据不同的屏幕分辨率自动调整位置

一、需求 “求职入口” 下面的浮窗位置在其正下方&#xff0c;并且浏览器分辨的改变&#xff08;拖动浏览器&#xff09;&#xff0c;位置依旧在最下方 二、实现 <div class"btn_box"><div class"btn_link id"js-apply">求职入口<di…...

串口接收的使用-中断

1、引言 单片机串口、按键等等这种外部输入的&#xff0c; 用轮询的方式非常浪费资源&#xff0c;所以最好的方法就是使用中断接收数据。 2、串口 对于串口中断&#xff0c; 使用的非常频繁。 1. 基本原理 串口中断接收通过以下方式工作&#xff1a; 当串口接收到一个字节…...

处理 Flutter 没有反应

现象 有以下几种 VS Code 中 Initializing the Flutter SDK. This may take a few minutes. 会一直维持在这个右下角提示窗, 但是无后续动作 Flutter CMD flutter_console.bat 执行 --version 或者 doctor [-v] 没有任何输出, 命令卡住 解决办法 参考官方说明 管理员身份…...