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

Linux驱动开发——字符设备(2)

目录

虚拟串口设备驱动

一个驱动支持多个设备

习题


虚拟串口设备驱动


        字符设备驱动除了前面搭建好代码的框架外,接下来最重要的就是要实现特定于设备的操作方法,这是驱动的核心和关键所在,是一个驱动区别于其他驱动的本质所在,是整个驱动代码中最灵活的代码所在。了解了虚拟串口设备的工作方式后,接下来就可以针对性的编写驱动程序,代码如下:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"static struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied = 0;kfifo_to_user(&vsfifo, buf, count, &copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;kfifo_from_user(&vsfifo, buf, count, &copied);return copied;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;cdev_init(&vsdev, &vser_ops);vsdev.owner = THIS_MODULE;ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);cdev_del(&vsdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        新的驱动在代码第15行定义并初始化了一个名叫 vsfifo 的 struct kfifo 对象,每个元素的数据类型为char,共有32个元素的空间。代码第17行到第25行设备打开和关闭函数,分别对应于file_operations 内的open和release 方法。因为是虚拟设备,所以这里并没有需要特别处理的操作,仅仅返回 0表示成功。这两个函数都有两个相同的形参,第一个形参是要打开或关闭文件的inode,第二个形参则是打开对应件后由内核构造并初始化好的file结构,在前面我们已经较深入地分析了这两个对象的作用。这里之所以叫release而不叫close是因为一个文件可以被打开多次,那么vser_open函数相应地会被调用多次,但是关闭文件只有到最后一个close操作才会导致vser_release函数被调用,所以用 release 更贴切。
        代码第27第34行是read系统调用驱动实现,这里主要是把FIFO中的数据返回给用户层,使用了kfifo_to_user 这个宏。read系统调用要求用户返回实际读取的字节数,而copied变量的值正好符合这一要求。代码36到第43对应的write系统调用的驱动实现,同read系统调用一样,只是数据流向相反而已。
        读和写函数引入了3个新的形参,分别是buf,count和pos,根据上面的代码,已经不难发现它们的含义。buf代表的是用户空间的内存起始地址;count表示用户想要读写多少个字节的数据:而pos是文件的位置指针,在虚拟串口这个不支持随机访问的设备中,该参数无用。_user是提醒驱动代码编写者,这个内存空间属于用户空间。
        代码第 47 行到第 50 行是将file_operations中的函数指针分别指向上面定义的函数这样在应用层发生相应的系统调用后,在驱动里面的函数就会被相应地调用。上面这个示例实现了一个功能非常简单,但是基本可用的虚拟串口驱动程序。按照下面的步骤可以进行验证。


        通过实验结果可以看到,对/dev/vser0写入什么数据,就可以从这个设备读到什么数据,和一个具备内环回功能的串口是一致的。
        为了方便读者对照查阅,特将file_operations结构类型的定义代码列出。从中我们可以看到,还有很多接口函数还没有实现,在后面的章节中,我们会陆续再实现一些接口。显然,一个驱动对下面的接口的实现越多,它对用户提供的功能就越多,但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问,那么llseek函数接口自然就不用实现。

1525 struct file_operations {
1526 struct module *owner; 
1527 loff_t (*llseek) (struct file *, loff t, int); 
1528 ssize_t (*read) (struct file *, char__user *, size t, loff t *); 
1529 ssize_t (*write) (struct file *, const char _user *, size t, loff t*); 
1530 ssize t (*alo read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1531 ssize t (*aio write) (struct kiocb *, const struct iovec *, unsigned long, loff t);

1532 int (iterite) latruct tile ', atruet dir_context *);
1533 unsigned int (*poll) (struct file *, strunt poll_table_struct *);
1534 long (unlocked ioctl) (struct file *, unsigned int, unsigned  long);

1535 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

1536 int (*mmap) (struct file *, struct vm_area_struct *);
1537 int (*open) (struct inode *, struct file *); 
1538 int (*flush) (struct file *, f1_owner_t id); 
1539 int (*release) (struct inode *, struct file *); 
1540 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1541 int (*aio_fsync) (struct kiocb *, int datasync); 
1542 int (*fasync) (int, struct file *, int); 
1543 int (*lock) (struct file *, int, struct file_lock *); 
1544 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,int);
1545 unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1546 int (*check_flags)(int); 
1547 int (*flock) (struct file *, int, struct file_lock *); 
1548 ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, lofft *, size_t, unsigned int);
1549 ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1550 int (*setlease) (struct file *, long, struct file_lock **); 
1551 long (*fallocate)(struct file *file, int mode, loff_t offset, 
1552 loff_t len); 
1553 int (*show_fdinfo)(struct seq_file *m, struct file *f); 
1554 };


一个驱动支持多个设备


        如果一类设备有多个个体(比如系统上有两个串口),那么我们就应该写一个驱动来支持这几个设备,而不是每一个设备都写一个驱动。对于多个设备所引入的变化是什么呢?首先我们应向内核注册多个设备号,其次就是在添加 cdev对象时指明该cdev对象管理了多个设备;或者添加多个 cdev 对象,每个cdev对象管理一个设备。接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO 进行操作)?观察读和写函数,没有发现能够区别设备的形参。再观察open 接口,我们会发现有一个inode形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。因此,我们可以在open接口函数中取出这些信息,并存放在file结构对象的某个成员中,再在读写的接口函数中获取该 file 结构的成员,从而可以区分出对哪个设备进行操作。
        下面首先展示用一个 cdev实现对多个设备的支持

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);static int vser_open(struct inode *inode, struct file *filp)
{switch (MINOR(inode->i_rdev)) {default:case 0:filp->private_data = &vsfifo0;break;case 1:filp->private_data = &vsfifo1;break;}return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied = 0;struct kfifo *vsfifo = filp->private_data;kfifo_to_user(vsfifo, buf, count, &copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;struct kfifo *vsfifo = filp->private_data;kfifo_from_user(vsfifo, buf, count, &copied);return copied;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;cdev_init(&vsdev, &vser_ops);vsdev.owner = THIS_MODULE;ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);cdev_del(&vsdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        上面的代码针对前一示例做的修改是:将VSER_DEV_CNT定义为2,表示支持两个设备;用DEFINE_KFIFO,分别是vsfifo0和vsfifo1(很显然,这里动态分配FIFO要优于静态定义,但是这会涉及后面章节中内核内存分配的相关知识,故此使用静态的方法);在open接口函数中根据次设备号的值来确定保存哪个FIFO结构体的地址到file结构中的private_data成员中,file结构中的private_data是一个void *类型的指针,内核保证不会使用该指针,所以正如其名一样,是驱动私有的;在读写接口函数中则是先从file结构中取出private_data的值,即FIFO结构的地址,然后再进一步操作。

        接下来演示如何将每一个edev对象对应到一个设备来实现一个驱动对多个设备的支持

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);struct vser_dev {struct kfifo *fifo;struct cdev cdev;
};static struct vser_dev vsdev[2];static int vser_open(struct inode *inode, struct file *filp)
{	filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied = 0;struct vser_dev *dev = filp->private_data;kfifo_to_user(dev->fifo, buf, count, &copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied = 0;struct vser_dev *dev = filp->private_data;kfifo_from_user(dev->fifo, buf, count, &copied);return copied;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,
};static int __init vser_init(void)
{int i;int ret;dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;for( i = 0; i < VSER_DEV_CNT; i++) {cdev_init(&vsdev[i].cdev, &vser_ops);vsdev[i].cdev.owner = THIS_MODULE;vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1; ret = cdev_add(&vsdev[i].cdev, dev + i, 1);if (ret)goto add_err;}return 0;add_err:for(--i;i>0;--i)cdev_del(&vsdev[i].cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{int i;dev_t dev;dev = MKDEV(VSER_MAJOR,VSER_MINOR);for(i = 0; i < VSER_DEV_CNT; i++)cdev_del(&vsdev[i].cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        代码第17行至第20行新定义了一个结构类型vser_dev,代表一种具体的设备类,通常和设备相关的内容都应该和cdev定义在一个结构中。如果用面向对的思想理解这种做法将会变得很容易。cdev是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的强性,比如vser_dev中的fifo,这样子类就更能刻画好一类具体的设备。代码第22行创建了两个vser_dev类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存并没有调用构造函数来初始化这两个对象,但在代码的第 74行到第77行完成了这个作。查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。代码的第 74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo成员的指向。这里需要说明的是,用 DEFINE_KFIFO 定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说 vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。
        代码第26行用到了一个container_of宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。它的作用就是根据结构成员的地址来反向得到结构的起始地址。在代码中,inode->i_cdev给出了struct vser_dev结构类型中cdev成员的地址(见图3.2),通过container_of宏就得到了包含该 cdev的结构地址。
使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。

make ARCH=arm

./lazy

上面再ubuntu中下面再开发板中

mknod /dev/vser0 c 256 0
mknod /dev/vser1 c 256 1

depmod

modprobe vser

echo "11111" > /dev/vser0

echo "22222" > /dev/vser1

cat /dev/vser0

cat /dev/vser1

这俩就会分别打印出来。没带开发板,但是现象绝对没问题。

习题

1.字符设备和块设备的区别不包括( B)。
[A]字符设备按字节流进行访问,块设备按块大小进行访问

[B]字符设备只能处理可打印字符,块设备可以处理二进制数据

[C]多数字符设备不能随机访问,而块设备一定能随机访问

[D] 字符设备通常没有页高速缓存,而块设备有

2.在3.14.25 版本的内核中,主设备号占(C )位,次设备号占(D )位。
[A]8 [B]16 [C] 12 [D] 20 
3.用于分配主次设备号的函数是(C )。
[A]register_chrdev_region [B] MKDEV 
[C]alloc_chrdev_region [D] MAJOR 
4.在字符设备驱动中,struct file_operations 结构中的函数指针成员不包含( B)。
[A]open [B]close [C] read [D] show_fdinfo 

相关文章:

Linux驱动开发——字符设备(2)

目录 虚拟串口设备驱动 一个驱动支持多个设备 习题 虚拟串口设备驱动 字符设备驱动除了前面搭建好代码的框架外&#xff0c;接下来最重要的就是要实现特定于设备的操作方法&#xff0c;这是驱动的核心和关键所在&#xff0c;是一个驱动区别于其他驱动的本质所在&#xff0c;…...

【MySQL数据库原理】MySQL Community安装与配置

目录 安装成功之后查看版本验证1、介绍、安装与配置数据库2、操作MySQL数据库3、MySQL数据库原理安装成功之后查看版本验证 SELECT VERSION();查看mysql版本号 1、介绍、安装与配置数据库 下载安装包:https://download.csdn.net/download/weixin_41194129/87672588 MySQL…...

【ROS参数服务器增删改c++操作1】

需求:实现参数服务器参数的增删改查操作。 在C中实现参数服务器数据的增删改查&#xff0c;可以通过两套API实现:. ros::NodeHandle ros::param下面为具体操作演示&#xff1a; 在src下面的自己定义的作用包下面新建文件。 比如我的是一直存在的demo03_ws文件下的src里面&…...

elasticsearch 常用数据类型详解和范例

主要内容 elasticsearch 中的字符串&#xff08;keyword&#xff09;类型 的详解和范例 elasticsearch 中的字符串/文本&#xff08;text&#xff09;类型 的详解和范例 elasticsearch 中的数字&#xff08;数值&#xff09;类型 的详解和范例 elasticsearch 中的布尔&#…...

力扣119杨辉三角 II:代码实现 + 方法总结(数学规律法 记忆法/备忘录)

文章目录第一部分&#xff1a;题目第二部分&#xff1a;解法①-数学规律法2.1 规律分析2.2 代码实现2.3 需要思考第三部分&#xff1a;解法②-记忆法&#xff08;备忘录&#xff09;第四部分&#xff1a;对比总结第一部分&#xff1a;题目 &#x1f3e0; 链接&#xff1a;119.…...

安装pandas遇到No module named ‘_bz2’ 的解决方案

出现这个问题我们可以按照这篇博客去解决&#xff1a; https://blog.csdn.net/bf96163/article/details/128654915 如果解决不了&#xff0c;可以这样去做&#xff1a; 1.确保安装了 对应的库 // ubuntu安装命令 sudo apt-get install bzip2-devel // centos安装命令 sudo y…...

【数据治理-05】什么数据才是货真价实的数据资产,一起聊聊数据资产

在国家层面一些列文件、纲要、政策、办法等政府力量的推动下&#xff0c;数据资产这个词越来越频繁的出现在我们寻常工作当中&#xff0c;现在越来越觉得这个词被滥用&#xff0c;大有“一切数据皆是资产”的感觉&#xff0c;业务数据是资产、技术数据是资产&#xff0c;不能共…...

第三章 ARM处理器体系结构【嵌入式系统】

第三章 ARM处理器体系结构【嵌入式系统】前言推荐第三章 ARM处理器体系结构3.1 概述3.2 ARM处理器的结构3.7 ARM的异常中断处理最后前言 以下内容源自《【嵌入式系统】》 仅供学习交流使用 推荐 无 第三章 ARM处理器体系结构 留着占位 敬请期待 3.1 概述 3.2 ARM处理器的…...

最速下降法

首先&#xff0c;计算函数f的梯度向量&#xff1a;∇f(x1,x2)[2x150x2]\nabla f(x_1,x_2) \begin{bmatrix}2x_1\\50x_2\end{bmatrix}∇f(x1​,x2​)[2x1​50x2​​] 然后&#xff0c;选择一个初始点(x10,x20)(x_1^0,x_2^0)(x10​,x20​)&#xff0c;比如(0,0)(0,0)(0,0)。 接…...

R语言实践——ggplot2+ggrepel绘制散点+优化注释文本位置

简介 书接adjustText实践——调整matplotlib散点图标签&#xff0c;避免重复 上文中&#xff0c;matplotlibadjustText对于我的实例来说并没有起到很好的效果。所以&#xff0c;博主决定在R中利用gglot2ggrepel绘制&#xff0c;期待效果。 操作过程 博主不常使用R&#xff…...

[TIFS 2022] FLCert:可证明安全的联邦学习免受中毒攻击

FLCert: Provably Secure Federated Learning Against Poisoning Attacks | IEEE Journals & Magazine | IEEE Xplore 摘要 由于其分布式性质&#xff0c;联邦学习容易受到中毒攻击&#xff0c;其中恶意客户端通过操纵其本地训练数据和/或发送到云服务器的本地模型更新来毒…...

css3关键帧动画

CSS3关键帧动画是一种在网页设计中常用的技术&#xff0c;通过使用CSS3的关键帧动画功能&#xff0c;可以实现网页上各种形式的动画效果&#xff0c;例如淡入淡出、滑动、旋转、缩放等&#xff0c;这些动画效果可以让网页更加生动有趣&#xff0c;吸引用户的注意力&#xff0c;…...

在 macOS Mojave 之后的每一个版本中都隐藏着比特币白皮书(Bitcoin Whitepaper)

今天我在尝试解决打印机故障问题时&#xff0c;发现了自2018年Mojave版本以来&#xff0c;macOS都附带了一份Satoshi Nakamoto&#xff08;即中本聪&#xff09;的比特币白皮书PDF副本[1]。 我已经询问了十几位使用Mac的朋友&#xff0c;他们都确认macOS里面有这个文件。这个文…...

一文看懂SpringBoot操纵数据库

1.前言 很多同学进入公司就开始参与项目开发&#xff0c;大多数情况是对某个项目进行维护或者需求迭代&#xff0c;能够从0到1参与到项目中的机会很少&#xff0c;因此并没有多少机会了解某些技术的运行机制。换句话说&#xff0c;有的面试官在面试的时候就会探讨深层的技术问题…...

科普:java与C++的区别

Java与C是两种广泛使用的编程语言&#xff0c;它们在某些方面存在不同之处。本文将详细介绍Java与C的区别。 一、C与Java的历史 C语言是由Bjarne Stroustrup在20世纪80年代初期开发的一种面向对象编程语言&#xff0c;它是C语言的扩展。Java语言是由Sun Microsystems公司于20…...

突发!ChatGPT疯了!

‍数据智能产业创新服务媒体——聚焦数智 改变商业今天&#xff0c;笔者正常登录ChatGPT&#xff0c;试图调戏一下他。但是&#xff0c;突然震惊的发现&#xff0c;ChatGPT居然疯了。之所以说他是疯了&#xff0c;而不是崩溃了&#xff0c;是因为他还能回复我&#xff0c;但回…...

docker-compose容器编排使用详解+示例

文章目录一、docker-compose概述1、产生的背景2、核心概念3、使用的三个步骤4、常用命令二、下载安装1、官方文档2、下载3、卸载三、使用compose1、前置知识&#xff0c;将一个springboot项目打包为镜像2、编写docker-compose.yml文件3、启动docker-compose4、停止一、docker-c…...

可用的rtsp ,rtmp地址以及使用VLC和ffmpeg 播放视频流

可用的 rtmp地址: rtmp://ns8.indexforce.com/home/mystream 可用的 rtsp地址: rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 可搭配VLC播放器使用&#xff0c;以及虚幻4 流媒体使用&#xff0c;实现直播效果 1.使用VLC 播放&#xff1a;https://www.vi…...

Python机器学习:朴素贝叶斯

前两天不知道把书放哪去了&#xff0c;就停更了一下&#xff0c;昨天晚上发现被我放在书包夹层里面了&#xff0c;所以今天继续开始学习。 首先明确一下啊&#xff0c;朴素贝叶斯是什么&#xff1a;朴素贝叶斯分类器是一种有监督的统计学过滤器&#xff0c;在垃圾邮件过滤、信…...

几个最基本软件的环境变量配置

在Windows中配置环境变量位置&#xff1a; 控制面板->系统和安全->系统。可以点击&#xff1a;“此电脑”->“属性”直接进入。 点击“高级系统设置”->【环境变量】。在这里可以看见用户变量和系统变量&#xff0c;如果你这台机器不是你一个人使用设置为用户变量…...

Java基础 Day28 完结篇

一、方法引用 对 Lambda 表达式的进一步简化 方法引用使用一对冒号 :: Tips&#xff1a;静态方法用类名加双冒号&#xff0c;非静态方法用对象名加双冒号 通过方法的名字来指向一个方法 参数可推导即可省略 可以使语言的构造更紧凑简洁&#xff0c;减少冗余代码 二、单元…...

c# 显示正在运行的线程数

在 C# 中&#xff0c;若想获取当前进程正在运行的线程数&#xff0c;可以使用 System.Diagnostics 命名空间中的 Process 类来实现。该方法适用于 Windows 平台&#xff0c;并能够获取当前进程的线程信息&#xff0c;包括线程总数和运行中的线程数量。 ✅ 方法一&#xff1a;使…...

强化学习的前世今生(五)— SAC算法

书接前四篇 强化学习的前世今生&#xff08;一&#xff09; 强化学习的前世今生&#xff08;二&#xff09; 强化学习的前世今生&#xff08;三&#xff09;— PPO算法 强化学习的前世今生&#xff08;四&#xff09;— DDPG算法 本文为大家介绍SAC算法 7 SAC 7.1 最大熵强化…...

前端基础之《Vue(18)—路由知识点》

一、两种路由模式 1、hash路由 &#xff08;1&#xff09;url中有#号&#xff0c;背后是监听onhashchange事件 &#xff08;2&#xff09;hash路由部署上线不会出现404问题&#xff0c;背后是基于history api实现的 2、history路由 &#xff08;1&#xff09;url中没有#号 &a…...

HTTP协议完全指南:从请求响应到HTTPS安全机制

文章目录 一、HTTP协议中的基本概念1.HTTP协议介绍&#xff08;1&#xff09;协议&#xff08;2&#xff09;传输&#xff08;3&#xff09;超文本 2.统一资源定位符&#xff08;URL&#xff09; 二、HTTP协议中的请求和响应1.HTTP客户端请求消息&#xff08;1&#xff09;请求…...

运维 vm 虚拟机ip设置

虚拟网络设置 nat 模式 网卡 主机设置网卡地址 虚拟机绑定网卡...

Windows版PostgreSQL 安装 vector 扩展

问题 spring-ai在集成PGVector向量存储的时候会报错如下&#xff0c;那么就需要安装pgsql的vector扩展。 SQL [CREATE EXTENSION IF NOT EXISTS vector]; 错误: 无法打开扩展控制文件 "C:/Program Files/PostgreSQL/9.6/share/extension/vector.control": No such …...

云游戏混合架构

云游戏混合架构通过整合本地计算资源与云端能力&#xff0c;形成了灵活且高性能的技术体系&#xff0c;其核心架构及技术特征可概括如下&#xff1a; 一、混合架构的典型模式 分层混合模式‌ 前端应用部署于公有云&#xff08;如渲染流化服务&#xff09;&#xff0c;后端逻辑…...

Vue中安装插件的方式

一. 认识Vue插件 1.1. 通常向Vue全局添加一些功能时&#xff0c;会采用插件的模式&#xff0c;它有两种编写方式&#xff1a; 1.1.1. 对象类型&#xff1a;一个对象&#xff0c;但是必须包含一个install的函数&#xff0c;该函数会在安装插件时执行 // 方式一&#xff1a;传入…...

agent mode 代理模式,整体要求,系统要求, 系统指令

1. 起因&#xff0c; 目的: 我发现很多时候&#xff0c;我在重复我的要求。很烦。决定把一些过程记录下来&#xff0c;提取一下。 2. 先看效果 无。 3. 过程: 要求: 这2个文件&#xff0c;是我与 AI 聊天的一些过程记录。 请阅读这2个文件&#xff0c;帮我提取出一些共同…...