【北京迅为】《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://xiahunao.cn/news/3269931.html

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

相关文章

远程项目调试-informer2020

informer2020 Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting(原文&#xff09;Informer 是一个基于Transformer的模型&#xff0c;是为了应对长依赖关系而开发的。本文的主要主题是序列预测。序列预测可以在任何具有不断变化的数据的地方…

【STM32物联网】基于STM32+ESP8266+手机APP设计的智能密码锁源码原理图APP文档

实物图 1.摘要 设计了一种基于单片机的智能密码锁系统,提供了一种安全度高、智能化 便捷的门锁解决方案。系统由多个关键模块组成,包括 STM32 主控芯片、 RC522 射频 卡模块、指纹采集模块、矩阵按键、LCD 显示屏、 WiFi 模块和继电器等。这些模块发挥 着重要的功…

回溯的undo choice

重写N皇后和分割回文串,发现会想不明白path.remove(path.size() - 1)是在if里面还是if外面&#xff0c;问了GPT感觉很清楚 题目 N皇后 class Solution {List<List<Integer>> res new ArrayList<>();List<Integer> path new ArrayList<>();pu…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十八章 Platform 设备驱动

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

微信小程序之调查问卷

一、设计思路 1、界面 调查问卷又称调查表&#xff0c;是以问题的形式系统地记载调查内容的一种形式。微信小程序制作的调查问卷&#xff0c;可以在短时间内快速收集反馈信息。具体效果如下所示&#xff1a; 2、思路 此调查问卷采用服务器客户端的方式进行设计&#xff0c;服…

【0300】Postgres内核之 INSERT INTO 原始解析树 转 Query 树 (2 - 1)

1. 前言 在【0298】Postgres内核之 INSERT INTO 原始解析树 转 Query 树 (2)一文中讲解过Postgres内核在通过RangeVar打开一个目标关系表时,在函数parserOpenTable()中,会注册parser错误位置报告回调函数。 同时也说明了这个注册过程的使用模式。 本文将继续讲解该使用模…

【Linux】-----工具篇(编译器gcc/g++,调试器gdb)

目录 一、gcc/g 简单认识 程序的翻译过程认识gcc 预处理(宏替换) 编译 汇编 链接 宏观认识 如何理解&#xff08;核心&#xff09; 什么是链接&#xff1f; 链接的分类 二、gdb 基本的认识 基本操作及指令 安装gdb 启动gdb ​编辑 显示源代码(list) 运行程序…

【云原生】Docker搭建知识库文档协作平台Confluence

目录 一、前言 二、企业级知识库文档工具部署形式 2.1 开源工具平台 2.1.1 开源工具优点 2.1.2 开源工具缺点 2.2 私有化部署 2.3 混合部署 三、如何选择合适的知识库平台工具 3.1 明确目标和需求 3.2 选择合适的知识库平台工具 四、Confluence介绍 4.2 confluence特…

动视发布长篇“论文”试图证明:没有SBMM 只有高手受益

SBMM——基于技能的比赛匹配系统&#xff0c;一直是《使命召唤》和广大 FPS 玩家所诟病的东西&#xff0c;但是《使命召唤》抱怨的玩家最多&#xff0c;因为似乎它所使用的匹配系统是让技术较好的玩家体验最糟糕的。 动视在此前一改对匹配系统避而不谈的态度后&#xff0c;日前…

鸿蒙开发——axios封装请求、拦截器

描述&#xff1a;接口用的是PHP&#xff0c;框架TP5 源码地址 链接&#xff1a;https://pan.quark.cn/s/a610610ca406 提取码&#xff1a;rbYX 请求登录 HttpUtil HttpApi 使用方法

Hadoop单机版环境搭建

一 . 案例信息 Hadoop 的安装部署的模式一共有三种&#xff1a; 本地模式&#xff0c;默认的模式&#xff0c;无需运行任何守护进程&#xff08; daemon &#xff09;&#xff0c;所有程序都在单个 JVM 上执行。由 于在本机模式下测试和调试 MapReduce 程序较为方便&#x…

Object Detection in 20 Years: A Survey 论文阅读

前言 如果要学习目标检测&#xff0c;那了解目标检测发展历程和各个技术将有助于更好地学习。所以今天我们看一篇来自IEEE的综述。 论文名&#xff1a;Object Detection in 20 Years: A Survey 论文作者&#xff1a;Zhengxia Zou et.al. 期刊/会议名&#xff1a;IEEE 发表时间…

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

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

用Python做一个翻译软件,比上浏览器快100倍

简单的用Python来做一个翻译软件 开发环境 Python 3.10 Pycharm模块使用 requests -> pip install requests hashlib tkinter案例分为三部分: 1. 爬虫: 获取翻译接口, 请求获取翻译结果问题1: 接口抓包分析问题2: 请求需要写cookie问题3: 不同文本翻译, s加密参数2. 界面…

PHP多场地预定小程序系统源码

一键畅游多地&#xff01;多场地预定小程序的超实用指南 段落一&#xff1a;【开篇&#xff1a;告别繁琐&#xff0c;预订新体验】 &#x1f389;&#x1f680; 还在为多个活动或会议的场地预订而头疼不已吗&#xff1f;多场地预定小程序来拯救你啦&#xff01;它像是一位贴心…

[Windows CMD] 检测网络连通性 ping

ping 是一个非常常用的网络工具&#xff0c;用于测试网络连接的可达性和测量网络延迟。它通过发送 ICMP (Internet Control Message Protocol) Echo Request 数据包到目标主机&#xff0c;并等待接收回显应答 (Echo Reply) 来工作。ping 命令可以帮助您快速检测网络问题&#x…

blender使用- 置换修改器

置换修改器 对于物体可以先做细分&#xff0c;然后添加置换修改器&#xff0c;添加贴图。再对贴图的参数进行修改&#xff0c;渲染想要的效果。 旋转模式下&#xff08;按下s&#xff09;&#xff0c;z表示方向&#xff0c;0表示平整

水源地(水库)泵闸远程控制与调度系统

水源地&#xff08;水库&#xff09;泵闸远程控制与调度系统是智慧水利管理领域的重要组成部分。这一系统集现代通信、自动化控制、物联网及大数据分析技术于一体&#xff0c;旨在实现对水源地&#xff08;水库&#xff09;泵闸设备的远程监控、智能调度和高效管理。还能够为管…

若依ruoyi+AI项目二次开发(智能售货机运营管理系统)

(一) 帝可得 - 产品原型 - 腾讯 CoDesign (qq.com)

Github 2024-07-26开源项目日报 Top10

根据Github Trendings的统计,今日(2024-07-26统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目2TypeScript项目2C++项目2HTML项目1Python项目1C#项目1Lua项目1JavaScript项目1Vue项目1C项目1免费编程学习平台:freeCodeCamp.org 创…