【北京迅为】《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.mzph.cn/pingmian/50662.shtml

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

相关文章

电力系统 | 发电、输电、变电、配电、用电介绍 | 一度电从电厂发出来到用户终端需要经历哪些环节 | 变电站建在哪里

文章目录 一、一度电从电厂发出来到用户终端需要经历哪些环节&#xff1f;二、发电、变电、输电、配售电和用电过程介绍三、变电站建在哪里&#xff1f; 一、一度电从电厂发出来到用户终端需要经历哪些环节&#xff1f; 电力系统是由发电、变电、输电、配售电和用电等环节组成的…

leetcode106. 从中序与后序遍历序列构造二叉树,力扣105姊妹题

leetcode106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…

活动报名小程序

#活动报名工具# # 活动报名小程序 ## 项目简介 一款通用的活动报名工具&#xff0c;包含活动展示&#xff0c;微信支付&#xff0c;订单管理&#xff0c;分享评价等功能。 品客聚精彩&#xff0c;有你才精彩&#xff01;不只有线下活动还可以进行线上裂变活动。 …

UE4-构建光照后导入的静态网格体变黑

当我们将我们的静态网格体导入到项目当中的时候&#xff0c;此时我们进行重新构建光照&#xff0c;我们在从新构建完光照后&#xff0c;会发现我们的静态网格体全部变黑了&#xff0c;此时是因为没有设置光照贴图分辨率和坐标索引引起的。 将General Settings中的L…

Cmake生成的Xcode工程相对路径与绝对路径的问题

Cmake生成的Xcode工程相对路径与绝对路径的问题 文章目录 Cmake生成的Xcode工程相对路径与绝对路径的问题前言修改.pbxproj文件验证工程小结 前言 由于Cmake的跨平台的自动化构建的方便性以及他广泛应用于编译过程的管理&#xff0c;在开发过程中难免用到Cmake。我也使用Cmake…

framework直播学习笔记--安卓如何实现Launcher启动应用全部变自由窗口Freeform模式

背景&#xff1a; 前些天在学员在学员群里有聊到一个需求&#xff0c;那就是把手机桌面点击应用图标后&#xff0c;不是进行全屏显示&#xff0c;而是都进行自由窗口显示。这个其实有点类似我们windows电脑打开app&#xff0c;每个app都是一个非全屏的窗口&#xff0c;而且可以…

从 Batch Norm 到 SGD 隐藏的内容

我们仍然不了解机器学习的哪些方面 欢迎来到雲闪世界。令人惊讶的是&#xff0c;机器学习中的一些基本主题仍然不为研究人员所知&#xff0c;尽管它们很基础且常用&#xff0c;但却似乎很神秘。机器学习的有趣之处在于我们构建了可以工作的东西&#xff0c;然后弄清楚它们为什么…

硅纪元视角 | 摩根大通拥抱AI:LLM Suite开启金融行业新篇章!

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

华为OD机试 - 计算三叉搜索树的高度 (python 2024年C卷D卷)

华为OD机试&#xff08;C卷D卷&#xff09;2024真题目录(Java & c & python) 题目描述 定义构造三叉搜索树规则如下&#xff1a; 每个节点都存有一个数&#xff0c;当插入一个新的数时&#xff0c;从根节点向下寻找&#xff0c;直到找到一个合适的空节点插入。查找的…

【字母异位词分组】python刷题记录

R2-字符串篇 class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:#哈希表分组#把每种字母出现次数相同的字符串分到同一组#sort一下好像能分dictdefaultdict(list)for s in strs:asorted(s)#sorted(s)相同的字符串分到同一组dict[.join(a)].appen…

Java 基础 and 进阶面试知识点(超详细)

一个 Java 文件中是否可以存在多个类&#xff08;修饰类除外&#xff09;&#xff1f; 一个 Java 文件中是可以存在多个类的&#xff0c;但是一个 Java 文件中只能存在一个 public 所修饰的类&#xff0c;而且这个 Java 文件的文件名还必须和 public 所修饰类的类名保持一致&a…

如何应对零日威胁:漏洞扫描

零日威胁正变得比以往任何时候都更加危险。5月底至6月初&#xff0c;恶意行为者通过零日攻击接管了众多名人和品牌的TikTok账户。用户声称在打开一条私信后便丧失了账户的控制权。而用于攻击的恶意软件能够在用户不下载或安装任何程序的情况下感染设备。 目前尚不清楚此次事件的…

linux系统巡检及shell脚本

目录 步骤1 系统巡检基本命令 CPU 内存 磁盘 进程 网络、流量 步骤2 shell脚本编写 awk awk常用内置变量 awk正则 实例一 实例二 实例三 实例四 实例五 sed 选项与参数 例一 例二 例三 例四 例五 例六 例七 grep 主要参数 常用用法 例一 例二 例三…

vue 开发环境配置

1. nvm 安装 在 github上下载 最新的 nvm 包 https://github.com/coreybutler/nvm-windows/releases或者在 csdn 上下载&#xff08;从github上迁移&#xff0c;方便下载&#xff09;https://download.csdn.net/download/u011171506/89585197 下载后不用修改任何配置&#x…

2024年【广东省安全员B证第四批(项目负责人)】考试报名及广东省安全员B证第四批(项目负责人)模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员B证第四批&#xff08;项目负责人&#xff09;考试报名根据新广东省安全员B证第四批&#xff08;项目负责人&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将广东省安全员B证第四批&#x…

系统架构师(每日一练7)

每日一练 1.关于网络延迟正确的是()。答案与解析 A.在对等网络中&#xff0c;网络的延迟大小与网络中的终端数量无关 B.使用路由器进行数据转发所带来的延迟小于交换机, C.使用internet服务器可最大程度地减小网络延迟 D.服务器延迟的主要影响因素是队列延迟和磁盘10延迟 2.以…

【ESP32 IDF SPI硬件驱动W25Q64】

目录 SPISPI介绍idf配置初始化配置通信 驱动代码 SPI SPI介绍 详细SPI介绍内容参考我之前写的内容【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】 idf配置 初始化配置 spi_bus_initialize() 参数1 &#xff1a;spi几&#xff0c;例如spi2,spi3 参数2&#xff1a;…

GitHub Actions是什么

目录 GitHub Actions是什么 GitHub Actions的使用方法 示例 注意事项 GitHub Actions配置文件中-工作流的 :Workflow 一、自动化任务执行 二、规范团队协作 三、灵活配置和定制 四、提高开发效率 五、集成GitHub生态 六、可复用性和共享性 仓库中的“Actions”部分…

mac 使用ssh 密钥登录linux 服务器

本地操作 1. 生成SSH密钥对 # your_emailexample.co 自行定义即可 ssh-keygen -t rsa -b 4096 -C "your_emailexample.com"这会提示你输入文件保存位置和密码(密码可以留空)&#xff1a; Generating public/private rsa key pair. Enter file in which to save the…

Postman入门(三):创建post请求及请求参数

创建post请求Raw data&#xff1a; 请求方式&#xff1a;Post 请求体&#xff1a;raw,选择json格式 参数{}内数据&#xff0c;点击send即可发送请求 创建post请求Form-data: 请求方式&#xff1a;Post 请求体&#xff1a;form-data, 参数&#xff1a;在form-data内&#…