【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十九章 等待队列

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263


五十九章 等待队列

本章导读

阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞 IO,以及如何在驱动程序中处理阻塞与非阻塞

59.1章节讲解了阻塞和非阻塞IO的概念

59.2章节编写了驱动程序,在iTOP-IMX8MM开发板上为例,实现了非阻塞的按键驱动

59.3章节编写应用测试程序

59.4章节运行测试,发现CPU占用率很高

59.5章节在59.2章节的基础上编写驱动程序,用等待队列阻塞,当按键按下的时候,再去读取value的信息,这样做是比较专业的,而且可以极大的减少cpu的占用率。

本章内容对应视频讲解链接(在线观看):

等待队列  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=38

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列”路径下。

59.1 阻塞和非阻塞IO

59.1.1 阻塞与非阻塞简介

阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。

被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程

在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

在阻塞访问时,不能获取资源的进程将进入休眠,它将 CPU 资源“礼让”给其他进程。因为阻塞的进程会进入休眠状态,所以必须确保有一个地方能够唤醒休眠的进程,否则,进程就真的“寿终正寝”了。唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断尝试,直到可以进行 I/O。阻塞访问如图所示:

若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_read() 、 xxx_write

() 等操作应立即返回,read() 、write() 等系统调用也随即被返回,应用程序收到-EAGAIN 返回值。

 

应用程序可以使用如下所示示例代码来实现阻塞访问:

int fd;

int data = 0;

fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

ret = read(fd, &data, sizeof(data)); /* 读取数据 */

可以看出对于设备驱动文件的默认读取方式就是阻塞式的,所以我们前面所有的例程测试 APP 都是采

用阻塞 IO。

如果应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

int fd;

int data = 0;

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */

ret = read(fd, &data, sizeof(data)); /* 读取数据 */

使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。

 

使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。

59.1.2 等待队列

当我们进程去访问设备的时候,经常需要等待有特定事件发生以后再继续往下运行,这个时候就需要在驱动里面实现当条件不满足的时候进行休眠,当条件满足的时候在由内核唤醒进程。在 Linux 驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。即满足先进先出的形式 FIFO。

举个例子,比如说我现在去食堂打饭,阿姨和我说现在没有饭,你需要等一会,等我做好了我再叫你,那么我当前不能获得资源,我被阻塞在这儿了,那么等待队列就是让我们阻塞在这儿,然后等特定的事件发生以后,再继续运行。那么等待队列阻塞在这儿的这件事情就相当于阿姨和我们说现在没有饭,你需要等一会。为什么我们要先讲完中断以后再讲等待队列呢?举个例子来说,比如说阿姨和你说现在没饭,你需要在旁边等一会,等我做好了我再叫你,如果说阿姨做完了不叫你,你又睡着了,那么你今天是不是吃不上饭了,所以说在我们阻塞访问的时候不能获得资源的进程,将进入休眠状态,他将cpu的资源全部让给别的进程,必须保证有一个地方可以唤醒休眠进程,否则的话将会长睡不醒。进程唤醒最大可能的地方发生在中断里面,伴随着一个中断的发生我们可以唤醒该进程,对应的事件是阿姨说饭好了,小王你过来打吧。所以说,我们学习等待队列在中断之后,这样用等待队列可以极大的降低cpu的占用率。

Linux 内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心

的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。

等待队列头和等待队列项中都包含一个 list_head 类型的域作为”连接件”。它通过一个双链表和把等待 task

的头,和等待的进程列表链接起来。

59.1.3 等待队列头

等待队列头就是一个等待队列的头部, 每个访问设备的进程都是一个队列项, 当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。

等待队列头使用结构体wait_queue_head_t来表示,这个结构体定义在文件include/linux/wait里面,结构体内容如下:

struct __wait_queue_head {

spinlock_t lock; //自旋锁

    struct list_head task_list; //链表头

};

typedef struct __wait_queue_head wait_queue_head_t;

类型名是wait_queue_head_t,只需要记住这个即可。

定义一个等待队列头:

wait_queue_head_t   test_wq;  //定义一个等待队列的头

定义等待队列头以后需要初始化,可以使用init_waitqueue_head函数初始化等待队列头, 函数原型如下:

函数

void init_waitqueue_head(wait_queue_head_t *q)

q

wait_queue_head_t 指针

功能

动态初始化等待队列头结构

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。

DECLARE_WAIT_QUEUE_HEAD (wait_queue_head_t *q);

59.1.4 等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就

要将这些进程对应的等待队列项添加到等待队列里面。结构体 wait_queue_t 表示等待队列项,结构体内容如下:

struct __wait_queue {

unsigned int flags;

void *private;

wait_queue_func_t func;

struct list_head task_list;

};

typedef struct __wait_queue wait_queue_t;

使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,宏的内容如下:

DECLARE_WAITQUEUE(name, tsk)

name 就是等待队列项的名字,tsk 表示这个等待队列项属于哪个任务(进程),一般设置为 current , 在

Linux 内核中 current 相当于一个全局变量,表示当前进程。因此 DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。

59.1.5 添加/删除队列

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到

等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中

移除即可,等待队列项添加队列函数如下所示:

函数

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

q

等待队列项要加入的等待队列头。

wait

要加入的等待队列项

返回值

功能

从等待队列头中添加队列

等待队列项移除队列函数如下:

函数

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

q

要删除的等待队列项所处的等待队列头

wait

要删除的等待队列项

返回值

59.1.6 等待唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数

void wake_up(wait_queue_head_t *q)           //功能:唤醒所有休眠进程

void wake_up_interruptible(wait_queue_head_t *q)//功能:唤醒可中断的休眠进程

参数 q 就是要唤醒地等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。

wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE状态的进程,而wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。

 

59.1.7 等待事件

除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中

的进程,相关函数:

#define wait_event(wq, condition)

do {

if (condition)

break;

__wait_event(wq, condition);

} while (0)

wait_event(queue,condition);等待以 queue 为等待队列头等待队列被唤醒,condition 必须满足,否则阻塞

wait_event_interruptible(queue,condition);可被信号打断

wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论 condition 是否满足,都要返回

wait_event_interruptible_timeout(queue,condition,timeout)

wait_event()宏

功能:不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到condition变成真,被内核唤醒。

wait_event_interruptible() 函数

功能:可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到condition变成真被内核唤醒或被信号打断唤醒。

wait_event_timeout() 宏:

也与 wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且 condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回 0.

wait_event_interruptible_timeout() 宏:

与 wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回 ERESTARTSYS 错误码.

wait_event_interruptible_exclusive() 宏:

同样和 wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程

注意:调用的时要确认condition 值是真还是假,如果调用condition为真,则不会休眠。

59.2 编写驱动程序

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列\001”路径下。

我们以IMX8MM开发板为例,在Ubuntu的/home/topeet/imx8mm/16/001目录下新建driver.c,编写驱动代码如下所示;

/** @Author: topeet* @Description: 等待队列实验*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/io.h>//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
//GPIO 编号
int gpio_nu;
//用来模拟管脚的状态
int value = 0;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{value = !value;return IRQ_RETVAL(IRQ_HANDLED);
}
int misc_open(struct inode *node, struct file *file)
{printk("hello misc_open \n");return 0;
}int misc_release(struct inode *node, struct file *file)
{printk("hello misc_release bye bye\n");return 0;
}static ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{if (copy_to_user(ubuf, &value, sizeof(value)) != 0){printk("copy_to_user error\n");return -1;}return 0;
}
//文件操作集
struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "test_wq",.fops = &misc_fops,
};
/*** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           
*/
int led_probe(struct platform_device *pdev)
{int ret = 0;printk("led_probe\n");//of_find_node_by_path函数通过路径查找节点,/test_key是设备树下的节点路径test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){printk("of_find_node_by_path is error\n");return -1;}//of_get_named_gpio函数获取 GPIO 编号gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_named_gpio is error\n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq \n");return -1;}//注册杂项设备ret = misc_register(&misc_dev);if (ret < 0){printk("misc_register is error\n");return -1;}printk("misc_register is successd \n");return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove \n");return 0;
}
const struct platform_device_id led_idtable = {.name = "led_test",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2. 在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");return ret;}printk("platform_driver_register ok \n");return 0;
}static void led_driver_exit(void)
{printk("gooodbye! \n");free_irq(irq, NULL);misc_deregister(&misc_dev);platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

59.3 编写应用程序

在Ubuntu的/home/topeet/imx8mm/16/001目录下我们编写应用程序app.c,如下图所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int fd;int value;//打开设备节点fd = open("/dev/test_wq",O_RDWR);if(fd < 0){//打开设备节点失败perror("open error \n"); return fd;}while(1){read(fd,&value,sizeof(value));      printf("value is %d \n",value); }close(fd);return 0;
}

编译应用程序,如下图所示:

59.4 运行测试

我们将刚刚编写的驱动代码编译为驱动模块,如下图所示:

我们进入共享目录并且加载驱动模块,如下图所示: 

运行应用程序,串口调试信息会不停的打印value值是0,如下图所示:

 

我们按底板上的音量+按键时,value值取反,变为1,如下图所示: 

我们重新编译将应用程序app后台运行,然后输入top查看内存占用率,如下图所示:

 

 

59.5 优化方案

在59.4章节中,如上图所示,app的cpu占用率高达99%,这样肯定是不行的,别的程序是不能运行的,所以说我们要用等待队列阻塞,当按键按下的时候,再去读取value的信息,这样做是比较专业的,而且可以极大的减少cpu的占用率。我们在part1代码的基础上进行修改,代码如下所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\016-等待队列\002”路径下。

/** @Author: topeet* @Description: 等待队列实验*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/io.h>//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
//GPIO 编号
int gpio_nu;
//用来模拟管脚的状态
int value = 0;
//等待队列头的定义和初始化。
DECLARE_WAIT_QUEUE_HEAD(key_wq);
//定义等待队列标志位
int wq_flags = 0;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{//将等待队列置1,然后唤醒等待队列wq_flags = 1;wake_up(&key_wq);//将value取反value =!value;return IRQ_HANDLED;
}int misc_open(struct inode *node, struct file *file)
{printk("hello misc_open \n");return 0;
}
int misc_release(struct inode *node, struct file *file)
{printk("hello misc_release bye bye\n");return 0;
}
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{//阻塞,可被wake_up唤醒wait_event_interruptible(key_wq, wq_flags);wq_flags = 0;if (copy_to_user(ubuf, &value, sizeof(value)) != 0){printk("copy_to_user error\n");return -1;}return 0;
}
//文件操作集
struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read};
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "test_wq",.fops = &misc_fops,
};/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq = gpio_to_irq(gpio_nu);// irq =irq_of_parse_and_map(test_device_node,0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}//注册杂项设备ret=misc_register(&misc_dev);if(ret<0){printk("misc_register is error \n");return -1;}printk("misc_register is success \n");return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

我们还是像part1实验一样,将驱动编译为驱动模块,应用程序还是使用part1编译好的app,我们加载驱动模块,如下图所示:

如上图所示,运行了应用程序以后,我们触摸以下屏幕,按一次按键打印一次value的值。 

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

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

相关文章

Java数据结构与算法--链表(Linked List)

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;Java SE关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 深入了解链表&#xff1a; 链表是一种常见的数据结构&#xff0c;它由一系列节点…

【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !

目录 C语言指针精讲1. 什么是指针&#xff1f;1.1 指针的内存模型1.1.1 指针演示输出 1.2 指针运算1.2.1 指针算术运算输出1.2.2 指针与数组的关系输出 1.3 指针类型1.3.1 不同类型的指针示例输出1.3.2 void 指针输出 1.4 指针与内存管理动态内存分配输出 1.5 指针与内存泄漏1.…

Android进阶之NDK开发,保姆级教程

目录 前言NDK下载CMake文件创建指定ABI架构编写CMake文件编写JNI方法Java调用CC调用Java 生成JNI头文件实现对应C方法编译so文件编写demo验证运行效果总结 前言 作为Android应用开发从业者来说&#xff0c;掌握NDK开发是必备技能之一&#xff0c;本文将从NDK环境下载&#xff…

均匀圆形阵列原理及MATLAB仿真

均匀圆形阵列原理及MATLAB仿真 目录 前言 一、均匀圆阵原理 二、圆心不存在阵元方向图仿真 三、圆心存在阵元方向图仿真 四、MATLAB仿真代码 总结 前言 本文详细推导了均匀圆形阵列的方向图函数&#xff0c;对圆心不放置阵元和圆心放置阵元的均匀圆形阵列方向图都进行了仿…

PySide(PyQt)的QPropertyAnimation(属性动画)

学不完&#xff0c;根本学不完:(&#xff0c;感觉逐渐陷入了学习深渊。。。 QPropertyAnimation 是 PySide(PyQt) 中一个用于在时间轴上平滑地改变对象属性的类。它常用于制作动画效果&#xff0c;比如移动、缩放或改变透明度等。 基本概念 QPropertyAnimation 是 Qt …

03。正式拿捏ArkTS语言第一天

1, 打印日志命令 &#xff1a; console.log() 2, 三种基本数据类型&#xff1a; number 数字类型 &#xff08;数字&#xff09; string 字符串类型&#xff08;例如&#xff1a;“我是字符串”&#xff09; boolean 布尔类型 (true 或者 false) ***…

昇思25天学习打卡营第24天|RNN实现情感分类

RNN实现情感分类学习总结 概述 情感分类是自然语言处理领域的重要任务&#xff0c;主要用于识别文本中表达的情绪。本文使用MindSpore框架实现基于RNN的情感分类模型&#xff0c;示例包括&#xff1a; 输入: “This film is terrible” -> 标签: Negative输入: “This fi…

飞牛爬虫FlyBullSpider 一款简单方便强大的爬虫,限时免费 特别适合小白!用它爬下Boss的2024年7月底Java岗位,分析一下程序员就业市场行情

一、下载安装FlyBullSpider 暂时支持Window,现在只在Win11上做过测试 1 百度 点击百度网盘 下载 链接&#xff1a;https://pan.baidu.com/s/1gSLKYuezaZgd8iqrXhk8Kg 提取码&#xff1a;Fly6 2 csdn https://download.csdn.net/download/fencer911/89584687 二、体验初…

Linux shell编程学习笔记66:ping命令 超详细的选项说明

0 前言 网络信息是电脑网络信息安全检查中的一块重要内容&#xff0c;Linux和基于Linux的操作系统&#xff0c;提供了很多的网络命令&#xff0c;今天我们研究最常用的ping命令。 1 ping命令 的功能、格式和选项说明 1.1 ping命令 的功能 简单来说&#xff0c; ping 命令 会…

Linus: vim编辑器的使用,快捷键及配置等周边知识详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 vim的安装创建新用户 adduser 用户名Linus是个多用户的操作系统是否有创建用户的权限查看当前用户身份:whoami** 怎么创建设置密码passwdsudo提权(sudo输入的是用户…

日记审计遵守合规安全要求

一、什么是日志审计系统 日记审计系统是一种用于记录、监视和分析系统日志的工具或系统。它主要用于帮助组织实时监控与分析各种事件和行为的日志记录&#xff0c;以便检测潜在的安全威胁&#xff0c;了解系统性能和进行故障排除。日志审计系统通常能够收集、存储和分析来自各…

.env.local 配置本地环境变量 用于团队开发

.env.local 用途&#xff1a;.env.local 通常用于存储本地开发环境中的环境变量。这些变量可能包括敏感数据或特定于单个开发者的设置&#xff0c;不应该被提交到版本控制系统中。优先级&#xff1a;在大多数框架中&#xff0c;.env.local 文件中的变量会覆盖其他 .env 文件中…

vite环境下使用bootstrap

环境 nodejs 18 pnpm 初始化 pnpm init pnpm add -D vite --registry http://registry.npm.taobao.org pnpm add bootstrap popperjs/core --registry http://registry.npm.taobao.org pnpm add -D sass --registry http://registry.npm.taobao.org新建vite.config.js cons…

opencv 按键开启连续截图,并加载提示图片

背景图小图 键盘监听使用的是pynput 库 保存图片时使用了年月日时分秒命名 原图&#xff1a; from pynput import keyboard import cv2 import time# 键盘监听 def on_press(key):global jieglobal guanif key.char a:jie Trueelif key.char d:jie Falseelif key.char…

SpringBoot启动命令过长

Error running DromaraApplication: Command line is too long. Shorten command line for DromaraApplication or also for Spring Boot default configuration?

LeetCode 热题 HOT 100 (010/100)【宇宙最简单版】

【链表】No. 0206 反转链表 【简单】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xf…

轻松学EntityFramework Core--模型创建

一、使用代码优先&#xff08;Code-First&#xff09;创建模型 Code-First 方法是 EF Core 提供的一种用于定义模型的方式&#xff0c;它允许开发人员通过编写 C# 类来定义数据库模式&#xff0c;再通过迁移命令生成数据库表。下面我们来一起看一下代码优先如何使用。 1.1、创…

问题记录-SpringBoot 2.7.2 整合 Swagger 报错

详细报错如下 报错背景&#xff0c;我将springboot从2.3.3升级到了2.7.2&#xff0c;报了下面的错误&#xff1a; org.springframework.context.ApplicationContextException: Failed to start bean documentationPluginsBootstrapper; nested exception is java.lang.NullPo…

MySQL练手 --- 1789. 员工的直属部门

题目链接&#xff1a;1789. 员工的直属部门 这道题虽然是个简单题&#xff0c;但是"坑"倒是不少&#xff0c;所以记录一下 思路&#xff1a; 题目要干&#xff1a; 一个员工可以属于多个部门。当一个员工加入超过一个部门的时候&#xff0c;他需要决定哪个部门是…

导航网站WP主题/WP黑格导航主题BlackCandy-简约酷黑色高逼格+焕然一新的UI设计

源码简介&#xff1a; 导航网站WP主题-WP黑格导航主题BlackCandy&#xff0c;它有着简约酷黑色高逼格&#xff0c;而且有焕然一新的UI设计。它是一个简约漂亮的 WordPress 自媒体主题。黑格网址导航主题&#xff0c;自适应电脑端和手机端。 BlackCandy-V2.0这次全新升级了&am…