Linux第67步_linux字符设备驱动_注册和注销
1、字符设备注册与注销的函数原型”
/*字符设备注册的函数原型*/
static inline int register_chrdev(unsigned int major,\
const char *name, \
const struct file_operations *fops)
/*
major:主设备号,Limnux下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分。
name:设备名字,指向一串字符串。
fops:结构体file_operations类型指针,指向设备的操作函数集合变量。
*/
/*字符设备注销的函数原型*/
static inline void unregister_chrdev(unsigned int major,\
const char *name)
/*
major:主设备号,Limnux下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分。
name:设备名字,指向一串字符串。
*/
2、Linux设备号
1)、使用设备号的原因:为了方便管理,Linux 中每个设备都有一个设备号。
2)、设备号的组成
Linux设备号是由主设备号和次设备号两部分组成;
主设备号表示某一个具体的驱动;
次设备号表示使用这个驱动的各个设备。
Linux使用“dev_t的数据类型”表示设备号;
“dev_t的数据类型”定义在文件“include/linux/types.h”里面,定义如下:
typedef __u32 __kernel_dev_t;
//为“__u32”起个别名叫“__kernel_dev_t”
typedef __kernel_dev_t dev_t;
//为“__kernel_dev_t”起个别名叫“dev_t”
由此可见,dev_t是 umsigned int类型,是一个32位的数据类型。这个32位的数据就是Linux设备号,它是由“主设备号”和“次设备号”两部分构成,其中高12位为“主设备号”,低20位为“次设备号”。因此,Linux系统中主设备号范围为0~4095,所以,大家在选择主设备号的时候一定不要超过这个范围。
在文件“include/linux/kdev_t.h”中提供了几个关于设备号的操作函数(本质是宏),如下所示:
#define MINORBITS 20 //定义“次设备号”占据低20位
#define MINORMASK ((1U << MINORBITS) - 1) //定义“次设备号”的掩码值
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
//输入参数dev为“Linux设备号”
//将dev右移20位得到“主设备号”
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//输入参数dev为“Linux设备号”
//将dev与0xFFFFF相与后得到“次设备号”
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//输入参数ma为“主设备号”
//输入参数mi为“次设备号”
//将ma左移20位,再与mi相或,就得到“Linux设备号”
设备号的申请函数
int alloc_chrdev_region(dev_t *dev,\
unsigned baseminor,\
unsigned count,\
const char *name)
//dev:保存申请到的设备号
//baseminor:次设备号的起始地址
//alloc_chrdev_region可以申请一段连续的多个“设备号”,这些“设备号”的“主设备号”是一样的,但是“次设备号”不同。“次设备号”以baseminor为起始值开始递增。通常baseminor的值为0。
//count:要申请的设备号数量;
// name:表示“设备名字”
设备号的释放函数
void unregister_chrdev_region(dev_t from, unsigned count)
// from:要释放的设备号;
// count:表示从from开始,要释放的设备号数量。
3、字符设备加载,注册,注销和卸载的一般模板
#define xxx_MAJOR 200
//定义主设备号
//静态分配设备号:在串口输入“cat/proc/devices”查询当前已用的主设备号
//然后使用一个“没有被使用的设备号”作为该设备的的主设备号
#define xxx_NAME "xxxName" //定义设备的名字。
const struct file_operations xxx_fops = {};
//声明一个file_operations结构变量
/*驱动入口函数 */
static int __init xxx_init(void)
{
int ret;
ret = register_chrdev(xxx_MAJOR, xxx_NAME, &xxx_fops);
//注册字符设备驱动
//xxx_MAJOR为主设备号,采用宏xxx_NAME定义设备名字
//xxx_fops是设备的操作函数集合,它是file_operations结构变量
if (ret < 0) {
pr_err("xxx_init is failed!!!\r\n");
return ret;
}
else pr_err("xxx_init is ok!!!\r\n");
return 0;
}
/*驱动出口函数 */
static void __exit xxx_exit(void)
{
/*出口函数具体内容 */
unregister_chrdev(xxx_MAJOR, xxx_NAME);
//注销字符设备驱动
//xxx_MAJOR为主设备号,采用宏xxx_NAME定义设备名字
}
module_init(xxx_init); //声明xxx_init()为驱动入口函数
module_exit(xxx_exit); //声明xxx_exit()为驱动出口函数
4、查看“linux-5.4.31”中的设备注册和注销
打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,见下图:

点击“确定”
点击“查看”,点击“搜索”,输入“register_chrdev”
假如我们点击的是“rtlx-cmp.c”,见下图:

“rtlx-cmp.c”程序如下:
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <asm/mips_mt.h>
#include <asm/vpe.h>
#include <asm/rtlx.h>
static int major;//major:设备号
/*从字面意思看,是一个中断*/
static void rtlx_interrupt(void)
{
int i;
struct rtlx_info *info;
struct rtlx_info **p = vpe_get_shared(aprp_cpu_index());
if (p == NULL || *p == NULL)
return;
info = *p;
if (info->ap_int_pending == 1 && smp_processor_id() == 0) {
for (i = 0; i < RTLX_CHANNELS; i++) {
wake_up(&channel_wqs[i].lx_queue);
wake_up(&channel_wqs[i].rt_queue);
}
info->ap_int_pending = 0;
}
}
/*从字面看是讲中断堆栈指针*/
void _interrupt_sp(void)
{
smp_send_reschedule(aprp_cpu_index());
}
/*入口函数初始化*/
int __init rtlx_module_init(void)
{
struct device *dev;
int i, err;
if (!cpu_has_mipsmt) {
pr_warn("VPE loader: not a MIPS MT capable processor\n");
return -ENODEV;
}
if (num_possible_cpus() - aprp_cpu_index() < 1) {
pr_warn("No TCs reserved for AP/SP, not initializing RTLX.\n"
"Pass maxcpus=<n> argument as kernel argument\n");
return -ENODEV;
}
major = register_chrdev(0, RTLX_MODULE_NAME, &rtlx_fops);
//注册字符设备驱动
//0为主设备号,采用宏RTLX_MODULE_NAME定义设备名字
//rtlx_fops是设备的操作函数集合,它是file_operations结构变量
if (major < 0) {//读取主设备号小于0,则打印错误信息
pr_err("rtlx_module_init: unable to register device\n");
return major;
}
/* initialise the wait queues */
for (i = 0; i < RTLX_CHANNELS; i++) {
init_waitqueue_head(&channel_wqs[i].rt_queue);
//初始化等待队列头
init_waitqueue_head(&channel_wqs[i].lx_queue);
//初始化等待队列头
atomic_set(&channel_wqs[i].in_open, 0);//状态重置
mutex_init(&channel_wqs[i].mutex);//初始化互斥体
dev = device_create(mt_class, NULL, MKDEV(major, i), NULL,
"%s%d", RTLX_MODULE_NAME, i);
//创建设备, major为主设备号,i为次设备号
//参数mt_class表示该设备位于mt_class类下面
// parent为NULL表示没有父设备
//将major左移20位,再与i相或,就得到“Linux设备号”
//drvdata为NULL表示没有使用设备文件
//采用RTLX_MODULE_NAME宏定义指向字符串表示设备名
if (IS_ERR(dev)) {
while (i--)
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是设备所处的类,参数i是设备号
err = PTR_ERR(dev);
goto out_chrdev;
}
}
/* set up notifiers */
rtlx_notify.start = rtlx_starting;
rtlx_notify.stop = rtlx_stopping;
vpe_notify(aprp_cpu_index(), &rtlx_notify);
if (cpu_has_vint) {
aprp_hook = rtlx_interrupt;
} else {
pr_err("APRP RTLX init on non-vectored-interrupt processor\n");
err = -ENODEV;
goto out_class;
}
return 0;
out_class:
for (i = 0; i < RTLX_CHANNELS; i++)
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是要删除的设备所处的类,参数i是要删除的设备号
out_chrdev:
unregister_chrdev(major, RTLX_MODULE_NAME);
//注销字符设备驱动
//major为主设备号,采用宏RTLX_MODULE_NAME定义设备
return err;
}
/*出口函数初始化*/
void __exit rtlx_module_exit(void)
{
int i;
for (i = 0; i < RTLX_CHANNELS; i++)
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是要删除的设备所处的类,参数i是要删除的设备号
unregister_chrdev(major, RTLX_MODULE_NAME);
//注销字符设备驱动
//major为主设备号,采用宏RTLX_MODULE_NAME定义设备名字
aprp_hook = NULL;
}
5、编写“字符设备驱”动注册与注销的程序
1)、创建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“cd /home/zgq/linux/Linux_Drivers/”
切换到“/home/zgq/linux/Linux_Drivers/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹
输入“mkdir 01_MyCharDevice”
创建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹
输入“cd 01_MyCharDevice/”
切换到“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“pwd”获取绝对路径
输入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/”
切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹
输入“cp Makefile /home/zgq/linux/Linux_Drivers/01_MyCharDevice回车”
拷贝“Makefile”

2)、修改Makefile文件
Makefile文件内容如下:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
obj-m := MyCharDevice.o
#生成“obj-m”需要依赖“MyCharDevice.o”
build: kernel_modules
#生成“build”需要依赖“kernel_modules”
@echo $(KERNELDIR)
#输出KERNELDIR的值为“/home/zgq/linux/atk-mp1/linux/linux-5.4.31”
@echo $(CURRENT_PATH)
#输出CURRENT_PATH的值为/home/zgq/linux/Linux_Drivers/00_My_TestDriver”
@echo $(MAKE)
#输出MAKE的值为make
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
#后面的"modules"表示编译成模块
#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”
#“CURRENT_PATH”上面定义为“当前的工作目录”
#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中
#即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。
#M表示模块源码目录
#在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”
#“CURRENT_PATH”上面定义为“当前的工作目录”
3)、使用VSCode创建“MyCharDevice.c”文件
添加内容如下:
#include <linux/init.h> //必须要包含的头文件
#include <linux/module.h> //必须要包含的头文件
#include <linux/string.h> //下面要用到字符串,显然也要包含
#include <linux/kernel.h> //必须要包含的头文件
#include <linux/device.h> //必须要包含的头文件
#include <linux/fs.h> //使能结构体"file_operations"
#define MyCharDevice_MAJOR 200
//定义主设备号
//可以通过串口输入“cat /proc/devices”查询当前已用的主设备号
#define MyCharDevice_NAME "MyCharDeviceName"
//使用MyCharDevice_NAME指向一串字符串表示设备的名字。
const struct file_operations MyCharDevice_fops = {};
//声明file_operations结构变量MyCharDevice_fops
//它是指向设备的操作函数集合变量
/*入口函数初始化*/
static int __init MyCharDevice_init(void)
{
int ret = 0;
printk("MyCharDevice_init\r\n");
ret = register_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME, &MyCharDevice_fops);
//返回的ret=0表示字符设备驱动注册成功
//主设备号为MyCharDevice_MAJOR
//设备名字为RTLX_MODULE_NAME宏定义
//MyCharDevice_fops是设备的操作函数集合
if (ret < 0) {
pr_err("MyCharDevice_init is failed!!!\r\n");
return ret;
}
else printk("ret=%d\r\n",ret);
return ret;
}
/*出口函数初始化*/
static void __exit MyCharDevice_exit(void)
{
printk("MyCharDevice_exit\r\n");
unregister_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME);
//主设备号为MyCharDevice_MAJOR的值
//设备名字为MyCharDevice_NAME宏定义的字符串“MyCharDeviceName”
}
module_init(MyCharDevice_init);
/*将MyCharDevice_init()指定为入口函数*/
module_exit(MyCharDevice_exit);
/*将MyCharDevice_exit()指定为出口函数*/
MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_DESCRIPTION("This is test module!");//模块介绍
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”
4)、编译
输入“cd /home/zgq/linux/Linux_Drivers/01_MyCharDevice/”
输入“ls”
输入“sudo cp MyCharDevice.ko /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入密码“123456回车”
输入“cd /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入“ls”

5)、测试
启动开发板,从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/”
在nfs挂载中,切换到“/lib/modules/5.4.31/”目录,
注意:“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中。
输入“ls”
输入“depmod”,驱动在第一次执行时,需要运行“depmod”
输入“modprobe MyCharDevice.ko”,加载“MyCharDevice.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“rmmod MyCharDevice.ko”,卸载“MyCharDevice.ko”模块
注意:输入“rmmod MyCharDevice”也可以卸载“MyCharDevice.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“modprobe MyCharDevice.ko”,加载“MyCharDevice.ko”模块
输入“cat /proc/devices回车”查询设备号

相关文章:
Linux第67步_linux字符设备驱动_注册和注销
1、字符设备注册与注销的函数原型” /*字符设备注册的函数原型*/ static inline int register_chrdev(unsigned int major,\ const char *name, \ const struct file_operations *fops) /* major:主设备号,Limnux下每个设备都有一个设备号,设备号分…...
设计模式:软件工程的艺术
引言 设计模式是软件工程中一种解决常见问题的经验总结,是一套可复用的设计思想。它们提供了在特定情境下的解决方案,有助于构建可维护、灵活、可复用、可扩展的软件系统。设计模式是对软件设计中通用问题的抽象,提供了一种共享的语言和思维…...
试题 算法训练 数的潜能
资源限制 内存限制:256.0MB C/C时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述 将一个数N分为多个正整数之和,即Na1a2a3…ak,定义Ma1*a2*a3*…*ak为N的潜能。 给定N,…...
OpenAI Triton 入门教程
文章目录 Triton 简介背景Triton 与 CUDA 的关系 Triton 开发样例样例一:Triton vector addition 算子Triton kernel 实现kernel 函数封装函数调用性能测试 样例二:融合 Softmax 算子动机Triton kernel 实现kernel 封装单元测试性能测试 样例三ÿ…...
【flask+python】利用魔术方法,更优雅的封装model类
定义model # Time :2024-2024/2/27-14:49 # Email :514422868qq.com # Author :Justin # file :user.py # Software :01-fishbook from app.model.base import Base from sqlalchemy import Column, Integer, SmallInteger, String from werkzeug.security …...
Qt程序设计-报警灯自定义控件实例
本文讲解Qt报警灯自定义控件实例。 实现功能 设置边框和内部颜色。 设置是否闪烁点亮。 添加的报警灯类 #ifndef LIGHT_H #define LIGHT_H#include <QWidget> #include <QDebug> #include <QPainter> #include <QTimer>class Light : public QWid…...
Linux之定时任务02
一、什么是crond Linux 中 crond 就是定时任务,即根据 crond 指定的时间,由系统按指定的时间,周期性,自动触发的事件。 crond 服务在默认的情况下会每分钟检查系统中是否有定时任务,如果有且符合触发条件,…...
PHP堆栈+errLog定位
调用堆栈(Call Stack)是一个记录了程序在运行时所有活动子例程的栈结构。它以函数调用的方式描述了程序的执行流程和调用关系。 在PHP中,我们可以通过打印PHP调用堆栈来辅助调试和定位代码中的问题。本文将介绍如何在PHP中打印调用堆栈&…...
【大数据】Flink SQL 语法篇(七):Lookup Join、Array Expansion、Table Function
《Flink SQL 语法篇》系列,共包含以下 10 篇文章: Flink SQL 语法篇(一):CREATEFlink SQL 语法篇(二):WITH、SELECT & WHERE、SELECT DISTINCTFlink SQL 语法篇(三&…...
【云原生】Spring Cloud Gateway的底层原理与实践方法探究
🎉🎉欢迎光临🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟特别推荐给大家我的最新专栏《Spring 狂野之旅:从入门到入魔》 🚀 本…...
springboot 实现本地文件存储
springboot 实现本地文件存储 实现过程 上传文件保存文件(本地磁盘)返回文件HTTP访问服务器路径给前端,进行效果展示 存储 服务端接收上传的目的是提供文件的访问服务,对于SpringBoot而言,其对静态资源访问提供了很…...
Python进阶学习:Pandas--查看DataFrame中每一列的数据类型
Python进阶学习:Pandas–查看DataFrame中每一列的数据类型 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程👈 希…...
Groovy - 大数据共享搜索配置
数据共享搜索列中配置了搜索列,相应的数据共享接口中也需要支持根据配置的字段搜索,配置实体时,支持搜索的入参code必须是searchKeys,且接口应该是需要支持分页(入参必须是 current、pageSize)的。current …...
第三节:Vben Admin登录对接后端login接口
系列文章目录 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 第五节:Vben Admin权限-前端控制方式 文章目录 系列文章目录前言一、Flask项目介绍…...
关于CSS 优先级布局应用的教程
在前端开发中,CSS 的优先级布局是非常重要的一部分。通过合理地应用 CSS 优先级,我们可以更加灵活地控制页面的布局和样式。本教程将向您介绍如何利用 CSS 优先级进行布局,并通过实例展示其应用。 1. 了解 CSS 优先级 在 CSS 样式表中&…...
vue2+elementui上传照片(el-upload 超简单)
文章目录 element上传附件(el-upload 超详细)代码展示html代码data中methods中接口写法 总结 element上传附件(el-upload 超详细) 这个功能其实比较常见的功能,后台管理系统基本上都有,这就离不开element的…...
目标检测新SOTA:YOLOv9问世,新架构让传统卷积重焕生机(附代码)
在目标检测领域,YOLOv9 实现了一代更比一代强,利用新架构和方法让传统卷积在参数利用率方面胜过了深度卷积。 继 2023 年 1 月 YOLOv8 正式发布一年多以后,YOLOv9 终于来了! 我们知道,YOLO 是一种基于图像全局信息进行预测的目标检测系统。自 2015 年 Joseph Redmon、Al…...
Javascript:输入输出
目录 一.前言 二.正文 1.输出 2.输入 3.字面量 概念: 三.结语 一.前言 Javascript作为运行浏览器的语言,对于学习前端的同学来说十分重要,那么从现在开始我们将开始介绍有关 Javascript。 二.正文 1.输出 document.write() : 向body内…...
Windows系统安装TortoiseSVN并结合内网穿透实现远程访问本地服务器——“cpolar内网穿透”
文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统,它与Apache Subversion(SVN)集成在一起,提供了一个用户友好的界面,方便用…...
HarmonyOS 开发之———应用程序入口—UIAbility的使用
谢谢关注!! 前言:上一篇文章主要介绍ArkJS 基础—〉自定义组件使用。如需了解谢谢查阅:http://t.csdnimg.cn/01PQ2 一、UIAbility概述 UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面。 …...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
