4. 字符设备驱动高级--- 下篇
文章目录
- 一、字符设备驱动高级
- 1.1 注册字符设备驱动新接口
- 1.1.1 新接口与旧接口
- 1.1.2 cdev介绍
- 1.1.3 设备号
- 1.1.4 编程实践
- 1.1.5 alloc_chrdev_region自动分配设备号
- 1.1.6 中途出错的倒影式错误处理方法
- 二、字符设备驱动注册代码分析
- 2.1 旧接口register_chrdev
- 2.2 新接口register_chrdev_region & alloc_chrdev_region
- 2.3 注销
- 三、自动创建字符设备驱动的设备文件
- 3.1 解决方案:udev(嵌入式中用的是mdev)
- 3.2 内核驱动设备类相关函数
- 四、设备类相关代码分析
- 4.1 sysfs文件系统
- 五、静态映射表建立过程分析
- 5.1 建立映射表的三个关键部分
- 六、动态映射结构体方式操作寄存器
- 七、内核提供的读写寄存器接口
一、字符设备驱动高级
1.1 注册字符设备驱动新接口
1.1.1 新接口与旧接口
- 旧接口:register_chrdev
- 新接口:register_chrdev_region(注册设备号)/alloc_chrdev_region(分配设备号) + cdev
1.1.2 cdev介绍
(1) 结构体
struct cdev {struct kobject kobj; // 内嵌的内核对象,每个 cdev 都是一个 kobjectstruct module *owner; // 指向实现驱动的模块const struct file_operations *ops; // 操纵这个字符设备文件的方法struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头,用来将已经向内核注册的所有字符设备形成链表dev_t dev; // 字符设备的设备号,由主设备号和次设备号构成unsigned int count; // 隶属于同一主设备号的次设备号的个数.
};
(2) 模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号;
(3) 通过 cdev_init( ) 建立cdev与 file_operations之间的连接,通过 cdev_add( ) 向系统添加一个cdev以完成注册;
(4) 模块卸载函数通过cdev_del( )来注销cdev,通过 unregister_chrdev_region( )来释放设备号;
1.1.3 设备号
(1) 设备号 = 主设备号 + 次设备号
(2) dev_t类型
(3) MKDEV(MAJOR, MINOR);
说明: 获取设备在设备表中的位置。
MAJOR 主设备号
MINOR 次设备号
1.1.4 编程实践
(1) 使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册
全局变量:#define MYMAJOR 200#define MYCNT 1#define MYNAME "testchar"static dev_t mydev;static struct cdev test_cdev;注册驱动:// 新的接口注册字符设备驱动需要2步// 第1步:静态注册/分配主次设备号int retval;mydev = MKDEV(MYMAJOR, 0);retval = register_chrdev_region(mydev, MYCNT, MYNAME);if (retval){printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);return -EINVAL;}printk(KERN_INFO "register_chrdev_region success\n");// 第2步:注册字符设备驱动cdev_init(&test_cdev, &test_fops);retval = cdev_add(&test_cdev, mydev, MYCNT);if (retval) {printk(KERN_ERR "Unable to cdev_add\n");return -EINVAL;}printk(KERN_INFO "cdev_add success\n");注销驱动:
// 使用新的接口来注销字符设备驱动// 注销分2步:// 第一步真正注销字符设备驱动用cdev_delcdev_del(&test_cdev);// 第二步去注销申请的主次设备号unregister_chrdev_region(mydev, MYCNT);
1.1.5 alloc_chrdev_region自动分配设备号
(1) register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2) 更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3) 自动分配的设备号,必须去知道它的主次设备号,否则后面没法去mknod创建他对应的设备文件。
(4) 使用MAJOR宏和MINOR宏从dev_t得到major和minor
(5) 反过来使用MKDEV宏从major和minor得到dev_t。
(6) 使用这些宏的代码具有可移植性
#define MYCNT 1#define MYNAME "testchar"static dev_t mydev;static struct cdev test_cdev;//自动分配主次设备号retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);if (retval < 0) {printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);goto flag1;}printk(KERN_INFO "alloc_chrdev_region success\n");printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev)); //获取我们的主次设备号,用于创建设备文件
1.1.6 中途出错的倒影式错误处理方法
(1) 内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后,后面的步骤就没有进行下去的必要性了。所以就有了倒影式处理错误的方法。
// 第1步:分配主次设备号retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);if (retval < 0) {printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);goto flag1;}// 第2步:注册字符设备驱动cdev_init(&test_cdev, &test_fops);retval = cdev_add(&test_cdev, mydev, MYCNT);if (retval) {printk(KERN_ERR "Unable to cdev_add\n");goto flag2;}// 第3步:使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))goto flag3;// 如果第3步才出错跳转到这里来
flag3:release_mem_region(GPJ0CON_PA, 4);// 如果第2步才出错跳转到这里来
flag2:cdev_del(&test_cdev);// 如果第1步才出错跳转到这里来
flag1:// 在这里把第1步做成功的东西给注销掉unregister_chrdev_region(mydev, MYCNT);
| 注:使用cdev_alloc,cdev_init的替代(重点) |

二、字符设备驱动注册代码分析
2.1 旧接口register_chrdev
register_chrdev注册函数
=》 __register_chrdev -- 内核级函数==》 __register_chrdev_region -- 内核级函数==》cdev_alloc -- 让内核为这个结构体分配内存的。===》cdev_add -- 向内核里面添加一个驱动,注册驱动。
函数 __register_chrdev_region() 主要执行以下步骤:
- 分配一个新的 char_device_struct 结构,并用 0 填充。
- 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
- 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
- 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
- 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。
2.2 新接口register_chrdev_region & alloc_chrdev_region
register_chrdev_region== 动态分配主次设备号
=》__register_chrdev_region
register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
alloc_chrdev_region==让内核自动给我们分配设备号
=》__register_chrdev_region
alloc_chrdev_region() 函数用于动态申请设备编号范围,通过指针参数返回实际获得的起始设备编号。
2.3 注销
注销和注册分配字符设备编号范围类似,内核提供了两个注销字符设备编号范围的函数,分别是 unregister_chrdev_region() 和 unregister_chrdev() 。它们都调用__unregister_chrdev_region函数,这个就不分析了。
三、自动创建字符设备驱动的设备文件
(1) 整体流程回顾
(2) 使用mknod创建设备文件的缺点
(3) 能否自动生成和删除设备文件
3.1 解决方案:udev(嵌入式中用的是mdev)
(1) 什么是udev?应用层的一个应用程序
(2) 内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3) 应用层启用udev,内核驱动中使用相应接口
(4) 驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除

3.2 内核驱动设备类相关函数
- class_create/class_destroy
- device_create/device_destroy
#include <linux/device.h> //相关的函数包含在这个头文件里面static dev_t mydev;
static struct class *test_class;// 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息// 给udev,让udev自动创建和删除设备文件test_class = class_create(THIS_MODULE, "aston_class");if (IS_ERR(test_class))return -EINVAL;// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字// 所以我们这里要的文件名是/dev/test111device_create(test_class, NULL, mydev, NULL, "test111");//在注销设备驱动之前device_destroy(test_class, mydev);class_destroy(test_class);

四、设备类相关代码分析
4.1 sysfs文件系统
因为udev需要sysfs文件系统的支持(sysfs文件系统只在linux-2.6内核以上才有),所以它存在于Linux-2.6版本之后的内核。udev借助于netlink协议在内核驱动和应用层之间传递信息。当内核中的驱动完成注册和注销时,信息会被传送给应用层的udev,udev便会自动地完成设备文件的创建和删除。
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类, 内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

2、class_creat & device_createt
(1) class_creat 树形调用的主要的函数
class_create();
__class_create();
__class_register();
kset_register();
kobject_uevent();
(2) device_createt 树形调用的主要的函数
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) ------------------- 参数”...”就是可变参数, 更多的时候表示了次设备号。
{
.........
}
例如:此图中 tty 就表示主设备号, 后面的14… 15等表示次设备号

device_createt();device_create_vargs();kobject_set_name_vargs();device_register();device_add();kobject_add(); //真正的把设备添加进去了device_create_file();//下面这些函数都是操作 sysfs 的函数device_create_sys_dev_entry();devtmpfs_create_node();device_add_class_symlinks();device_add_attrs();device_pm_add();kobject_uevent();
device_create_file() 函数创建的就是 dev 目录(sysfs函数实现的),
int device_create_file(struct device *dev,const struct device_attribute *attr)
{int error = 0;if (dev) {....error = sysfs_create_file(&dev->kobj, &attr->attr);}return error;
}
EXPORT_SYMBOL_GPL(device_create_file);
参数 uevent_attr
static struct device_attribute uevent_attr =__ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent); -------- 给出的属性, 参数中 show_uevent, store_uevent 表示读和存储。
所以在执行cat dev 读取 dev 这个文件时, 内核调用的就是 show_uevent 函数。
五、静态映射表建立过程分析
5.1 建立映射表的三个关键部分
(1) 主映射表:映射表具体物理地址和虚拟地址的值相关的宏定义

(2) 映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io();s5p_init_io();iotable_init(); // 引出io 描述符的概念
经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
注:该部分重点记录分析方法。

// 由上图可以看出,内存管理最小的表是4K。
(3) 开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
函数调用层级:
start_kernel();setup_arch();paging_init();devicemaps_init();if (mdesc->map_io)mdesc->map_io();
六、动态映射结构体方式操作寄存器
知识回顾:之前的动态映射,每个寄存器地址是单独映射的,要进行多次。
(1) 仿效真实驱动中,用结构体封装的方式来进行单次多寄存器的地址映射。
实验代码:
typedef struct GPJ0REG
{volatile unsigned int gpj0con;volatile unsigned int gpj0dat;
} gpj0_reg_t;#define GPJ0_REGBASE 0xe0200240 //物理地址
gpj0_reg_t *pGPJ0REG;// 使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "GPJ0REG"))return -EINVAL;pGPJ0REG = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t));// 映射之后用指向结构体的指针来进行操作// 指针使用->结构体内元素的方式来操作各个寄存器pGPJ0REG->gpj0con = 0x11111111;pGPJ0REG->gpj0dat = ((0<<3) | (0<<4) | (0<<5)); // 亮// 解除映射iounmap(pGPJ0REG);release_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t));
七、内核提供的读写寄存器接口
- 内核提供的寄存器读写接口(在不同的架构下,可移植性高)
(1) writel和readl,在3.字符设备驱动上篇已经提过了。
(2) iowrite32和ioread32 - 代码实践
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244#define S5P_GPJ0REG(x) (x)
#define S5P_GPJ0CON S5P_GPJ0REG(0)
#define S5P_GPJ0DAT S5P_GPJ0REG(4)unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;static void __iomem *baseaddr; // 寄存器的虚拟地址的基地址// 使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))return -EINVAL;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))return -EINVAL;pGPJ0CON = ioremap(GPJ0CON_PA, 4);pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);/************/ 原始的用解引用指针的方法 /***********/*pGPJ0CON = 0x11111111; *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮/***********/ 使用内部读写接口的方法 /***********/测试1:用2次ioremap得到的动态映射虚拟地址来操作,测试成功
writel(0x11111111, pGPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), pGPJ0DAT);测试2:用静态映射的虚拟地址来操作,测试成功
writel(0x11111111, GPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);测试3:用1次ioremap映射多个寄存器得到虚拟地址,测试成功
if (!request_mem_region(GPJ0CON_PA, 8, "GPJ0BASE"))return -EINVAL;
baseaddr = ioremap(GPJ0CON_PA, 8);writel(0x11111111, baseaddr + S5P_GPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), baseaddr + S5P_GPJ0DAT);
- 操作寄存器的大致流程

注: 本文参考朱有鹏老师网上学员的学习记录博客,根据自己的理解,进行学习汇总,如有侵权,联系本人及时删除。
相关文章:
4. 字符设备驱动高级--- 下篇
文章目录一、字符设备驱动高级1.1 注册字符设备驱动新接口1.1.1 新接口与旧接口1.1.2 cdev介绍1.1.3 设备号1.1.4 编程实践1.1.5 alloc_chrdev_region自动分配设备号1.1.6 中途出错的倒影式错误处理方法二、字符设备驱动注册代码分析2.1 旧接口register_chrdev2.2 新接口regist…...
ChatGPT介绍以及一些使用案例
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...
PCL 点云高斯混合聚类(GMM)
文章目录 一、简介二、算法实现三、实现效果参考资料一、简介 与k均值使用原型向量来刻画聚类结构不同,高斯混合聚类(Mixture-of-Gaussian)采用了概率模型来表达聚类原型。从名字中就可以知晓,该方法将会结合高斯分布来进行聚类过程,该分布的概率密度函数定义如下所示: p (…...
Docker学习(十六)踩坑,如何将对容器的修改同步到基础镜像中
目录1.背景2.解决方法1)将容器文件进行归档2)创建一个新的 Dockerfile3)构建新的基础镜像3.注意事项4.commit命令踩坑记录1.背景 最近接手了一个docker服务,现需要对镜像进行修改,原始的 Dockerfile 已经丢失ÿ…...
食品与疾病关系预测赛题
和鲸平台数据分析实战 题目:食品与疾病关系预测算法赛道 一、赛题描述 食品与疾病关系预测算法赛道 越来越多的证据表明,食物分子与慢性疾病之间存在关联甚至治疗关系。营养成分可能直接或间接地作用于人类基因组,并调节参与疾病风险和疾病…...
Symbol
Symbol是ES6新增的一种基本数据类型 它用来表示独一无二的值, 通过Symbol函数生成 Symbol前面不能加new ,创建symbol类型指的时候传入一个参数,这个参数需要是字符串 使用Symbol函数创建一个symbol类型值,可以给它传入一个字符串参数…...
NC65 对上年度反结账,调整数据后重新结账后,对本年度年初重算时系统报错:更新记数错误。
1、对上年度反结账,调整数据后重新结账后,对本年度年初重算时系统报错:更新记数错误。 解决方案: 1、在期初余额节点,按Ctrl+ALT+A重建期初凭证; 2、到结账节点,重建余额表,选择有问题的财务核算账簿,注意:会计期间要放空; 3、到期初余额节点,将刚才删除期初数据的…...
位运算相关
文章目录一、求1的个数二、另类加法三、数组中出现一次的数字四、数组中出现一次的数字变形一、求1的个数 二进制中1的个数 法一:逐位判断 根据与&运算 n&10,说明n的最右边一位为0 n&11,说明n的最右边一位为1 所以思路就是&…...
Linux进程信号(产生、保存、处理)/可重入函数概念/volatile理解/SIGCHLD信号
首先区分一下Linux信号跟进程间通信中的信号量,它们的关系就犹如老婆跟老婆饼一样,没有一毛钱的关系。 信号的概念 信号的概念:信号是进程之间事件异步通知的一种方式,属于软中断。比如:红绿灯是一种信号,…...
锯齿数组 - 贪心
文章目录锯齿数组 -贪心(不过挺像滑动窗口的)1144. 递减元素使数组呈锯齿状锯齿数组 -贪心(不过挺像滑动窗口的) 1144. 递减元素使数组呈锯齿状 题目链接:1144. 递减元素使数组呈锯齿状 题目大意:给你一个…...
[CVPR 2022] Balanced Contrastive Learning for Long-Tailed Visual Recognition
Contents IntroductionMethodPreliminariesBalanced Contrastive Learning (BCL)Drawbacks of SCLClass-averagingClass-complementLower bound of BCLOptimization with Logit CompensationFrameworkExperimentReferencesIntroduction 作者发现对于在长尾数据集上,Supervised…...
23种设计模式-工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,而无需将具体的对象创建逻辑暴露给客户端。在Java中,工厂模式常常用于创建复杂对象或对象的构造过程涉及到多个步骤的情况。 在Android开发中,工厂模式也经常被使用&am…...
Linux操作系统学习(进程等待)
文章目录进程等待进程等待的必要性如何进程等待waiwaitpid验证进程等待 我们知道fork函数可以创建一个子进程,而子进程通常是替父进程完成一些任务,而父进程在fork之后需要通过wait/waitpid等待子进程退出。这就是进程等待 进程等待的必要性 通过获…...
Docker学习(十八)load 和 import 命令的区别
Docker 中有两个命令可以将本地文件系统中的 tar 文件导入到 Docker 中:docker load 和 docker import。尽管它们的作用类似,但它们之间有一些重要的区别。 1.使用方式的不同: docker load 的使用示例: docker load --input tes…...
mysql中的事务
在日常生活中,我们会遇到一个场景,那就是在转账的时候,A有1000块钱,要给B转账500,那么最后的结果是A有500,B有500,但是也有可能出现A没有钱了,B有1000块,或者在转账过程中卡顿,这是不符合逻辑的,那么这个时候就要使用事务来解决问题 事务就是把一堆sql语句打包成一个整体,要么…...
《C++ Primer Plus》第18章:探讨 C++ 新标准(9)
编程练习 下面是一个简短程序的一部分: int main() {using namespace std;// list of double deduced from list contentsauto q average_list ({15.4, 10.7, 9.0});cout << q << endl;// list of int deduced from list contentscout << averag…...
记录一次PWM信号异常问题
问题我使用单片机输出PWM控制机械臂,但是控制过程中,机械臂总是会出现莫名的抽动。利用示波器测试PWM信号,发现信号正常。过程(1)在反复的测试过程中,队友提出,将示波器的地线放在左侧的GND波形…...
简单了解---性能测试
目录 一、什么是性能测试 二、常见的性能测试指标 1、并发 2、响应时间 3、事务 4、点击率 5、吞吐量 6、资源利用率 三、性能测试的分类 1、一般测试 2、负载测试 3、压力测试 4、稳定性测试 四、为什么要做性能测试? 五、影响性能的因素有哪些&…...
1.机器学习笔记第一周
机器学习利用领域: 1:随着网络数据增大,需要搜集用户的数据,做喜好性偏向判断等。 2:只要有数据的,无论是医疗领域,还是基因领域都是需要机器学习来发现数据密码。 3:机器自我学习…...
若依学习(前后端分离版)——启动时发生了啥?(@PostConstruct)(mybatis log free)
我们可以发现若依启动时执行了一些sql我们可以安装一个插件mybatis log free 来更好的进行sql查看 ,安装后需要修改一下若依的日志配置如下查看日志,我们发现执行了三个方法(),分别查询了一些数据。以第二个方法为例子…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
【java面试】微服务篇
【java面试】微服务篇 一、总体框架二、Springcloud(一)Springcloud五大组件(二)服务注册和发现1、Eureka2、Nacos (三)负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...
