【北京迅为】《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的值。
相关文章:

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十九章 等待队列
i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...

35.【C语言】详解函数递归
目录: 定义 作用 例子1~3 拓展学习 趣味练习 1.定义:函数自己调用自己(递推回归) int main() {main()return 0; } 这样容易死循环,导致爆栈(Stack Overflow) 所以需要设立限制条件,使执行时越来越接近条…...

【机器学习】智驭未来:机器学习如何重塑制造业的转型与升级
📝个人主页🌹:Eternity._ 🌹🌹期待您的关注 🌹🌹 ❀目录 🔍1. 引言📒2. 机器学习重塑制造业生产流程🌸预测性维护:减少停机时间,提高设…...

Python爬虫(5) --爬取网页视频
文章目录 爬虫爬取视频指定url发送请求UA伪装请求页面 获取想要的数据解析定位定位音视频位置 存放视频完整代码实现总结 爬虫 Python 爬虫是一种自动化工具,用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持(如 requ…...

【Unity】关于Luban的简单使用
最近看了下Luban导出Excel数据的方式,来记录下 【Unity】关于Luban的简单使用 安装Luban开始使用UnityLubanC# 扩展 安装Luban Luban文档:https://luban.doc.code-philosophy.com/docs/beginner/quickstart 1.安装dotnet sdk 8.0或更高版本sdk 2.githu…...

企业公户验证API如何使用JAVA、Python、PHP语言进行应用
在纷繁复杂的金融与商业领域,确保每笔交易的安全与合规是至关重要的。而企业公户验证API,正是这样一位默默守护的数字卫士,它通过智能化的手段,简化了企业对公账户验证流程,让繁琐的审核变得快捷且可靠。 什么是企业公…...

杰发科技Bootloader(2)—— 基于7840的Keil配置地址
序 在7840的sample代码里面有一个简单的Boot跳转APP的示例 PFlash地址从0开始 DFlash的地址从1000000开始 Boot解析 他的boot地址配置为0 Boot的代码主要是这几行,主要作用就是Flash的跳转 int main(void) {SystemClock_Config();InitDebug();printf("demo…...

cmd常用命令
在Windows操作系统中,CMD(Command Prompt)是一个强大的命令行工具,允许用户通过键入命令来执行各种系统级操作。以下是一些常用的CMD命令及其功能: 文件与目录管理 dir:显示当前目录下的文件和子目录列表。…...

PCIe 以太网芯片 RTL8125B 的 spec 和 Linux driver 分析备忘
1,下载 RTL8125B driver 下载页: https://www.realtek.com/Download/List?cate_id584 2,RTL8125B datasheet下载 下载页: https://file.elecfans.com/web2/M00/44/D8/poYBAGKHVriAHnfWADAT6T6hjVk715.pdf3, 编译driver 解压: $ tar xj…...

Python tkinter Menu菜单组件详解
好久没有更新了,今天我来领大家熟悉一下Menu组件 1.认识、了解Menu 什么是Menu menu组件是tkinter中的菜单组件,通过该组件,开发者可以为窗口设计菜单和工具栏等。(ttk还提供了treeview树形菜单,python遍历目录的两种…...

谷粒商城实战笔记-46-商品服务-API-三级分类-配置网关路由与路径重写
文章目录 一,准备工作1,新增一级菜单2,新增二级菜单 二,前端树形界面开发1,开发分类展示组件 三,远程调用接口获取商品分类数据1,远程调用2,路由配置 错误记录 本节的主要内容&#…...

简要了解sql注入
sql注入安全测试中危害 数据库中的数据,对数据库数据进行操作(查询、删除等);网站的权限,找到注入点后可后门写入; sql注入产生原理详细分析 可控变量,带入数据库查询,变量未存在…...

Java 扫雷游戏
程序分析 使用Java编写的扫雷游戏界面程序,主要内容总结如下: Frame类继承自JFrame,构建了扫雷游戏的界面。 包含文本框text、标签nowBomb和setBomb、按钮start、面板MenuPamel和bombPanel等组件。通过jbInit方法进行初始化设置,…...

vue3 命令运行窗口暴露网络地址,以及修改端口号
一般情况下这里的地址是隐藏的 这里加上 --host 可以暴露网络地址,再加上--port --8080 就可以将端口号修改为8080(修改后边的数字就可以修改为你想要的端口号)...

由CANoe自带协议栈在TCP断开连接时同时发送两条FIN报文引起的注意事项
在我写这篇文章CAPL如何在底层模拟TCP Server端断开TCP连接时,我发现了一个奇怪的现象。我为了使用CAPL组装报文的方式实现TCP Server断开连接的过程,插入一个网络节点作为Client端。为了让Client能够发起连接和发起断开连接,给网络节点配置了独立的TCP/IP Stack,也就是CAN…...

FastGPT部署和接入使用重排模型bce-reranker-base
bce-reranker简介 bce-reranker 是一种专门用于信息检索和自然语言处理领域中的重排序(reranking)模型。这种模型由北京智源人工智能研究院(BAAI)开发,是 BGE(BAAI General Embedding)系列的一部分。BGE 系列模型专注于提供通用的嵌入表示,而 bce-reranker 则更进一步…...

Android笔试面试题AI答之线程Handler、Thread(2)
答案仅供参考,来自 讯飞星火大模型 目录 1.Android多线程间通信和多进程之间通信有什么不同,分别怎么实现?2.请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系?3.Android 线程间通信有哪几种方式?4.子线程发消息…...

某某物联rabbitmqhttp二轮充电桩协议充电协议对接
对接方式概述: 1)请求采用 http 协议方式,推送数据采用 amqp(默认 rabbitmq)点对点消息队 列方式。 2)消息队列连接信息,需贵方完善。 1 hostIp: 2 virtualHost: 3 userName: 4 pass…...

黑马JavaWeb企业级开发(知识清单)03——HTML实现正文:排版(音视频、换行、段落)、布局标签(div、span)、盒子模型
文章目录 前言一、正文排版1. 视频标签: < video >2. 音频标签: < audio >3. 换行标签: < br >4. 段落标签 < p >5. vscode实现 二、布局1. 盒子模型2. 布局标签< div >和< span >3. VScode实现 三、源代码和运行结果总结 前言 本篇文章是…...

Java | Leetcode Java题解之第283题移动零
题目: 题解: class Solution {public void moveZeroes(int[] nums) {int n nums.length, left 0, right 0;while (right < n) {if (nums[right] ! 0) {swap(nums, left, right);left;}right;}}public void swap(int[] nums, int left, int right)…...

Django REST Framework(十三)视图集-GenericViewSet
Django REST Framework 中,ModelViewSet 和 ReadOnlyModelViewSet 提供了快速实现常见视图操作的便捷方法。它们分别继承自 GenericViewSet 并组合了多个 Mixin 类,使得视图的编写变得更加简单。 ModelViewSet ModelViewSet 继承自 GenericViewSet&…...

《0基础》学习Python——第二十四讲__爬虫/<7>深度爬取
一、深度爬取 深度爬取是指在网络爬虫中,获取网页上的所有链接并递归地访问这些链接,以获取更深层次的页面数据。 通常,一个简单的爬虫只会获取到初始页面上的链接,并不会进一步访问这些链接上的其他页面。而深度爬取则会不断地获…...

Python Pygame制作简单五子棋游戏
代码参考自:https://blog.csdn.net/weixin_43918046/article/details/119521845 新增功能:1任意棋盘大小;2.任意棋子连线 # 棋盘大小 [670, 670] # 棋盘行列 15*15 import pygame from pygame.locals import QUIT, KEYDOWN import numpy as…...

JS+H5在线文心AI聊天(第三方接口)
源码在最后面 调用的不是文心官方接口 可以正常聊天 有打字动画 效果图 源代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-s…...

kafka源码阅读-ReplicaStateMachine(副本状态机)解析
概述 Kafka源码包含多个模块,每个模块负责不同的功能。以下是一些核心模块及其功能的概述: 服务端源码 :实现Kafka Broker的核心功能,包括日志存储、控制器、协调器、元数据管理及状态机管理、延迟机制、消费者组管理、高并发网络…...

【MetaGPT系列】【MetaGPT完全实践宝典——如何定义单一行为多行为Agent】
目录 前言一、智能体1-1、Agent概述1-2、Agent与ChatGPT的区别 二、多智能体框架MetaGPT2-1、安装&配置2-2、使用已有的Agent(ProductManager)2-3、拥有单一行为的Agent(SimpleCoder)2-3-1、定义写代码行为2-3-2、角色定义2-3…...

Kolla-Ansible的确是不支持CentOS-Stream系列产品了
看着OpenStack最新的 C 版本出来一段时间了,想尝个鲜、用Kolla-Ansible进行容器化部署,结果嘛。。。 根据实验结果,自OpenStack Bobcat版本开始,Kolla-Ansible就适合在CentOS系列产品上部署了,通过对 Bobcat和Caracal…...

IDEA启动C:\Users\badboy\.jdks\corretto-17.0.7\bin\java.exe -Xmx700m报错
这篇文章写的就很详细了(IDEA启动C:\Users\badboy\.jdks\corretto-17.0.7\bin\java.exe -Xmx700m报错_error occurred during initialization of vm failed -CSDN博客)...

ctfshow298-300(java信息泄露,代码审计)
Web298 代码审计 这里看到getVipStatus方法,获得了获取flag的条件就是user等于admin,password等于ctfshow Poc: https://d036a90d-ac1c-4de1-9b0b-86f52d2586b9.challenge.ctf.show/ctfshow/login?usernameadmin&passwordctfshow Web299 打开页面…...

Java 基础 and 进阶面试知识点(超详细)
一个 Java 文件中是否可以存在多个类(修饰类除外)? 一个 Java 文件中是可以存在多个类的,但是一个 Java 文件中只能存在一个 public 所修饰的类,而且这个 Java 文件的文件名还必须和 public 所修饰类的类名保持一致&a…...