Linux驱动进阶(二)——设备驱动中的阻塞和同步机制

news2024/10/7 6:40:21

文章目录

  • 前言
  • 阻塞与非阻塞
  • 等待队列
    • 等待队列概述
    • 等待队列的实现
    • 等待队列的使用
  • 同步机制实验
    • 同步机制设计
    • 实验验证
  • 小结


前言

阻塞和非阻塞是设备访问的两种基本方式。使用这两种方式,驱动程序可以灵活地支持阻塞与非阻塞访问。在写阻塞与非阻塞的驱动程序时,经常用到等待队列,所有本章将对等待队列进行简要介绍。

阻塞与非阻塞

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上它们是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select()就是这样的一个例子。下面是调用select()函数进入阻塞的一个例子。

void main()
{
	FILE *fp;
	struct fd_set fds;
	struct timeval timeout={4, 0}; //select()函数等待4s,4s后轮询
	char buffer[256]={0};  //256字节的缓冲区
	fp = fopen(....);     //打开文件
 	while(1)
 	{
		FD_ZERO(&fds);  //清空集合
		FD_SET(fp, &fds);  //同上
		maxfdp=fp+1;   //描述符最大值加1
		switch(select(maxfdp, &fds, &fds, NULL, &timeout)) //select函数使用
		{
			case -1:
				exit(-1);
				break;   //select()函数错误,退出程序
			case 0:
				break;  //再次轮询
			default:
				if(FD_ISSET(fp, &fds)) //判断是否文件中有数据
				{
					read(fds, buffer, 256, ...); //接受文件数据
					if(FD_ISSET(fp, &fds)) //测试文件是否可写
					fwrite(fp, buffer...); //写入文件buffer清空
				}
		}
	}
}

等待队列

本节将介绍驱动程序编程中常用的等待队列机制。这种机制使等待的进程暂时睡眠,当等待的信号到来时,便唤醒等待队列中进程继续执行。本节将详细介绍等待队列的内容。

等待队列概述

在Linux驱动程序中,阻塞进程可以使用等待队列(Wait Queue)来实现。由于等待队列很有用,在Linux2.0的时代,就已经引入了等待队列机制。等待队列的基本数据结构是一个双向链表,这个链表存储睡眠的进程。等待队列也与进程调度机制紧密结合,能够用于实现内核中异步事件通知机制。等待队列可以用来同步对系统资源的访问。例如,当完成一项工作之后,才允许完成另一项工作。
在内核中,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。

等待队列的实现

根据不同的平台,其提供的指令代码有所不同,所以等待队列的实现也有所不同。在Linux中,等待队列的定义如下代码所示。

struct __wait_queue_head{
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

下面详细介绍该结构体中的各个成员变量。
1.lock自旋锁
lock自旋锁的功能很简答,用来对task_list链表起保护作用。当要向task_lsit链表中加入或者删除元素时,内核内部就会锁定lock锁,当修改完成后,会释放lock锁。也就是说,lock自旋锁在对task_lsit与操作的过程中,实现了对等待队列的互斥访问。
2.task_list变量
task_list是一个双向循环链表,用来存放等待的进程。

等待队列的使用

在Linux中,等待队列的类型为struct wait_queue_head_t。内核提供了一系列的函数对struct wait_queue_head_t进行操作。下面将对等待队列的操作方法进行简要的介绍。
1.定义和初始化等待队列头
在Linux中,定义等待队列的方法和定义普通结构体的方法相同,定义方法如下:

struct wait_queue_head_t wait;

一个等待队列必须初始化才能被使用,init_waitqueue_head()函数用来初始化一个等待队列,其代码形式如下:

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITALIZER(name)

2.定义等待队列
Linux内核中提到了一个宏用来定义等待队列,该宏的代码如下:

#define DECLARE_WAITQUEUE(name, tsk) \wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

该宏用来定义并且初始化一个名为name的等待队列。
3.添加和移除等待队列
Linux内核中提供了两个函数用来添加和移除队列,这两个函数的定义如下:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()函数用来将等待队列元素wait添加到等待队列头q所指向的等待队列链表中。与其相反的函数是remove_wait_queue(),该函数用来将队列元素wait从等待队列q所指向的等待队列中删除。
4.等待事件
Linux内核中提供一些宏来等待相应的事件,这些宏的定义如下:

#define wait_event(wq, condition)
#define wait_event_timeout(wq, condition, ret)
#define wait_event_interruptible(wq, condition, ret)
#define wait_event_interruptible_timeout(wq, condition, ret)
  • wait_event宏的功能是,在等待队列中睡眠直到condition为真。在等待的期间进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量为真。每次进程被唤醒的时候都会检查condition的值。
  • wait_event_timeout宏与wait_event类似,不过如果所给的睡眠时间为负数则立即返回。如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0。
  • wait_event_interruptible宏与wait_event的区别是,调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态。在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回;否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码。如果condition为真,则返回0。
  • wait_event_interruptible_timeout宏与wait_event_timeout宏类似,不过如果睡眠期间被信号打断则返回ERESTARTSYS错误码。
    5.唤醒等待队列
    Linux内核中提供一些宏用来唤醒相应的队列中的进程,这些宏的定义如下:
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_interruptible(x)   __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
  • wake_up宏唤醒等待队列,可唤醒处于TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE状态的进程,这个宏和wait_event/wait_event_timeout成对使用。
  • wake_up_interruptible宏和wake_up()唯一的区别是,它只能唤醒TASK_INTERRUPTIBLE状态的进程。这个宏可以唤醒使用wait_event_interruptible、wait_event_interruptible_timeout宏睡眠的进程。

同步机制实验

本节将讲解一个使用等待队列实现的同步机制的实验,通过本节的实验,读者可以对Linux中的同步机制有一个较深的了解。

同步机制设计

进程同步机制的设计首先需要一个等待队列,所有等待一个事件完成的进程都挂接在这个等待队列中,一个包含队列的数据结构可以实现这种意图。这个数据结构的定义代码如下:

struct CustomEvent{
	int eventNum;  //事件号
	wait_queue_head_t *p; //系统等待队列首指针
	struct CustomEvent *next; //队列链指针
}

下面对这个结构体进行简要的解释:

  • 2行的eventNum表示进程等待的事件号
  • 3行,是一个等待队列,进程在这个等待队列中等待。
  • 4行,是连接这个结构体的指针。
    为了实现实验的意图,设计了两个指针分别表示事件链表的头部和尾部,这两个结构的定义如下代码所示。
CustomEvent *lpevent_head = NULL; //链头指针
CustomEvent *lpevent_end = NULL;  //链尾指针

每个事件由一个链表组成,每个链表中包含了等待这个事件的等待队列。这个结构下图所示。
在这里插入图片描述
为了实现实验的设计,定义了一个函数FindEventNum()从一个事件链表中找到某个事件对应的等待链表,这个函数的代码如下:

CustomEvent *FindEventNum(int eventNum, CustomEvent **prev)
{
	CustomEvent *tmp = lpevent_head;
	*prev = NULL;
	while(tmp)
	{
		if(tmp->eventNum == eventNum)
			return tmp;
		*prev = tmp;
		tmp = tmp->next;
	}
	return NULL;
}

下面对这个函数进行简要的介绍:

  • 1行,函数接收两个参数,第1个参数eventNum是事件的序号,第2个参数是返回事件的前一个事件。该函数找到所要的事件则返回,否则返回NULL。
  • 3行,将tmp赋值为事件链表的头部。
  • 4行,将prev指向NULL。
  • 5~11行,是一个while()循环,找到所要事件的结构体指针。
  • 7行,判断tmp所指向的事件号是否与eventNum相同,如果相同则返回,表示找到,否则继续沿着链表查找。
  • 10行,将tmp向后移动。
  • 12行,如果没有找到,则返回NULL值。
    为了实现实验的设计,定义了一个系统调用函数sys_CustomEvent_open(),该函数新分配了一个事件,并返回新分配事件的事件号,其函数的定义如下:
asmlinkage int sys_CustomEvent_open(int eventNum)
{
	CustomEvent *new;
	CustomEvent *prev;
	if(eventNum)
		if(!FindEentNum(eventNum, &prev))
			return -1;
		else
			return eventNum;
	else
	{
		new = (CustomEvent *)kmalloc(sizeof(CustomEvent), GFP_KERNEL);
		new->p = (wait_queue_head_t *)kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL);
		new->next = NULL;
		new->p->task_list.next = &new->p->task_list;
		new->p->task_list.prev = &new->p->task_list;
		if(!lpevent_head)
		{
			new->eventNum = 2; //从2开始按偶数递增事件号
			lpevent_end->next = lpevent_end = new;
			return new->eventNum;
		}
		else
		{
			//事件队列不为空,按偶数递增一个事件号
			new->eventNum = lpevent_end->eventNum + 2;
			lpevent_end->next = new;
			lpevent_end = new;
		}
		return new->eventNum;
	}
	return 0;
}

下面对该函数进行简要的介绍:

  • 1行,该函数用来建立一个新的事件,参数为新建立的事件号。
  • 3、4行,定义了两个事件的指针。
  • 5行,判断事件是否为0,如果为0,则重新创建一个事件。
  • 6~9行,根据事件号查找事件,如果找到返回事件号,如果没有找到返回-1。FindEventNum()函数根据事件号查找相应的事件。
  • 12~31行,用来重新分配一个事件。
  • 12行,调用kmalloc()函数新分配一个事件。
  • 13行,分配该事件对应的等待队列,将等待队列的任务结构体链接指向自己。
  • 17~22行,如果没有事件链表头,则将新分配的事件赋给事件链表头,并返回新分配的事件号。
  • 25~28行,如果已经没有事件链表头,则将新分配的事件连接到链表中。
  • 30行,返回新分配的事件号。
    下面定义了一个将进程阻塞到一个事件的系统调用函数,直到等待的事件被唤醒时,事件才退出。该函数的代码如下:
asmlinkage int sys_CustomEvent_wait(int eventNum)
{
	CustomEvent *tmp;
	CustomEvent *prev = NULL;
	if((tmp = FindEventNum(eventNum, &prev)) != NULL)
	{
		DEFINE_WAIT(wait); //初始化一个wait_queue_head
		//当前进程进入阻塞队列
		prepare_to_wait(tmp->p, &wait, TASK_INTERRUPTIBLE); 
		
		schedule(); //重新调度
		finish_wait(tmp->p, &wait); //进程被唤醒从阻塞队列退出
		return eventNum;
	}
	return -1;
}

下面对该函数进行简要的介绍:

  • 1行,函数实现了一个等待队列等待的系统调用。
  • 3,4行,定义了两个事件的指针。
  • 5行,通过eventNum找到事件结构体,如果查找失败,则返回-1。
  • 7行,定义并初始化一个等待队列。
  • 8行,将当前进程放入等待队列中。
  • 9行,重新调度新的进程。
  • 10行,当进程被唤醒时,进程从等待队列中退出。
  • 11行,返回事件号。
    有使进程睡眠的函数,就有使进程唤醒的函数。唤醒等待特定事件的函数是sys_CustomEvent_signal(),该函数的代码如下:
asmlinkage int sys_CustomEvent_signal(int eventNum)
{
	CustomEvent *tmp = NULL;
	CustomEvent *prev = NULL;
	if(!(tmp = FindEventNum(eventNum, &prev)))
		return 0;
	wake_up(tmp->p); //唤醒等待事件的进程
	return -1;
}

下面对该函数进行简要的介绍:

  • 1行,函数接收一个参数,这个参数是要唤醒的事件的事件号,在这个事件上等待的函数,都将被唤醒。
  • 1行,函数接收一个参数,这个参数是要唤醒的事件的事件号,在这个事件上等待的函数,都将被唤醒。
  • 2、3行,定义了两个结构体指针。
  • 5行,如果没有发现事件,则返回。
  • 7行,唤醒等待队列上的所有进程。
  • 8行,返回1,表示成功。
    定义了一个关闭事件的函数,该函数先唤醒事件上的等待队列,然后清除事件占用的空间。函数的代码如下:
asmlinkage int sys_CustomEvent_close(int eventNum)
{
	CustomEvent *prev=NULL;
	CustomEvent *releaseItem;
	if(releaseItem = FindEventNum(eventNum, &prev))
	{
		if(releaseItem == lpevent_end)
			lpevent_end = prev;
		else if(releaseItem == lpevent_head)
			lpevent_head = lpevent_head->next;
		else
			prev->next = releaseNum->next;
		sys_CustomEvent_signal(eventNum);
		if(releaseNum){
			kfree(releaseNum);
		}
		return releasNum;
	}
	return 0;
}

下面对该函数进行简要的介绍:

  • 1行,函数表示关闭事件。如果关闭失败返回0,否则返回关闭的事件号
  • 3、4行,定义了两个结构体指针。
  • 5行,找到需要关闭的事件。
  • 7行,如果是链表的最后一个事件,那么将lpevent_end指向前一个事件。
  • 9行,如果是链表中的第一个事件,那么将lpevent_head指向第二个事件。
  • 10行,如果事件是中间的事件,那么将中间的事件去掉,用指针连接起来。
  • 13行,唤醒需要关闭的事件。
  • 14行,清空事件占用的内存。
  • 18行,返回事件号。

实验验证

将以上的代码编译进内核,并用新内核启动系统。那么系统中就存在了4个新的系统调用。这4个新的系统调用分别是__NR_CustomEvetn_open、__NR_CustomEvent_wait、__NR_CustomEvent_signal和__NR_myevent_close。分别使用这4个系统调用编写程序来验证同步机制。
首先需要打开一个事件,完成这个功能的代码如下,该段代码打开看一个事件号为2的函数,然后退出。

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_open(int flag){
	return syscall(__NR_CustomEvent_open, flag);
}
int main(int argc, char **argv)
{
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_open(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

打开一个事件号为2的函数后,就可以在这个事件上将多个进程置为等待状态。将一个进程置为等待状态的代码如下,多次执行下面的代码,并传递参数2,会将进程放入事件2的等待队列中。

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_wait(int flag){
	return syscall(__NR_CustomEvent_wait, flag);
}
int main(int argc, char **argv)
{
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_wait(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

如果执行了上面的操作,那么会将多个进程置为等待状态,这时候调用下面的代码,并传递参数2,来唤醒多个等待事件2的进程。

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_wait(int flag){
	return syscall(__NR_CustomEvent_signal, flag);
}
int main(int argc, char **argv)
{
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_signal(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

当不需要一个事件时,可以删除这个事件,那么在这个事件上等待的所有进程,都会返回并执行,完成该功能的代码如下:

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int myevent_close(int flag){
	return syscall(__NR_CustomEvent_close, flag);
}
int main(int argc, char **argv)
{
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_close(atoi(argv[1]));
	printf("%d\n", i);
	return 0;
}

小结

阻塞和非阻塞在驱动程序中经常用到。阻塞在I/O操作暂时不能进行时,让进程进入等待队列。后者在I/O操作暂时不能进行时,立刻返回。这两种方式各有优劣,在实际应用中,应该有选择地使用。由于阻塞和非阻塞也是由等待队列来实现的,所以本章也概要地讲解了一些等待队列的用法。

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

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

相关文章

喜报!Coremail连续4届入选嘶吼网络安全产业图谱

7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》&#xff08;以下简称“图谱”&#xff09;。 Coremail成功入选本次图谱五大分类下14个细分领域&#xff0c;分别是安全自动化/协作&#x…

linux中数据库表相关查询

目录 1.显示所有职工的基本信息 2.查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号 3.求出所有职工的人数 4.列出最高工资和最低工资 5.列出职工的平均工资和总工资 6.创建一个只有职工号&#xff0c;姓名和参加工作的新表&#xff0c;名为工作日期表 7、显示所…

GPT(Generative Pre-Training)论文解读及实现(一)

1 GPT Framework 1.1 Unsupervised pre-training Given an unsupervised corpus of tokens U {u1, . . . , un}, we use a standard language modeling objective to maximize the following likelihood: 在给定语料上下文环境下&#xff0c;目标时最大化下面的语言模型&…

Java8 Stream 使用详解

Java8 Stream 使用详解 一、流的定义二、流的创建1. 通过集合创建流&#xff1a;2. 通过数组创建流&#xff1a;3. 通过Stream.of()创建流&#xff1a;4. 通过Stream.generate()创建流&#xff1a;5. 通过Stream.iterate()创建流&#xff1a;6. 通过文件、网络、IO流等方式来创…

Effective Java 高效编码阅读 笔记

《Effective Java》 有关于 Java 变成编程性能优化的 的书籍&#xff0c;看了不计其数 &#xff0c;但是每当读起来能让我眼前一亮的还得是 Effective Java 像这一类的书籍还有很多例如下面的 的两本&#xff0c;今天呢&#xff0c;来记录下此书的阅读笔记方便日后记忆&#xf…

【面试题34】什么是MVC,为什么要使用它

文章目录 一、前言二、MVC介绍2.1 模型&#xff08;Model&#xff09;2.2 视图&#xff08;View&#xff09;2.3 控制器&#xff08;Controller&#xff09; 三、MVC模式的优点四、总结 一、前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP面试专区。 计划将全覆盖PHP开发领域…

深入了解Spring Cloud的服务注册与发现组件Eureka

摘要&#xff1a;Spring Cloud是一个基于Spring框架的开发工具包&#xff0c;可以帮助开发人员构建基于微服务架构的分布式系统。其中的核心组件之一是Eureka&#xff0c;它提供了一套强大的服务注册与发现功能。本文将深入介绍Spring Cloud中的Eureka组件&#xff0c;包括其背…

MIT 6.S081 -- Networking

MIT 6.S081 -- Networking 引言计算机网络概述二层网络 --- Ethernet二/三层地址转换 --- ARP三层网络 --- Internet四层网络 --- UDP网络协议栈&#xff08;Network Stack&#xff09;Ring BufferReceive Livelock如何解决Livelock 引言 本文整理至: MIT 6.S081 2020 操作系统…

回归预测 | MATLAB实现WOA-CNN-BiLSTM鲸鱼算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现WOA-CNN-BiLSTM鲸鱼算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现WOA-CNN-BiLSTM鲸鱼算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计学习总结参考资料 预测效果 基本介…

nginx高并发架构

1.前言 对于高并发的流量web架构&#xff0c;单纯的使用nginx是不够用的&#xff0c;nginx做七层代理需要处理数据&#xff0c;在大并发的情况下对主机资源的消耗就非常厉害了&#xff0c;所以此情况下就引入了lvs&#xff0c;使用lvs的四层转发功能&#xff0c;四层转发不需要…

【MySQL】如何优化SQL查询的总体框架(详细版,关于如何优化数据库服务器从大到小详细说明了步骤)

文章目录 1 数据库服务器的优化步骤2 观察2.1 观察系统总体运行情况2.2 定位执行慢的 SQL&#xff1a;慢查询日志2.3 查看 SQL 执行成本&#xff1a;SHOW PROFILE2.4 分析查询语句&#xff1a;EXPLAIN&#xff08;重点掌握&#xff09;2.4.1 EXPLAIN各列作用2.4.2 EXPLAIN 的 t…

银行安全用电监管平台可行性研究及解决方案

2017年4月26日&#xff0c;国务院安全生产委员会印发《国务院安全生产委员会关于开展电气火灾综合治理工作的通知》&#xff08;安委〔2017〕4号&#xff09;&#xff0c;强调用三年时间综合治理电气火灾工作&#xff0c;提高社会单位发现和处置消防电气安全隐患能力&#xff0…

2023国自然会评:上会及分数解析,这几种情况本子容易被拿下?

国自然基金上会标准 今年的会评已经临近“重头戏”---面青地项目会议评审。 在国自然会评中&#xff0c;通过函评筛选出的科研工作者&#xff0c;经过会评筛选和评审&#xff0c;最终被评选出的项目将获得国自然会的资金支持。 国自然的会评&#xff0c;分为几个部分&#x…

美国访问学者怎么考驾照?

作为一个美国访问学者&#xff0c;你可能会想知道在美国如何考取驾照。在这篇文章中&#xff0c;知识人网小编将介绍美国的驾照考试流程和一些相关要求。 首先&#xff0c;作为一名访问学者&#xff0c;你需要了解美国各州对驾照的规定可能会有所不同。因此&#xff0c;在考取驾…

单片机尽力少用位域操作

1、在51单片机中少用uint32_t类型&#xff0c;查看汇编真的好多条指令&#xff0c;尽力避免少用。 2、在32位单片机中&#xff0c;u8、u16、u32类型操作起来基本没有什么影响&#xff0c;下图是我做的测试&#xff0c;可能测试不全面&#xff0c;按照当前测试&#xff0c;在32…

CVE-2023-28432-MinIO集群模式信息泄露漏洞流量分析

简介 MinIO是一个开源对象存储系统。 在其RELEASE.2023-03-20T20-16-18Z版本&#xff08;不含&#xff09;以前&#xff0c;集群模式部署下存在一处信息泄露漏洞&#xff0c;攻击者可以通过发送一个POST数据包获取进程所有的环境变量&#xff0c;其中就包含账号密码MINIO_SEC…

ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用

0. 前言 终于&#xff01;终于&#xff01;自个翻遍了网上的文章&#xff0c;加上对官网的文档和API的翻找&#xff0c;终于明白这玩意到底更新出了个啥出来&#xff01; 本文章会带你了解&#xff0c;使用 SpringDataES5.1 对 ES8.7 的【新增、修改、删除、多条件查询、聚合】…

MFC 工具栏中的按钮控件下拉式

有一个需求 工具栏中的按钮需要有一个下拉按钮&#xff0c;点击下拉按钮可以弹出一个子窗口来选择 显示该 TBSTYLE_EX_DRAWDDARROWS 扩展的样式设置&#xff0c;将箭头下方将显示。 DWORD dwExStyle TBSTYLE_EX_DRAWDDARROWS; m_toolbar.GetToolBarCtrl().SendMessage(TB_SE…

「深度学习之优化算法」(十一)鲸鱼算法

1. 鲸鱼算法简介 (以下描述,均不是学术用语,仅供大家快乐的阅读)   鲸鱼算法(Whale Optimization Algorithm)是根据鲸鱼围捕猎物的行为而提出的算法。鲸鱼是一种群居的哺乳动物,在捕猎时它们也会相互合作对猎物进行驱赶和围捕。鲸鱼算法提出时间并不长,也是一个新兴…

利用Gradio的UploadButton模块实现文件上传功能

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…