linux驱动编程 - kfifo先进先出队列

简介:

        kfifo是Linux Kernel里面的一个 FIFO(先进先出)数据结构,它采用环形循环队列的数据结构来实现,提供一个无边界的字节流服务,并且使用并行无锁编程技术,即当它用于只有一个入队线程和一个出队线程的场情时,两个线程可以并发操作,而不需要任何加锁行为,就可以保证kfifo的线程安全。
        FIFO主要用于缓冲速度不匹配的通信。

下面图解kfifo工作过程:

蓝色表示kfifo剩余空间,红色表示kfifo已占用空间

1)空的kfifo

2)put数据到buffer后

3)从buffer中get数据后

4)当此时put到buffer中的数据长度超出in到末尾长度时,则将剩下的移到头部去

注意,kfifo如果只有一个写入者,一个读取者,是不需要锁的。但是多对一的情况,多的那方需要上锁。如:多个写入者,一个读取者,需对写入者上锁。 反之,如果有多个读取者,一个写入者,需对读取者上锁。

一、kfifo常用函数介绍

Linux内核中的路径:lib/kfifo.c、include/linux/kfifo.h

头文件:#include <linux/kfifo.h>

常用函数 / 宏功能
DECLARE_KFIFO_PTR(fifo, type)定义一个名字为fifo,element类型为type,其数据需要kfifo_alloc动态分配
DECLARE_KFIFO(fifo, type, size)定义一个名字为fifo,element类型为type,element个数为size,其数据静态存储在结构体中,size需为常数且为2的整数次方
INIT_KFIFO(fifo)初始化DECLARE_KFIFO接口定义的fifo
DEFINE_KFIFO(fifo, type, size)定义并初始化fifo
kfifo_initialized(fifo)fifo是否初始化
kfifo_recsize(fifo)返回fifo的recsize
kfifo_size(fifo)返回fifo的size
kfifo_reset(fifo)将in和out置0,注意:需要上锁
kfifo_reset_out(fifo)设置out=in,由于只修改out,因此在读者上下文,且只有一个读者时,是安全的。否则需要上锁。
kfifo_len(fifo)返回fifo的总size
kfifo_is_empty(fifo)fifo是否为空 (in == out)
kfifo_is_full(fifo)fifo是否满
kfifo_avail(fifo)获取队列的空闲空间长度
kfifo_skip(fifo)跳过一个element
kfifo_peek_len(fifo)获取下一个element的字节长度。
kfifo_alloc(fifo, size, gfp_mask)为指针式FIFO分配空间并初始化,成功返回0,错误则返回负数错误码
kfifo_free(fifo)释放kfifo_alloc分配的内存
kfifo_init(fifo, buffer, size)用户自己申请缓存,然后传递给fifo进行初始化,成功返回0,错误则返回负数错误码
kfifo_put(fifo, val)这是一个宏,将val赋值给一个FIFO type类型的临时变量,然后将临时变量入队。存放一个element,如果成功返回入队的elements个数。如果FIFO满,则返回0。
kfifo_get(fifo, val)

val是一个指针,内部将val赋值给一个ptr指针类型的临时变量,并拷贝sizeof(*ptr)长度到val的地址。拷贝一个element。
如果FIFO为空,返回0,否则返回拷贝的element数。

kfifo_peek(fifo, val)和kfifo_get相同,除了不更新out外。
kfifo_in(fifo, but, n)入队n个elemnts。返回工程入队的elements数。
kfifo_in(fifo, buf, n, lock)加锁入队。加锁方式为spin_lock_irqsave
kfifo_out(fifo, buf, n)出队n个elements,返回成功拷贝的elements数
kfifo_out_spinlocked(fifo, buf, n, lock)加锁出队。加锁方式位spin_lock_irqsave
kfifo_from_user(fifo, from, len, copied)

复制用户空间的数据到kfifo

最多拷贝len个字节,参考record FIFO和非record FIFO的对应底层接口。
kfifo_to_user(fifo, to, len, copied)

复制kfifo中的数据到用户空间

最多拷贝len个字节到用户空间,参考record FIFO和非record FIFO的对应底层接口。
kfifo_out_peek(fifo, buf, n)peek n个elements的数据,但是内部out不动,返回拷贝的elements个数

1、结构体定义

1.1 struct __kfifo 结构体

struct __kfifo {unsigned int	in;           //入队指针,指向下一个元素可被插入的位置unsigned int	out;          //出队指针,指向即将出队的第一个元素unsigned int	mask;         //向上扩展成2的幂queue_size-1unsigned int	esize;        //每个元素的大小,单位为字节void		*data;            //存储数据的缓冲区
};

下图可以直观的表示各结构体成员之间的关系:

1.2 struct kfifo 结构体


#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \union { \struct __kfifo	kfifo; \datatype	*type; \const datatype	*const_type; \char		(*rectype)[recsize]; \ptrtype		*ptr; \ptrtype const	*ptr_const; \}#define __STRUCT_KFIFO_PTR(type, recsize, ptrtype) \
{ \__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \type		buf[0]; \
}struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);

kfifo结构体展开后格式如下:

struct kfifo
{union{struct __kfifo    kfifo;            //__kfifo是kfifo的成员unsigned char        *type;const unsigned char  *const_type;char                 (*rectype)[0];void                 *ptr;void const           *ptr_const;  };unsigned char buf[0];
}

kfifo怎么和其它字段是联合的?其它字段读写岂不是会覆盖kfifo的内容。其实这又是内核的一个技巧,其它字段不会读写数据,只是编译器用来获取相关信息 。

比如:

获取recsize:

#define kfifo_recsize(fifo)     (sizeof(*(fifo)->rectype))
通过kfifo_alloc分配buf存储空间时,获取块的大小

__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : 

2、初始化kfifo

声明kfifo有2种方式:

  • DECLARE_KFIFO_PTR 配合 kfifo_alloc 用于动态申请kfifo;
  • DECLARE_KFIFO 用于静态定义kfifo;
功能相似方法

DECLARE_KFIFO_PTR(fifo, type)

参数:

fifo:要定义的kfifo的名字

type:元素的类型

宏定义一个kfifo指针对象,会设置type buf[]数组的大小为0,因此需配合 kfifo_alloc  动态分配buf的存储空间struct kfifo fifo;

DECLARE_KFIFO(fifo, type, size)

参数:

fifo:要定义的kfifo的名字

type:元素的类型

size:kfifo可容纳的元素个数,必须是2的幂

静态声明一个kfifo对象,设置type buf[] 大小为size、类型为 type 的数组DEFINE_KFIFO

笔者常用到动态申请方式,因此主要介绍动态申请方式。

动态申请除了用 DECLARE_KFIFO_PTR,还能用 struct kfifo 创建结构体,如:

struct kfifo fifo;        #可替代 DECLARE_KFIFO_PTR(fifo, unsigned char)

这种方式可替代 DECLARE_KFIFO_PTR(fifo, unsigned char),它们都用到 __STRUCT_KFIFO_PTR,仅传入的第3个参数不同。

/* struct kfifo结构体定义 */
struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);/* DECLARE_KFIFO_PTR宏定义 */
#define STRUCT_KFIFO_PTR(type) \struct __STRUCT_KFIFO_PTR(type, 0, type)#define DECLARE_KFIFO_PTR(fifo, type)   STRUCT_KFIFO_PTR(type) fifo

2.1 动态申请

方法一:

struct kfifo fifo = {0};                                //定义一个 struct kfifo 变量

kfifo_alloc(&fifo, 4096, GFP_KERNEL);  //使用 kfifo_alloc 动态申请内存空间,大小为4096

方法二:

DECLARE_KFIFO_PTR(fifo, unsigned char);        //申请

INIT_KFIFO(fifo);                                                    //初始化 

kfifo_alloc(&fifo, 4096, GFP_KERNEL);   //使用 kfifo_alloc 动态申请内存空间,大小为4096

注意:动态分配最后需要调用 kfifo_free 释放

2.2 静态定义

方法一:

DECLARE_KFIFO(fifo, char, 512);        //静态申明,type buf[] 大小为512,类型为char

INIT_KFIFO(fifo);                                   //初始化fifo结构

方法二:

DEFINE_KFIFO(fifo, char, 512)             //同上

3、入队、出队

3.1 kfifo_in

功能:buf中len长度数据写入到fifo中

返回值:实际写入长度

unsigned int __kfifo_in(struct __kfifo *fifo,const void *buf, unsigned int len)
{unsigned int l;l = kfifo_unused(fifo);        //判断kfifo还有多少剩余空间if (len > l)len = l;kfifo_copy_in(fifo, buf, len, fifo->in);    //将数据拷贝到kfifo中fifo->in += len;               //设置写入数量+lenreturn len;
}#define	kfifo_in(fifo, buf, n) \
({ \typeof((fifo) + 1) __tmp = (fifo); \typeof(__tmp->ptr_const) __buf = (buf); \unsigned long __n = (n); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ?\__kfifo_in_r(__kfifo, __buf, __n, __recsize) : \__kfifo_in(__kfifo, __buf, __n); \
})

3.2 kfifo_out

功能:从fifo中获取len长度数据到buf中

unsigned int __kfifo_out(struct __kfifo *fifo,void *buf, unsigned int len)
{len = __kfifo_out_peek(fifo, buf, len);    //fifo输出数据到buffifo->out += len;                          //输出数量+lenreturn len;
}#define	kfifo_out(fifo, buf, n) \
__kfifo_uint_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \typeof(__tmp->ptr) __buf = (buf); \unsigned long __n = (n); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ?\__kfifo_out_r(__kfifo, __buf, __n, __recsize) : \__kfifo_out(__kfifo, __buf, __n); \
}) \
)

4、动态申请、释放内存

4.1 kfifo_alloc

功能:动态申请kfifo内存

返回值:0-成功,其他-失败

int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,size_t esize, gfp_t gfp_mask)
{/** round up to the next power of 2, since our 'let the indices* wrap' technique works only in this case.*/size = roundup_pow_of_two(size);    //向上扩展为2的幂fifo->in = 0;fifo->out = 0;fifo->esize = esize;if (size < 2) {fifo->data = NULL;fifo->mask = 0;return -EINVAL;}fifo->data = kmalloc_array(esize, size, gfp_mask);    //动态申请内存if (!fifo->data) {fifo->mask = 0;return -ENOMEM;}fifo->mask = size - 1;return 0;
}#define kfifo_alloc(fifo, size, gfp_mask) \
__kfifo_int_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \struct __kfifo *__kfifo = &__tmp->kfifo; \__is_kfifo_ptr(__tmp) ? \__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \-EINVAL; \
}) \
)

注意:保证缓冲区大小为2的次幂,若不是,会向上取整为2的次幂(很重要)

4.2 kfifo_free

功能:释放kfifo动态申请的内存

void __kfifo_free(struct __kfifo *fifo)
{kfree(fifo->data);        //释放内存fifo->in = 0;fifo->out = 0;fifo->esize = 0;fifo->data = NULL;fifo->mask = 0;
}#define kfifo_free(fifo) \
({ \typeof((fifo) + 1) __tmp = (fifo); \struct __kfifo *__kfifo = &__tmp->kfifo; \if (__is_kfifo_ptr(__tmp)) \__kfifo_free(__kfifo); \
})

二、使用方法

使用kfifo的方式有两种,动态申请和静态定义。

3.1 动态申请

动态申请步骤如下:

① 包含头文件 #include <linux/kfifo.h>

② 定义一个 struct kfifo 变量;

③ 使用 kfifo_alloc 申请内存空间;

④ 分别使用 kfifo_in、kfifo_out 执行入队、出队的操作;

⑤ 不再使用kfifo时,使用 kfifo_free 释放申请的内存。

示例:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kfifo.h>//定义fifo存储结构体
struct member {char name[32];char val;
};//定义fifo最大保存的元素个数
#define FIFO_MEMBER_NUM     64//定义kfifo
static struct kfifo stFifo;static int __init kfifo_demo_init(void)
{int ret = 0;int i;/* 1.申请fifo内存空间,空间大小最好为2的幂 */ret = kfifo_alloc(&stFifo, sizeof(struct member) * FIFO_MEMBER_NUM, GFP_KERNEL);if (ret) {printk(KERN_ERR "kfifo_alloc fail ret = %d\n", ret);return;}/* 2.入队 */struct member stMember = {0}; for (i = 0; i < FIFO_MEMBER_NUM; i++) {snprintf(stMember.name, 32, "name%d", i);stMember.val = i;ret = kfifo_in(&stFifo, &stMember, sizeof(struct member));if (!ret) {printk(KERN_ERR "kfifo_in fail, fifo is full\n");}}/* 3.出队 */for  (i = 0; i < FIFO_MEMBER_NUM; i++) {ret = kfifo_out_peek(&stFifo, &stMember, sizeof(struct member));        //读,返回实际读到长度(不修改out)ret = kfifo_out(&stFifo, &stMember, sizeof(struct member));             //读,返回实际读到长度(修改out)if (ret) {printk(KERN_INFO "kfifo_out stMember: name = %s, val=%d\n", stMember.name, stMember.val);} else {printk(KERN_ERR "kfifo_out fail, fifo is empty\n");}if (kfifo_is_empty(&stFifo)) {        //判断fifo空printk(KERN_INFO "kfifo is empty!!!\n");break;}}/* 4.释放 */kfifo_free(&stFifo);
}static void __exit kfifo_demo_exit(void)
{kfifo_free(&stFifo);
}module_init(kfifo_demo_init);
module_exit(kfifo_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dong");

测试结果:

这种动态申请方式in、out都是以字节为单位。

3.2 静态定义

静态定义步骤如下:

① 包含头文件 #include <linux/kfifo.h>

② 使用宏 DECLARE_KFIFO 静态定义 fifo 变量;

③ 分别使用 kfifo_put、kfifo_get执行入队、出队的操作;

静态定义不需要申请和释放内存的步骤,出入队函数也更精简。

示例:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kfifo.h>//定义fifo存储结构体
struct member {char name[32];char val;
};//定义fifo最大保存的元素个数,最好为2的幂
#define FIFO_MEMBER_NUM     64//静态定义已经包含了缓存定义
DECLARE_KFIFO(stFifo, struct member, FIFO_MEMBER_NUM);static int __init kfifo_demo_init(void)
{int ret = 0;int i;/* 1.初始化  */INIT_KFIFO(stFifo);/* 2.入队 */struct member stMember = {0}; for (i = 0; i < FIFO_MEMBER_NUM; i++) {snprintf(stMember.name, 32, "name%d", i);stMember.val = i;ret = kfifo_put(&stFifo, stMember);  //注意这里的元素变量名而不是指针if (!ret) {printk(KERN_ERR "kfifo_put fail, fifo is full\n");}}/* 3.出队 */for  (i = 0; i < FIFO_MEMBER_NUM; i++) {ret = kfifo_get(&stFifo, &stMember); //注意这里传入地址if (ret) {printk(KERN_INFO "kfifo_get stMember: name = %s, val=%d\n", stMember.name, stMember.val);} else {printk(KERN_ERR "kfifo_get fail, fifo is empty\n");}printk(KERN_INFO "kfifo: in = %d, out = %d\n", stFifo.kfifo.in, stFifo.kfifo.out);if (kfifo_is_empty(&stFifo)) {printk(KERN_INFO "kfifo is empty!!!\n");break;}}
}static void __exit kfifo_demo_exit(void)
{return;
}module_init(kfifo_demo_init);
module_exit(kfifo_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dong");

测试结果: 

示例中静态定义的in、out是以结构体为单位,64次入队fifo中就会有64个结构体元素。

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

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

相关文章

nginx修改网站默认根目录及发布(linux、centos、ubuntu)openEuler软件源repo站点

目录 安装nginx配置nginx其它权限配置 安装nginx dnf install -y nginx配置nginx whereis nginxcd /etc/nginx llcd conf.d touch vhost.conf vim vhost.conf 命令模式下输入:set nu或:set number可以显示行号 复制如下内容&#xff1a; server {listen 80;server_name…

ESP32 通过蓝牙显示歌词代码示例

通过蓝牙协议播放音乐&#xff0c;有的时候需要显示歌词&#xff0c;这里就是a2dp库获取了歌词 值得注意的是要想正确获取到歌词&#xff0c;必须打开各种播放器的字幕&#xff08;歌词&#xff09;开关 本项目用了三个开源库 a2dp&#xff0c;tft_espi,xfont. a2dp &#x…

【】AI八股-神经网络相关

Deep-Learning-Interview-Book/docs/深度学习.md at master amusi/Deep-Learning-Interview-Book GitHub 网上相关总结&#xff1a; 小菜鸡写一写基础深度学习的问题&#xff08;复制大佬的&#xff0c;自己复习用&#xff09; - 知乎 (zhihu.com) CV面试问题准备持续更新贴 …

.net 调用海康SDK的跨平台解决方案

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔序言 上2篇海康SDK使用以及常见的坑…

【JavaEE精炼宝库】文件操作(1)——基本知识 | 操作文件——打开实用性编程的大门

目录 一、文件的基本知识1.1 文件的基本概念&#xff1a;1.2 树型结构组织和目录&#xff1a;1.3 文件路径&#xff08;Path&#xff09;&#xff1a;1.4 二进制文件 VS 文本文件&#xff1a;1.5 其它&#xff1a; 二、Java 操作文件2.1 方法说明&#xff1a;2.2 使用演示&…

HCIA综合实验

学习新思想&#xff0c;争做新青年。今天学习的是HCIA综合实验&#xff01; 实验拓扑 实验需求 总部&#xff1a; 1、除了SW8 SW9是三层交换机&#xff0c;其他交换机均为2层交换机。 2、GW为总部的出口设备&#xff0c;使用单臂路由技术&#xff0c;VLAN10,20,100的网关都在GW…

ERROR: “armeabi-v7a“ not supported for HarmonyOS

IDE 从 devecostudio-mac-4.1.3.700 升级至 devecostudio-mac-5.0.3.403 后抛出了如下异常: ERROR: "armeabi-v7a" not supported for HarmonyOS. 解决办法 一.entry/build-profile.json5 需 entry/build-profile.json5 的 abiFilters 中移除 "armeabi-v7a&qu…

计算机网络体系结构详解:协议与分层

在学习计算机网络时&#xff0c;理解网络协议与分层体系结构是至关重要的。本文将详细介绍这些概念&#xff0c;帮助基础小白快速入门。 1. 什么是网络协议 网络协议是计算机网络中用于数据交换的规则和标准。这些规则规定了数据格式、时序以及发送和接收数据时的动作。网络协…

基于java+springboot+vue实现的流浪动物管理系统(文末源码+Lw)277

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对流浪动物信息管理的提升&…

【React】React18 Hooks之useState

目录 useState案例1&#xff08;直接修改状态&#xff09;案例2&#xff08;函数式更新&#xff09;案例3&#xff08;受控表单绑定&#xff09;注意事项1&#xff1a;set函数不会改变正在运行的代码的状态注意事项2&#xff1a;set函数自动批量处理注意事项3&#xff1a;在下次…

[数据集][目标检测]护目镜检测数据集VOC+YOLO格式888张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;888 标注数量(xml文件个数)&#xff1a;888 标注数量(txt文件个数)&#xff1a;888 标注类别…

ORB 特征点提取

FAST关键点 选取像素p&#xff0c;假设它的亮度为Ip&#xff1b; . 设置一个阈值T&#xff08;比如Ip的20%&#xff09;&#xff1b; 以像素p为中心&#xff0c;选取半径为3的圆上的16个像素点&#xff1b; 假如选取的圆上&#xff0c;有连续的N个点的亮度大于IpT或小于…

Redis 八股文

标题 1. Redis主从同步原理&#xff1a;判断下线的条件:故障转移如何保证Sentinel高可用 1. Redis主从同步原理&#xff1a; 1、slave执行命令向master建立连接 2、master执行bgsave&#xff08;后台存储&#xff09;&#xff0c;生成rdb快照&#xff08;redis备份方式&#x…

日志自动分析-Web---360星图GoaccessALBAnolog

目录 1、Web-360星图(IIS/Apache/Nginx) 2、Web-GoAccess &#xff08;任何自定义日志格式字符串&#xff09; 源码及使用手册 安装goaccess 使用 输出 3-Web-自写脚本&#xff08;任何自定义日志格式字符串&#xff09; 4、Web-机器语言analog&#xff08;任何自定义日…

游戏AI的创造思路-技术基础-强化学习(1)

我们“强化”一下机器的“学习”&#xff0c;让机器变得更强~~~~ 目录 1. 强化学习的定义 2. 发展历史 3. 强化学习的基本概念和函数 3.1. 基本概念和函数 3.1.1. 基本概念和函数 3.1.2. Q函数 3.1.2.1. 定义与作用 3.1.2.2. 数学表示 3.1.2.3. 更新规则 3.1.2.4. 算…

AI时代算法面试:揭秘高频算法问题与解答策略

三种决策树算法的特点和区别 ID3算法&#xff1a;基本的决策树算法&#xff0c;适用于简单的分类问题C4.5算法&#xff1a;改进了ID3算法&#xff0c;适用于更复杂的分类问题&#xff0c;可以处理连续型数据和缺失值CART算法&#xff1a;更加通用的决策树算法&#xff0c;适用于…

云服务器在 Web 应用程序中作用

云服务器在Web应用程序中扮演着至关重要的角色&#xff0c;它不仅是现代Web应用程序的基石&#xff0c;还是推动业务发展和提升用户体验的关键技术之一。下面将详细探讨云服务器在Web应用程序中的重要作用及其优势。 首先&#xff0c;云服务器为Web应用程序提供了高度可扩展的…

【postgresql】索引

见的索引类型&#xff1a; B-tree 索引&#xff1a;这是最常用的索引类型&#xff0c;适用于大多数查询。B-tree索引可以高效地处理范围查询。 Hash 索引&#xff1a;适用于等值查询&#xff0c;但不支持范围查询。 GiST 索引&#xff1a;通用搜索树&#xff08;GiST&#xf…

怀念旧的Windows声音?以下是如何在Windows 11中恢复它们

如果你渴望旧的Windows声音,希望能在Windows 11上再次听到,那你就很幸运了。我们将向你展示如何下载必要的声音包并创建复古的声音方案。 如何获取旧Windows声音的声音包 你需要做的第一件事是下载一个包含旧Windows版本声音的声音包。此外,请确保它包含的每个声音都是WAV…

在 Vue 项目中使用 FullCalendar

1、先安装依赖包&#xff0c;根据自己的需求安装&#xff0c;建议使用cnpm安装&#xff0c;不然会很慢有时候会出现安装不上的情况。 npm i fullcalendar/vue --save npm i fullcalendar/core --save // 在月视图或日视图中操作事件 npm i fullcalendar/daygrid --save // 在…