19 异步通知

一、异步通知

1. 异步通知简介

  阻塞和非阻塞两种方式都是需要应用程序去主动查询设备的使用情况。

  异步通知类似于驱动可以主动报告自己可以访问,应用程序获取信号后会从驱动设备中读取或写入数据。

  异步通知最核心的就是信号

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

  其中 9 和 19 是不能被忽略的,这些信号相当于中断号,不同的中断号代表不同的中断,不同的中断又可以实现不同的功能。

  使用中断的时候需要设置中断处理函数,那么应用程序中使用了信号,也要有信号处理函数,在应用程序中使用 signal 函数来设置信号的处理函数:

/** @description : 设置指定信号的处理函数* @param - signum : 要设置处理函数的信号* @param - handler : 信号的处理函数* @return : 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR*/
sighandler_t signal(int signum, sighandler_t handler);

  这里的信号处理原型为:

typedef void (*sighandler_t)(int)

  之前使用的 kill -9 PID就是指定进程发送  SIGKILL 这个信号。当我们按下 CTRL + C 的时候,会向当前正在占用终端的应用程序发送 SIGINT(2)信号,最终中断终端。

  在 linux/atk-mpl 文件夹下,创建 signaltest.c,并输入以下代码:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>/* 信号处理函数 */
void signalint_handler(int num)
{printf("\r\nSIGINT signal!\r\n");exit(0);
}int main(void)
{signal(SIGINT, signalint_handler);while(1);return 0;
}/* 总体流程:首先自己设置信号处理函数,之后应用程序开启,按下CTRL+C向signaltest发送SIGINT信号以后signalint_handler函数就会执行,此函数会先输出SIGINT signal!后,exit函数会关闭signaltest的应用程序。*/

  输入命令编译 signaltest.c:

gcc signaltest.c -o signaltest

   输入命令执行应用程序 signaltest:

./signaltest

   按下 CTRL+ C后,可以看到:

2. 驱动中的信号处理

① fasync_struct 结构体

  如果在驱动中使用信号,首先先定义一个fasync_struct结构体变量,一般将这个结构体放在 xxx_dev 结构体中,例如:

/* key设备结构体 */
struct key_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */struct device_node	*nd; /* 设备节点 */int key_gpio;			/* key所使用的GPIO编号		*/struct timer_list timer;			/* 按键值 		*/int irq_num;			/* 中断号 		*/atomic_t status;   		/* 按键状态 */wait_queue_head_t r_wait;	/* 读等待队列头 */struct fasync_struct *async_queue;    /* fasync_struct结构体 */
};

② fasync 函数

  函数负责将用户空间应用程序的进程 ID 添加到驱动程序相应文件的异步通知队列中。

  如果要使用异步通知,需要在驱动程序里实现 file_operation 操作集中的 fasync函数:

int (*fasync) (int fd, struct file *filp, int on)

  驱动中的 fasync 函数示例如下:

struct xxx_dev {......struct fasync_struct *async_queue; /* 异步相关结构体 */
};static int xxx_fasync(int fd, struct file *filp, int on)    // fasync 函数
{struct xxx_dev *dev = (xxx_dev)filp->private_data;    // 私有数据if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)    // 这里的fasync_helper其实是被fasync调用的return -EIO;return 0;
}static struct file_operations xxx_ops = {.......fasync = xxx_fasync,    // 只要是 file_operations里的函数,都要这样的形势走一遍......
};

  如果需要关闭驱动文件,那么需要在 release 函数里面释放 fasync_struct,示例如下:

static int xxx_release(struct inode *inode, struct file *filp)
{return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}
static struct file_operations xxx_ops = {.......release = xxx_release,        // 只要用到了file_operations就要这样
};

③ kill_fasync 函数

/** @description : 当设备可以访问的时候,驱动程序需要向应用程序发出信号,此函数负责发送指定的信号* @param - fp : 要操作的 fasync_struct* @param - sig : 要发送的信号* @param - band : 可读时设置为 POLL_IN,可写时设置为 POLL_OUT* @return : 无*/
void kill_fasync(struct fasync_struct **fp, int sig, int band);

3. 应用程序对异步通知的处理

  总共包含三步:

① 注册信号处理函数

  这里的处理函数需要自己设置,并且最后传给 signal 函数。

② 将本应用程序的进程号告诉内核

  使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。 

③ 开启异步通知

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

  重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。 

  当用户空间应用程序调用 fcntl 系统调用并设置 F_SETFL 命令以及 FASYNC 标志时,内核会调用驱动程序中的 fasync 函数。

二、程序编写

  在 /linux/atk-mpl/Drivers 文件夹下创建 16_asynchronization 文件夹,并创建 asynchronization.c 文件,并输入以下代码:

#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/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fcntl.h>#define KEY_CNT			1		/* 设备号个数 	*/
#define KEY_NAME		"key"	/* 名字 		*//* 定义按键状态 */
enum key_status {KEY_PRESS = 0,      // 按键按下KEY_RELEASE,        // 按键松开KEY_KEEP,           // 按键状态保持
};/* key设备结构体 */
struct key_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */struct device_node	*nd; /* 设备节点 */int key_gpio;			/* key所使用的GPIO编号		*/struct timer_list timer;			/* 按键值 		*/int irq_num;			/* 中断号 		*/	atomic_t status;   		/* 按键状态 */wait_queue_head_t r_wait;	/* 读等待队列头 */struct fasync_struct *async_queue;	/* fasync_struct结构体 */
};static struct key_dev key;          /* 按键设备 */static irqreturn_t key_interrupt(int irq, void *dev_id)		// 中断处理函数
{/* 按键防抖处理,开启定时器延时15ms */mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));return IRQ_HANDLED;
}/** @description	: 初始化按键IO,open函数打开驱动的时候* 				  初始化按键所使用的GPIO引脚。* @param 		: 无* @return 		: 无*/
static int key_parse_dt(void)
{int ret;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点:key */key.nd = of_find_node_by_path("/key");if(key.nd == NULL) {printk("key node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(key.nd, "status", &str);if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(key.nd, "compatible", &str);if(ret < 0) {printk("key: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,key")) {printk("key: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到KEY0所使用的KYE编号 */key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);if(key.key_gpio < 0) {printk("can't get key-gpio");return -EINVAL;}/* 5 、获取GPIO对应的中断号 */key.irq_num = irq_of_parse_and_map(key.nd, 0);if(!key.irq_num){return -EINVAL;}printk("key-gpio num = %d\r\n", key.key_gpio);return 0;
}static int key_gpio_init(void)		// gpio初始化
{int ret;unsigned long irq_flags;ret = gpio_request(key.key_gpio, "KEY0");		// 申请gpioif (ret) {printk(KERN_ERR "key: Failed to request key-gpio\n");return ret;}	/* 将GPIO设置为输入模式 */gpio_direction_input(key.key_gpio);/* 获取设备树中指定的中断触发类型 */irq_flags = irq_get_trigger_type(key.irq_num);if (IRQF_TRIGGER_NONE == irq_flags)irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/* 申请中断 */ret = request_irq(key.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);if (ret) {gpio_free(key.key_gpio);return ret;}return 0;
}static void key_timer_function(struct timer_list *arg)		// 定时器处理函数
{static int last_val = 1;int current_val;/* 读取按键值并判断按键当前状态 */current_val = gpio_get_value(key.key_gpio);if (0 == current_val && last_val){atomic_set(&key.status, KEY_PRESS);	// 按下wake_up_interruptible(&key.r_wait);	// 唤醒r_wait队列头中的所有队列if(key.async_queue)kill_fasync(&key.async_queue, SIGIO, POLL_IN);	// }else if (1 == current_val && !last_val) {atomic_set(&key.status, KEY_RELEASE);   // 松开wake_up_interruptible(&key.r_wait);	// 唤醒r_wait队列头中的所有队列if(key.async_queue)kill_fasync(&key.async_queue, SIGIO, POLL_IN);}elseatomic_set(&key.status, KEY_KEEP);              // 状态保持last_val = current_val;
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int key_open(struct inode *inode, struct file *filp)
{return 0;
}/** @description     : 从设备读取数据 * @param – filp    : 要打开的设备文件(文件描述符)* @param – buf     : 返回给用户空间的数据缓冲区* @param – cnt     : 要读取的数据长度* @param – offt    : 相对于文件首地址的偏移* @return          : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{int ret;if (filp->f_flags & O_NONBLOCK) {	// 非阻塞方式访问if(KEY_KEEP == atomic_read(&key.status))return -EAGAIN;} else {							// 阻塞方式访问/* 加入等待队列,当有按键按下或松开动作发生时,才会被唤醒 */ret = wait_event_interruptible(key.r_wait, KEY_KEEP != atomic_read(&key.status));if(ret)return ret;}/* 将按键状态信息发送给应用程序 */ret = copy_to_user(buf, &key.status, sizeof(int));/* 状态重置 */atomic_set(&key.status, KEY_KEEP);return ret;
}/** @description		: fasync函数,用于处理异步通知* @param – fd		: 文件描述符* @param – filp	: 要打开的设备文件(文件描述符)* @param – on		: 模式* @return			: 负数表示函数执行失败*/
static int key_fasync(int fd, struct file *filp, int on)
{return fasync_helper(fd, filp, on, &key.async_queue);
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int key_release(struct inode *inode, struct file *filp)
{return key_fasync(-1, filp, 0);
}/** @description     : poll函数,用于处理非阻塞访问* @param - filp    : 要打开的设备文件(文件描述符)* @param - wait    : 等待列表(poll_table)* @return          : 设备或者资源状态,*/
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{unsigned int mask = 0;poll_wait(filp, &key.r_wait, wait);if(KEY_KEEP != atomic_read(&key.status))	// 按键按下或松开动作发生mask = POLLIN | POLLRDNORM;	// 返回PLLINreturn mask;
}/* 设备操作函数 */
static struct file_operations key_fops = {.owner = THIS_MODULE,.open = key_open,.read = key_read,.write = key_write,.release = 	key_release,.poll = key_poll,.fasync	= key_fasync,
};/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init mykey_init(void)
{int ret;/* 初始化等待队列头 */init_waitqueue_head(&key.r_wait);/* 初始化按键状态 */atomic_set(&key.status, KEY_KEEP);/* 设备树解析 */ret = key_parse_dt();if(ret)return ret;/* GPIO 中断初始化 */ret = key_gpio_init();if(ret)return ret;/* 注册字符设备驱动 *//* 1、创建设备号 */ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);	/* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", KEY_NAME, ret);goto free_gpio;}/* 2、初始化cdev */key.cdev.owner = THIS_MODULE;cdev_init(&key.cdev, &key_fops);/* 3、添加一个cdev */ret = cdev_add(&key.cdev, key.devid, KEY_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */key.class = class_create(THIS_MODULE, KEY_NAME);if (IS_ERR(key.class)) {goto del_cdev;}/* 5、创建设备 */key.device = device_create(key.class, NULL, key.devid, NULL, KEY_NAME);if (IS_ERR(key.device)) {goto destroy_class;}/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */timer_setup(&key.timer, key_timer_function, 0);return 0;destroy_class:device_destroy(key.class, key.devid);
del_cdev:cdev_del(&key.cdev);
del_unregister:unregister_chrdev_region(key.devid, KEY_CNT);
free_gpio:free_irq(key.irq_num, NULL);gpio_free(key.key_gpio);return -EIO;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit mykey_exit(void)
{/* 注销字符设备驱动 */cdev_del(&key.cdev);/*  删除cdev */unregister_chrdev_region(key.devid, KEY_CNT); /* 注销设备号 */del_timer_sync(&key.timer);		/* 删除timer */device_destroy(key.class, key.devid);/*注销设备 */class_destroy(key.class); 		/* 注销类 */free_irq(key.irq_num, NULL);	/* 释放中断 */gpio_free(key.key_gpio);		/* 释放IO */
}module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  编写 asynchronizationApp.c 文件:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>static int fd;/** SIGIO信号处理函数* @param – signum		: 信号值* @return				: 无*/
static void sigio_signal_func(int signum)
{unsigned int key_val = 0;read(fd, &key_val, sizeof(unsigned int));if (0 == key_val)printf("Key Press\n");else if (1 == key_val)printf("Key Release\n");printf("SIGIO signal!\r\n");
}/** @description		: main主程序* @param – argc		: argv数组元素个数* @param – argv		: 具体参数* @return			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int flags = 0;/* 判断传参个数是否正确 */if(2 != argc) {printf("Usage:\n""\t./asyncKeyApp /dev/key\n");return -1;}/* 打开设备 */fd = open(argv[1], O_RDONLY | O_NONBLOCK);  // 非阻塞O_NONBLOCKif(0 > fd) {printf("ERROR: %s file open failed!\n", argv[1]);return -1;}/* 设置信号SIGIO的处理函数 */signal(SIGIO, sigio_signal_func);       // signal函数,用于设置信号的处理函数fcntl(fd, F_SETOWN, getpid());			// 将当前进程的进程号告诉给内核flags = fcntl(fd, F_GETFD);				// 获取当前的进程状态fcntl(fd, F_SETFL, flags | FASYNC);		// 设置进程启用异步通知功能/* 循环轮询读取按键数据 */while(1) {sleep(2);}/* 关闭设备 */close(fd);return 0;
}

三、运行测试

  首先先编写 Makefile 文件:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)obj-m := asynchronization.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  之后编译 asynchronization.c 和 asynchronizationApp 文件:

make
arm-none-linux-gnueabihf-gcc asynchronizationApp.c -o asynchronization

  将编译好的 asynchronizationApp 和 asynchronization.ko 复制到:

sudo cp asynchronization asynchronization.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  开启开发板,输入以下命令:

cd lib/modules/5.4.31/
depmod
modprobe asynchronization.ko

   测试 App 应用程序:

./asynchronization /dev/key

  按下 Key0,则会出现:

  每当按下 KEY0 的时候,就会捕获到 SIGIO信号,并且按键获取成功。

  卸载驱动:rmmod asynchronization.ko

总结

  概念:

  其实异步通知就是让驱动设备空出来的时候发送一个信号给用户进程空间,让进程空间知道,哦原来可以使用这个设备了,一般搭配 非阻塞+异步通知使用,效果更佳。

  异步通知的使用方法需要掌握,他们分为驱动和用户进程空间:

  在内核空间中:① 定义 fasync_struct结构体;② 编写fasync函数;③ 最后当设备空出来的时候指定信号 kill_fasync

  在用户空间中:① 编写信号处理函数,并且最后把信号处理函数传给 signal函数;② 本程序进程号告诉内核 fcntl(fd, F_SETOWN, getpid());③ 开启异步通知:flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */ fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

  代码:其实跟上一节差不太多,在按键按下/松开的时候设置kill_fasync发送信号给用户空间。有个小tip,只要使用了file_operations函数里的函数,都要在 static struct file_operations key_fops = {...}这个函数下写下设备操作函数。测试代码中按照上述用户空间编写即可。

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

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

相关文章

帝国cms中如何让外部链接直接从新窗口打开页面

<?php if($bqr[isurl]) { ?> <a href"<?$bqsr[titleurl]?>" target"_blank"> <?php } else { ?> <a href"<?$bqsr[titleurl]?>"> <?php } ?>

KCC@广州与 TiDB 社区联手—广州开源盛宴

10月21日&#xff0c;KCC广州与 TiDB 社区联手&#xff0c;在海珠区保利中悦广场 29 楼召开了一次难忘的开源盛宴。这不仅仅是 KCC广州的又一次线下见面&#xff0c;更代表着与 TiDB 社区及广州技术社区的首次深度合作。 活动的策划与组织由 KCC广州负责人 - 惠世冀、PingCAP 的…

Doris:多源数据目录(Multi-Catalog)

目录 1.基本概念 2.基本操作 2.1 查看 Catalog 2.2 新增 Catalog 2.3 切换 Catalog 2.4 删除 Catalog 3.元数据更新 3.1手动刷新 3.2定时刷新 3.3自动刷新 4.JDBC Catalog 4.1 上传mysql驱动包 4.2 创建mysql catalog 4.3. 读取mysql数据 1.基本概念 …

ArcGIS10.8 连接 PostgreSQL 及遇到的两个问题

前提 以前同事用过我的电脑连PostgreSQL&#xff0c;失败了。当时不知道原因&#xff0c;只能使用GeoServer来发布数据了。现在终于搞明白了&#xff0c;原因是ArcGIS10.2版本太老&#xff0c;无法连接PostgreSQL9.4。参考这里 为了适应时代的发展&#xff0c;那我就用新的Ar…

大数据Doris(二十一):数据导入演示

文章目录 数据导入演示 一、启动zookeeper集群(三台节点都启动) 二、启动hdfs集群

PDF有限制密码,不能复制怎么办?

大家现在接触PDF文件越来越多&#xff0c;有的时候在网上下载的PDF文件打开之后&#xff0c;发现选中文字之后无法复制。甚至其他功能也都无法使用&#xff0c;这是怎么回事&#xff1f;该怎么办&#xff1f; 当我们发现文件打开之后&#xff0c;编辑功能无法使用&#xff0c;很…

封装

文章目录 概念优点访问限定符封装的必要性代码块普通代码块构造块静态块 概念 在面向对象中&#xff0c;封装是指对于某个对象&#xff0c;Java隐藏对象的属性和方法的实现细节&#xff0c;仅对外公开接口&#xff0c;控制在程序中属性的读取和修改的访问级别。适当的封装可以…

第二十五节——Vuex--历史遗留

文档地址 Vuex 是什么&#xff1f; | Vuex version V4.x 一、概念 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。一个状态自管理应用包含以下几个部…

git基础知识

1.git的必要配置 所有的配置文件&#xff0c;其实都保存在本地&#xff01; 查看所有配置 git config -l 即把 系统配置(system)和当前用户&#xff08;global&#xff09;配置都 列出来 以直接编辑配置文件&#xff0c;通过命令设置后会响应到这里。 注意&#xff1a; 如果…

抖音短视频账号矩阵系统、短视频矩阵源码+无人直播源码开发可打包

抖音短视频账号矩阵系统、短视频矩阵源码无人直播源码开发可打包 矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框…

【Excel】补全单元格值变成固定长度

我们知道股票代码都为6位数字&#xff0c;但深圳中小板代码前面以0开头&#xff0c;数字格式时前面的0会自动省略&#xff0c;现在需要在Excel表格补全它。如下图&#xff1a; 这时我们需要用到特殊的函数&#xff1a;TEXT或者RIGHT TEXT函数是Excel中一个非常有用的函数。TEX…

CSS布局001:画各种三角形

CSS实战中&#xff0c;有很多时候采用css来绘制三角形&#xff0c;而不是采用图片的方式&#xff0c;这样有利于快速成型&#xff0c;不用多调用服务器数据。 CSS代码 上三角 #triangle-up {width: 0;height: 0;border-left: 50px solid transparent;border-right: 50px solid…