Linux驱动实现IO模型
在Linux系统分为内核态和用户态,CPU会在这两个状态之间进行切换。当进行IO操作时,应用程序会使用系统调用进入内核态,内核操作系统会准备好数据,把IO设备的数据加载到内核缓冲区。
然后内核操作系统会把内核缓冲区的数据从内核空间拷贝到用户空间。但是进行IO操作时,CPU和内存的速度远远高于外设的速度,所以需要我们使用IO模型编程解决。
IO模型的种类:
- 阻塞IO
- 非阻塞IO
- IO多路复用
- 信号驱动IO
- 异步IO
阻塞IO
当进程以阻塞的方式打开设备文件时(默认方式),如果资源不可用,那么进程阻塞,也就是进程休眠。既然有休眠,就有对应的唤醒操作,否则进程将会一直休眠下去。驱动程序应该在资源可用时负责唤醒操作。相比于非阻塞IO,其最大的优点就是,资源不可用时,不占用CPU的时间,而非阻塞IO必须要定期尝试,看看资源是否可以获得,这对于键盘鼠标这类设备来说,其效率非常低。但是阻塞IO也有一个明显的缺点,那就是进程在休眠期间再也不能做其他事情。
通过上面的描述,发现要实现阻塞操作最重要的数据结构就是等待队列。不了解等待队列的请参考(等待队列)。
下面通过一个简单的驱动程序来实现阻塞IO模型。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/sched.h>struct devices {char buffer[32];int flag;
};struct devices qw_dev;DECLARE_WAIT_QUEUE_HEAD(wqh);int test_open(struct inode *inode, struct file *filp)
{filp->private_data = &qw_dev;printk("dev open!\n");return 0;
}ssize_t test_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;wait_event_interruptible(wqh, dev->flag);if(copy_to_user(buf, dev->buffer, size)!=0) {printk("copy to user error\n");return -EFAULT;}return size;
}ssize_t test_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if(copy_from_user(dev->buffer, buf, size)) {printk("copy from user error\n");return -EFAULT;}dev->flag = 1;wake_up_interruptible(&wqh);printk("WRITE: %s\n", dev->buffer);return size;}int test_release(struct inode *inode, struct file *filp)
{printk("dev close!\n");return 0;
}//声明操作函数集合
struct file_operations wq_fops = {.owner = THIS_MODULE,.open = test_open,.read = test_read,.write = test_write,.release = test_release,
};//分配初始化miscdevice
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "qw",//设备文件名.fops = &wq_fops,//操作函数集合
};//加载函数
int test_init(void)
{int ret;//注册miscdeviceret = misc_register(&misc_dev);if (ret < 0) {misc_deregister(&misc_dev);return -1;}qw_dev.flag = 0;return 0;}//卸载函数
void test_exit(void)
{//注销miscdevice misc_deregister(&misc_dev);
}//声明为模块的入口和出口
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("WQ driver!");//描述信息
向驱动写数据的应用程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char *argv[])
{int fd;char buf2[32] = "hello world";fd = open("/dev/qw", O_RDWR);if (fd < 0) {perror("open error");return fd;}write(fd, buf2, strlen(buf2));close(fd);return 0;
}
向驱动读数据的应用程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char *argv[])
{int fd;char buf1[32] = {0};fd = open("/dev/qw", O_RDWR);if (fd < 0) {perror("open error");return fd;}read(fd, buf1, 32);printf("READ:%s\n",buf1);close(fd);return 0;
}
将驱动模块加载到内核中后,先运行读程序,发现程序会阻塞。然后另开一个终端运行写程序,读程序才会打印写程序的数据。
非阻塞IO
设备不一定随时都能给用户提供服务,这就有了资源可用和不可用两种状态。如果应用程序以非阻塞的方法打开设备文件,当资源不可用时,驱动就应该立即返回,并用一个错误码EAGAIN来通知应用程序此时资源不可用,应用程序应该稍后再尝试。
下面通过一个简单的驱动程序来实现阻塞IO模型。在阻塞IO模型上进行修改,当应用程序打开设备文件时,以非阻塞方式打开 。
fd = open("/dev/qw", O_RDWR | O_NONBLOCK);
驱动程序读函数中判断设备是否以非阻塞方式打开,并且资源是否准备好。struct file 结构体中的f_flags成员存储打开设备文件的标志。
if (filp->f_flags & O_NONBLOCK) {if (dev->flag != 1) {return -EAGAIN;}
}
驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/sched.h>struct devices {char buffer[32];int flag;
};struct devices qw_dev;DECLARE_WAIT_QUEUE_HEAD(wqh);int test_open(struct inode *inode, struct file *filp)
{filp->private_data = &qw_dev;printk("dev open!\n");return 0;
}ssize_t test_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if (filp->f_flags & O_NONBLOCK) {if (dev->flag != 1) {return -EAGAIN;}}wait_event_interruptible(wqh, dev->flag);if(copy_to_user(buf, dev->buffer, size)!=0) {printk("copy to user error\n");return -EFAULT;}return size;
}ssize_t test_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if(copy_from_user(dev->buffer, buf, size)) {printk("copy from user error\n");return -EFAULT;}dev->flag = 1;wake_up_interruptible(&wqh);printk("WRITE: %s\n", dev->buffer);return size;}int test_release(struct inode *inode, struct file *filp)
{printk("dev close!\n");return 0;
}//声明操作函数集合
struct file_operations wq_fops = {.owner = THIS_MODULE,.open = test_open,.read = test_read,.write = test_write,.release = test_release,
};//分配初始化miscdevice
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "qw",//设备文件名.fops = &wq_fops,//操作函数集合
};//加载函数
int test_init(void)
{int ret;//注册miscdeviceret = misc_register(&misc_dev);if (ret < 0) {misc_deregister(&misc_dev);return -1;}qw_dev.flag = 0;return 0;}//卸载函数
void test_exit(void)
{//注销miscdevice misc_deregister(&misc_dev);
}//声明为模块的入口和出口
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("WQ driver!");//描述信息
读应用程序如下所示,写程序前面的一样就不展示出来。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char *argv[])
{int fd;char buf1[32] = {0};fd = open("/dev/qw", O_RDWR | O_NONBLOCK);if (fd < 0) {perror("open error");return fd;}while(1) {read(fd, buf1, 32);printf("READ:%s\n",buf1);sleep(1);}close(fd);return 0;
}
将驱动模块加载到内核中后,先运行读程序,发现程序不会阻塞,但是没有数据。然后另开一个终端运行写程序,读程序才会打印出写程序的数据。
IO多路复用
阻塞IO优点是在设备资源不可用时,进程主动放弃CPU,但是进程阻塞后不能做其他操作。非阻塞IO优点是资源不可用时不会阻塞,但是会不停地轮询,系统效率降低。当一个进程要同时对多个设备进程操作时以上两种方法显得非常不方便。这就需要使用到IO复用模型。
它允许一个进程可以同时监视多个文件描述符,并且可以在其中任何一个文件描述符上等待数据可读或可写,从而实现并发IO操作。在应用程序中Linux提供了三种API函数:poll,select和epoll。在驱动程序中只需要实现poll函数。
poll和select基本上是一样 ,都可以监听多个文件描述符,通过轮询多个文件描述符来获得已经准备好的文件描述符。epoll是将主动轮询变成了被动通知,当事件发生时,被动的接受通知。
接下来以poll为例进行说明,应用程序中poll系统调用的原型及相关的数据类型如下。
int poll (struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd;
short events;
short revents;
};
POLLIN There is data to read.
POLLOUT Writing now will not block.
POLLRDNORM Equivalent to POLLIN.
POLLWRNORM Equivalent to POLLOUT.
poll的第一个参数是要监听的文件描述符集合,类型为指向struct pollfd的指针,struct pollfd有3个成员,fd是要监听文件描述符,events是监听的事件,revents是返回的事件。常见的事件有POLLIN、POLLOUT,分别表示设备可以无阻塞地读、写。POLLRDNORM和POLLWRNORM是在_XOPEN_SOURCE宏被定义时所引入的事件,POLLRDNORM通常和POLLIN等价,POLLWRNORM通常和POLLOUT等价。
poll函数的第二个参数是要监听的文件描述符的个数,第三个参数的毫秒的超时值,负数表示一直监听,直到被监听的文件描述符集合中的任意一个设备发生了事件才会返回。函数返回值为-1表示失败,成功返回revents不为0的文件描述符个数。
读应用程序如下所示,写程序前面的一样就不展示出来。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>int main(int argc, char *argv[])
{int fd;int ret;char buf1[32] = {0};struct pollfd fds[1];fd = open("/dev/qw", O_RDWR | O_NONBLOCK);if (fd < 0) {perror("open error");return fd;}fds[0].fd = fd;fds[0].events = POLLIN;while(1) {ret = poll(fds, 1, 5000);if (!ret) {printf("timeout\n");} else if (fds[0].revents == POLLIN) {read(fd, buf1, 32);printf("READ:%s\n",buf1);sleep(1);}}close(fd);return 0;
}
驱动中poll函数需要完成两点:1. 对可能引起设备文件状态变化的等待队列调用poll_wait函数,将对应的等待队列头添加到poll_table。2. 返回表示是否能对设备进行无阻塞读写访问的掩码。
static __poll_t test_poll(struct file *filp, struct poll_table_struct *p)
{struct devices *dev = (struct devices *)filp->private_data;__poll_t mask = 0;poll_wait(filp, &wqh, p);if (dev->flag = 1) {mask |= POLLIN;}return mask;
}
注意:poll_wait函数不会阻塞。
驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>struct devices {char buffer[32];int flag;
};struct devices qw_dev;DECLARE_WAIT_QUEUE_HEAD(wqh);int test_open(struct inode *inode, struct file *filp)
{filp->private_data = &qw_dev;printk("dev open!\n");return 0;
}ssize_t test_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if (filp->f_flags & O_NONBLOCK) {if (dev->flag != 1) {return -EAGAIN;}}wait_event_interruptible(wqh, dev->flag);if(copy_to_user(buf, dev->buffer, size)!=0) {printk("copy to user error\n");return -EFAULT;}return size;
}ssize_t test_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if(copy_from_user(dev->buffer, buf, size)) {printk("copy from user error\n");return -EFAULT;}dev->flag = 1;wake_up_interruptible(&wqh);printk("WRITE: %s\n", dev->buffer);return size;}static __poll_t test_poll(struct file *filp, struct poll_table_struct *p)
{struct devices *dev = (struct devices *)filp->private_data;__poll_t mask = 0;poll_wait(filp, &wqh, p);if (dev->flag == 1) {mask |= POLLIN;}return mask;
}int test_release(struct inode *inode, struct file *filp)
{printk("dev close!\n");return 0;
}//声明操作函数集合
struct file_operations wq_fops = {.owner = THIS_MODULE,.open = test_open,.read = test_read,.write = test_write,.poll = test_poll,.release = test_release,
};//分配初始化miscdevice
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "qw",//设备文件名.fops = &wq_fops,//操作函数集合
};//加载函数
int test_init(void)
{int ret;//注册miscdeviceret = misc_register(&misc_dev);if (ret < 0) {misc_deregister(&misc_dev);return -1;}qw_dev.flag = 0;return 0;}//卸载函数
void test_exit(void)
{//注销miscdevice misc_deregister(&misc_dev);
}//声明为模块的入口和出口
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("WQ driver!");//描述信息
将驱动模块加载到内核中后,先运行读程序,发现程序不会阻塞,但是没有数据且每5s打印超时。然后另开一个终端运行写程序,读程序才会打印出写程序的数据。
信号驱动IO
在信号驱动IO中,进程使用系统调用 sigaction() 来注册一个信号处理函数,该函数会在IO事件就绪时被内核调用。当进程调用 sigaction() 注册一个信号处理函数时,它需要指定一个描述符和一个事件,内核在检测到该描述符上发生指定事件时,会向进程发送指定信号。进程可以通过捕获该信号并执行信号处理函数。
与其他IO多路复用技术相比,信号驱动IO也避免了轮询机制的开销,从而减少了 CPU 的占用。但是,信号驱动IO也存在一些缺点。首先,它对信号的处理需要一定的时间,因此它不适合高速IO操作。其次,由于信号是不可靠的,因此在使用信号驱动IO时需要考虑到信号可能会丢失的情况。
在应用程序使用信号驱动IO,需要完成以下几步:1. 注册信号处理函数,应用程序使用 sigaction()来注册一个信号处理函数。2. 设置能接受这个信号的进程。 3. 开启信号驱动IO,通常是使用fcntl函数的F_SETFL命令打开FASYNC标志。
fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性。
函数原型:
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
fcntl函数功能依据cmd的值的不同而不同。参数对应功能如下:
读应用程序如下所示,写程序前面的一样就不展示出来。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>int fd;
char buf1[32] = {0};static void test_func(int signal)
{read(fd, buf1, 32);printf("READ:%s\n",buf1);
}int main(int argc, char *argv[])
{int flags;fd = open("/dev/qw", O_RDWR | O_NONBLOCK);if (fd < 0) {perror("open error");return fd;}signal(SIGIO, test_func); //注册信号处理函数test_funcfcntl(fd, F_SETOWN, getpid()); //设置当前进程接收SIGIO信号flags = fcntl(fd, F_GETFD); //获得文件描述符标志fcntl(fd, F_SETFL, flags | FASYNC); //设置文件状态标志,在原来文件描述符标志上开启FASYNC标志while(1);close(fd);return 0;
}
当应用程序开启信号驱动IO时,会触发驱动程序中的fasync函数,而fasync函数会调用fasync_helper函数去初始化fasync_struct结构体。 当数据到达时调用kill_fasync函数用来通知应用程序,然后应用程序执行信号处理函数,它的参数是被传递的信号(常常是 SIGIO)和 band 。band:可读时设置成POLLIN,可写时设置成POLLOUT。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>struct devices {char buffer[32];int flag;struct fasync_struct *fasync;
};struct devices qw_dev;DECLARE_WAIT_QUEUE_HEAD(wqh);int test_open(struct inode *inode, struct file *filp)
{filp->private_data = &qw_dev;printk("dev open!\n");return 0;
}ssize_t test_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if (filp->f_flags & O_NONBLOCK) {if (dev->flag != 1) {return -EAGAIN;}}wait_event_interruptible(wqh, dev->flag);if(copy_to_user(buf, dev->buffer, size)!=0) {printk("copy to user error\n");return -EFAULT;}return size;
}ssize_t test_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{struct devices *dev = (struct devices *)filp->private_data;if(copy_from_user(dev->buffer, buf, size)) {printk("copy from user error\n");return -EFAULT;}dev->flag = 1;wake_up_interruptible(&wqh);kill_fasync(&dev->fasync, SIGIO, POLLIN);printk("WRITE: %s\n", dev->buffer);return size;}static __poll_t test_poll(struct file *filp, struct poll_table_struct *p)
{struct devices *dev = (struct devices *)filp->private_data;__poll_t mask = 0;poll_wait(filp, &wqh, p);if (dev->flag == 1) {mask |= POLLIN;}return mask;
}static int test_fasync(int fd, struct file *filp, int on)
{struct devices *dev = (struct devices *)filp->private_data;return fasync_helper(fd, filp, on, &dev->fasync); //对fasync_struct结构体进行初始化
}int test_release(struct inode *inode, struct file *filp)
{printk("dev close!\n");return 0;
}//声明操作函数集合
struct file_operations wq_fops = {.owner = THIS_MODULE,.open = test_open,.read = test_read,.write = test_write,.poll = test_poll,.fasync = test_fasync,.release = test_release,
};//分配初始化miscdevice
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "qw",//设备文件名.fops = &wq_fops,//操作函数集合
};//加载函数
int test_init(void)
{int ret;//注册miscdeviceret = misc_register(&misc_dev);if (ret < 0) {misc_deregister(&misc_dev);return -1;}qw_dev.flag = 0;return 0;}//卸载函数
void test_exit(void)
{//注销miscdevice misc_deregister(&misc_dev);
}//声明为模块的入口和出口
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("WQ driver!");//描述信息
将驱动模块加载到内核中后,先运行读程序,发现程序会阻塞,这是因为用while(1)来模拟程序继续处理其他事情。然后另开一个终端运行写程序,读程序才会打印出写程序的数据。
异步IO
相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到设备数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。异步IO可以在空户空间的glibc库实现,不依赖内核就不举实例了。
总结
阻塞IO实现简单,但是性能不佳,非阻塞IO虽然不需要阻塞线程,但是他需要轮询操作低效。
IO多路复用有三种实现,select和poll都是使用了轮询的方式,监听文件描述符不能太多。epoll的实现使用了红黑树,增删改文件描述符效率高,并且使用了事件触发机制,不需要进行轮询。
信号驱动IO依赖于信号机制,它对信号的处理需要一定的时间,因此它不适合高速IO操作。
异步IO模型的实现比同步IO模型更加复杂,需要使用操作系统提供的通知机制来处理IO完成的事件,同时也需要考虑到异步IO可能会引入的竞争条件和死锁问题。
参考文章:深入理解Linux的五种IO模型,linux五种IO模型。
相关文章:
Linux驱动实现IO模型
在Linux系统分为内核态和用户态,CPU会在这两个状态之间进行切换。当进行IO操作时,应用程序会使用系统调用进入内核态,内核操作系统会准备好数据,把IO设备的数据加载到内核缓冲区。 然后内核操作系统会把内核缓冲区的数据从内核空…...
wsl2 更新报错问题解决记录
1、问题 win10 中安装的 wsl2,启动 docker desktop 时提示 wsl2 有问题: 于是点击推荐的地址连接到微软,下载 wsl2 的更新文件。之后运行,又报错: 更新被卡住。 2、解决方法 WinR 输入 cmd 打开命令行窗口&#x…...
突破算法迷宫:精选50道-算法刷题指南
前言 在计算机科学和编程领域,算法和数据结构是基础的概念,无论你是一名初学者还是有经验的开发者,都需要掌握它们。本文将带你深入了解一系列常见的算法和数据结构问题,包括二分查找、滑动窗口、链表、二叉树、TopK、设计题、动…...
玩转Mysql系列 - 第26篇:聊聊mysql如何实现分布式锁?
这是Mysql系列第26篇。 本篇我们使用mysql实现一个分布式锁。 分布式锁的功能 分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 锁具有重入的功能:即一个使用者可以多次获取某个锁 获取锁有超时的功能ÿ…...
linux 解压缩命令tar
Tar tar 命令的选项有很多(用 man tar 可以查看到),但常用的就那么几个选项,下面来举例说明一下: tar -cf all.tar *.jpg 这条命令是将所有.jpg 的文件打成一个名为 all.tar 的包。-c 是表示产生新的包,-f 指 定包的文件名。 …...
OpenAI ChatGPT API 文档之 Embedding
译者注: Embedding 直接翻译为嵌入似乎不太恰当,于是问了一下 ChatGPT,它的回复如下: 在自然语言处理和机器学习领域,"embeddings" 是指将单词、短语或文本转换成连续向量空间的过程。这个向量空间通常被称…...
Java常用类(二)
好久不见,因工作原因,好久没发文了,OldWang 回来了,持续更新Java内容!⭐ 不可变和可变字符序列使用陷阱⭐ 时间处理相关类⭐ Date 时间类(java.util.Date)⭐ DateFormat 类和 SimpleDateFormat 类⭐ Calendar 日历类 ⭐…...
Java获取给定月份的前N个月份和前N个季度
描述: 在项目开发过程中,遇到这样一个需求,即:给定某一月份,得到该月份前面的几个月份以及前面的几个季度。例如:给定2023-09,获取该月份前面的前3个月,即2023-08、2023-07、2023-0…...
网页资源加载过程
网页资源加载是指在浏览器中访问一个网页时,浏览器如何获取和显示网页内容的过程。这个过程通常分为以下几个步骤: DNS 解析: 当用户在浏览器中输入一个网址(例如,https://www.example.com),浏览…...
使用git config --global设置用户名和邮件,以及git config的全局和局部配置
文章目录 1. 文章引言2. 全局配置2.1 命令方式2.2 配置文件方式 3. 局部配置3.1 命令方式3.2 配置文件方式 4. 总结 1. 文章引言 我们为什么要设置设置用户名和邮件? 我们在注册github,gitlab等时,一般使用用户名或邮箱: 这个用户…...
【C语言】21-指针-3
目录 1. 指针数组1.1 什么是指针数组1.2 如何定义指针数组1.3 如何使用指针数组2. 多重指针2.1 二重指针的定义2.2 二重指针的初始化与赋值2.3 二重指针的使用3. 指针常量、常量指针、指向常量的常指针3.1 概念3.2 const pointer3.3 pointer to a constant3.3.1 (pointer to a …...
解决craco启动react项目卡死在Starting the development server的问题
现象: 原因:craco.config.ts配置文件有问题 经过排查发现Dev开发模式下不能有splitChunk的配置, 解决办法: 加一个生产模式的判断,开发模式不加载splitChunk的配置,仅在生产模式才加载 判断条件代码&#…...
常见的密码学算法都有哪些?
密码学算法是用于保护信息安全的数学方法和技术。它们可以分为多个类别,包括对称加密、非对称加密、哈希函数和数字签名等。以下是一些常见的密码学算法: 1、对称加密算法: AES(高级加密标准):一种广泛使…...
云安全【阿里云ECS攻防】
关于VPC的概念还请看:记录一下弹性计算云服务的一些词汇概念 - 火线 Zone-安全攻防社区 一、初始化访问 1、元数据 1.1、SSRF导致读取元数据 如果管理员给ECS配置了RAM角色,那么就可以获得临时凭证 如果配置RAM角色 在获取ram临时凭证的时候ÿ…...
TBSS数据分析
tbss分析基本流程: 步骤一,指标解算:求解出FA,MD,AD,RD指标 #!/bin/bash #基于体素的形态学分析VBA path/media/kui/Passport5T/DATA_help/TBSS/row_data mkdir ${path}/Results_DTI_tbss mkdir ${path}/R…...
【单调队列】 239. 滑动窗口最大值
239. 滑动窗口最大值 解题思路 计算每一个滑动窗口的最大值 关键在于借助单调队列实现窗口对于单调队列 尾部添加元素 头部删除元素添加元素操作:从尾部开始循环对比 删除比当前元素小的元素获取最大值元素 直接获取头部元素删除元素操作 直接删除头部元素 class…...
Spring实例化源码解析之ComponentScanAnnotationParser(四)
上一章我们分析了ConfigurationClassParser,配置类的解析源码分析。在ComponentScans和ComponentScan注解修饰的候选配置类的解析过程中,我们需要深入的了解一下ComponentScanAnnotationParser的parse执行流程,SpringBoot启动类为什么这么写&…...
MySQL - 外键(foreign key)约束的作用和使用
什么是外键约束? 外键:用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。 外键约束是用于建立两个表之间关系的一种约束,它定义了一个表中的列与另一个表中的列之间的关系。外键约束可以保证数据的完整性和一致性…...
前端开发之服务器的基本概念与初识Ajax
1,服务器的基本概念与初识Ajax 1.1 URL地址的组成部分 1.2 客户端与服务器的通信过程 1.3 网页中如何请求数据 1.4 $.get()函数 1.4.1 $.get()函数的语法 // jQuery 中 $.get() 函数的功能单一,专门用来发起 get 请求,从而将服务器上的资源…...
数据结构排序算法---八大排序复杂度及代码实现
文章目录 一、冒泡排序代码实现 二、直接插入排序代码实现 三、希尔排序代码实现 四、选择排序代码实现 五、堆排序代码实现 六、快速排序代码实现 七、归并排序代码实现 八、计数排序代码实现 稳定性:相同的数据排序后,相对位置是否发生改变 一、冒泡排…...
GMS之Launcher中去除默认Search或替换为Chrome Search
将Launcher中搜索框去除 将FeatureFlags.java文件中的QSB_ON_FIRST_SCREEN变量修改为false \system\vendor\mediatek\proprietary\packages\apps\Launcher3\src\com\android\launcher3\config\FeatureFlags.java/*** Defines a set of flags used to control various launche…...
@DateTimeFormat 和 @JsonFormat 的详细研究
关于这两个时间转化注解,先说结论 一、介绍 1、DateTimeFormat DateTimeFormat 并不会根据得到其属性 pattern 把前端传入的数据转换成自己想要的格式,而是将前端的String类型数据封装到Date类型;其次它的 pattern 属性是用来规范前端传入…...
nodejs基于Vue.js健身体育器材用品商城购物网97794
管理员端的功能主要是开放给系统的管理人员使用,能够对用户的信息进行管理,包括对用户、健身器材、器材类型、系统和订单进行查看,修改和删除、新增等,对系统整体运行情况进行了解。用户的功能主要是对个人账号和密码进行更新信息…...
C#WPF框架Microsoft.Toolkit.MvvM应用实例
本文实例演示C#WPF框架Microsoft.Toolkit.MvvM应用 目录 一、MVVM概述 二、MVVMLight概述 三、使用Microsoft.Toolkit.Mvvm框架 一、MVVM概述 MVVM概述MVVM是Model-View-ViewModel的简写,主要目的是为了解耦视图(View)和模型(Model)。...
蓝桥杯每日一题2023.9.27
4408. 李白打酒加强版 - AcWing题库 题目描述 题目分析 对于这题我们发现有三个变量,店,花,酒的数量,对于这种范围我们使用DP来进行分析。 dp[i][j][k]我们表示有i个店,j朵花,k单位酒的集合,…...
Redis与分布式-主从复制
接上文 常用中间件-OAuth2 1.主从复制 启动两个redis服务器。 修改第一个服务器地址 修改第二个redis 然后分别启动 redis-server.exe redis.windows.conf) 查看当前服务器的主从状态,打开客户端:输入info replication命令来查看当前的主从状态&am…...
QT pyside2 线程嵌套子线程 实现开始运行和停止运行
文章目录 前言为什么要使用多线程 一、单个线程实现按钮方法的执行二、线程嵌套多个子线程实现按钮方法的执行三、QT GUI常用代码3.1 多线程取出队列任务循环执行,无停止3.2 将某个方法放在线程中执行3.3 QT pyside2 tableWidget 清除日志3.4 退出整个GUI程序(杀死进…...
江西广电会展集团总经理李悦一行莅临拓世科技集团调研参观,科技璀璨AIGC掀新潮
在江西这片充满活力的土地上,数字经济如潮水般涌动,会展文化与科技的完美结合,正如新时代的璀璨繁星照亮夜空,更预示着一场AIGC创新的壮丽篇章即将展开。作为拓世科技集团的老朋友,江西广电多位领导多次莅临拓世科技集…...
【RabbitMQ实战】06 RabbitMQ配置
一、概述 一般情况下,可以使用默认的内建配置来有效地运行RabbitMQ,并且大多数情况下也并不需要修改任何 RabbitMQ的配置。当然,为了更加有效地操控 RabbitMQ,也可以利用调节系统范围内的参数来达到定制化的需求。 RabbitMQ提供…...
CTF 全讲解:[SWPUCTF 2021 新生赛]jicao
文章目录 参考环境题目index.phphighlight_file()include()多次调用,多次执行单次调用,单次执行 $_POST超全局变量HackBarHackBar 插件的获取 $_POST打开 HackBar 插件通过 HackBar 插件发起 POST 请求 GET 请求查询字符串超全局变量 $_GET JSONJSON 数据…...
做桂林网站的图片大全/seo关键词有话要多少钱
NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。 umPy 是一个运行速度非常快的数学库,主要用于数组计算,包含: 一个强大的N维数组对…...
昆明网站建设服务/整合营销活动策划方案
使用springCloud时出现BindingException: Invalid bound statement (not found)错误 百度检查后发现是xml的命名空间写错了。。。 帮助来源:SpringBoot整合mybatis出现BindingException: Invalid bound statement (not found)问题解决...
武乡网站建设/营销型网站
用途 通过除去绑定程序和符号调试程序使用的信息,降低扩展公共对象文件格式(XCOFF)的对象文件的大小。 语法 strip [ -V ] [ -r [ -l ] | -x [ -l ] | -t | -H | -e | -E ] [ -X {32 |64 |32_64 }] [ -- ] File ... 描…...
保定做网站的公司/分销系统
2019独角兽企业重金招聘Python工程师标准>>> 是这样的,我的项目的框架是 之前 公司统一使用的框架,配置都配置好了的。 大神们说是没有问题的, 我简单过了一下,也没有问题。 在项目上线并完成了好久后。。。就在前几天…...
做海产品的外贸网站/人工智能培训班
mysql开发中文博客:iMySQL | 老叶茶馆 – Oracle MySQL ACE Director,专注MySQL 动态表名列名: delimiter // create procedure oneKey(in newName varchar(250),in oldName varchar(250),in idNum INT) BEGIN SET sqlStmt CONCAT(insert …...
广州网站建设o2o/万网域名查询注册商
信号调制与解调[实验目的]1. 了解用MATLAB 实现信号调制与解调的方法。 2. 了解几种基本的调制方法。 [实验原理]由于从消息变换过来的原始信号具有频率较低的频谱分量,这种信号在许多信道中不适宜传输。因此,在通信系统的发送端通…...