【阿里云】图像识别 智能分类识别 增加垃圾桶开关盖功能点和OLED显示功能点(二)

一、增加垃圾桶开关盖功能

  • 环境准备

二、PWM 频率的公式
三、pthread_detach分离线程,使其在退出时能够自动释放资源
四、具体代码实现

  • 图像识别数据及调试信息
  • wget-log打印日志文件

五、增加OLED显示功能
六、功能点实现语音交互视频

一、增加垃圾桶开关盖功能

实现功能:使用语音模块和摄像头在香橙派上做垃圾智能分类识别, 同时根据识别结果开关不同的垃圾桶的盖子。

环境准备

在《语音模块和阿里云图像识别结合》搭建环境的基础上, 接上用于开关盖的舵机(舵机模块可以直接粘在垃圾桶内侧),当前代码里仅用了2个舵机用于示例代码的编写,可以自行多购买3个垃圾桶和舵机用于区分4垃圾类型,接线位置如下:
在这里插入图片描述

实物图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、PWM 频率的公式

这个 PWM 频率的公式可以更详细地表示为:

P W M f r e q = 1 × 1 0 6 pulse-width × range \\{PWMfreq} = \frac{1 \times 10^6}{\text{pulse-width} \times \text{range}} \ PWMfreq=pulse-width×range1×106 

其中:

  • (\text{PWMfreq}) 是 PWM 的频率(赫兹)。
  • (1 \times 10^6) 是为了将频率从赫兹(Hz)转换为微秒(μs)。
  • (\text{pulse-width}) 是每个 PWM 脉冲的宽度(微秒)。
  • (\text{range}) 是 PWM 的范围,即 PWM 值的最大范围。

这个公式的基本思想是,PWM 的频率与脉冲宽度和范围有关。脉冲宽度表示每个 PWM 脉冲的持续时间,而范围表示 PWM 值的最大范围。通过调整这两个参数,可以控制 PWM 的频率。

三、pthread_detach分离线程,使其在退出时能够自动释放资源

pthread_detach 函数是 POSIX 线程库提供的一个函数,用于将一个线程标记为可被回收的。标记为可被回收的线程在退出时会自动释放其占用的系统资源,无需等待其他线程调用 pthread_join

具体来说,当一个线程被标记为可被回收时,其退出状态会自动被收回。这对于那些不需要其他线程等待其结束的线程是有用的,因为它允许主线程或其他线程继续执行而无需等待这个线程的完成。

#include <pthread.h>int pthread_detach(pthread_t thread);
  • pthread_detach 的参数是一个线程标识符(pthread_t 类型的变量),它表示要被标记为可被回收的线程。
  • 如果线程标识符为 thread 的线程处于 joinable 状态,那么它会被标记为可被回收,并且在线程退出时,其资源将被自动释放。
  • 如果线程已经处于 detached 状态,或者线程标识符不对应一个现存的线程,pthread_detach 函数将返回适当的错误码。

示例用法:

#include <pthread.h>void *thread_function(void *arg) {// 线程的执行体// ...return NULL;
}int main() {pthread_t my_thread;// 创建线程if (pthread_create(&my_thread, NULL, thread_function, NULL) != 0) {// 线程创建失败处理return 1;}// 将线程标记为可被回收if (pthread_detach(my_thread) != 0) {// 线程标记失败处理return 1;}// 主线程继续执行而不用等待子线程的结束// ...return 0;
}

在这个例子中,my_thread 线程被创建后立即被标记为可被回收,主线程可以继续执行而不用等待 my_thread 线程的完成。

总结:
你可以将这种机制称为“分离线程”或“分离父子线程”。当你将一个线程标记为可被回收,这个线程就不再和主线程形成关联,主线程不需要显式地等待它的结束。这样的线程就像“自洁”一样,它在结束时会自动释放资源。

这种机制对于那些主线程不关心其返回值,也不需要等待其结束的辅助线程是非常有用的。这样,主线程和辅助线程可以并行执行,提高了程序的性能。

四、具体代码实现

  1. 增加用于实现开光盖(驱动舵机)的源码文件(pwm.c):
#include <wiringPi.h>
#include <softPwm.h>
#include "pwm.h"// 根据PWM 频率公式:PWMfreq = 1 x 10^6 / (100 x range) 。
// 要得到PWM频率为50Hz,则range设置周期分为200步,周期20ms,控制精度相比硬件PWM较低。// 设置指定PWM引脚的输出,实现模拟PWM
void pwm_write(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin, 0, 200);	// 创建软件PWM,初始占空比为0%,范围为0到200softPwmWrite(pwm_pin, 10);		// 设置占空比为10%	45度delay(1000);					// 延时1秒softPwmStop(pwm_pin);			// 停止软件PWM
}// 停止指定PWM引脚的输出
void pwm_stop(int pwm_pin)
{pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin, 0, 200);	// 创建软件PWM,初始占空比为0%,范围为0到200softPwmWrite(pwm_pin, 5);		// 设置占空比为5%	0度delay(1000);					// 延时1秒softPwmStop(pwm_pin);			// 停止软件PWM
}
  1. pwm.h代码:
#ifndef __PWM__H
#define __PWM__H#define PWM_GARBAGE 7				// 干垃圾
#define PWM_RECOVERABLE_GARBAGE 5	// 可回收垃圾
#define PWM_WET_GARBAGE 8			// 湿垃圾
#define PWM_HAZARDOUS_GARBAGE 9		// 有害垃圾void pwm_write(int pwm_pin);		// 设置指定PWM引脚的输出
void pwm_stop(int pwm_pin);			// 停止指定PWM引脚的输出#endif
  1. 修改main.c代码,调整整体main函数的代码架构,利用多线程实现具体的功能(用到了线程里的条件变量控制线程间的数据同步)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"int serial_fd = -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量,用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁,用于线程之间的互斥访问// 判断进程是否在运行
static int detect_process(const char * process_name)
{int n = -1; // 存储进程PID,默认为-1FILE *strm;char buf[128] = {0}; // 缓冲区// 构造命令字符串,通过ps命令查找进程sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);// 使用popen执行命令并读取输出if ((strm = popen(buf, "r")) != NULL) {if (fgets(buf, sizeof(buf), strm) != NULL) {printf("buf = %s\n", buf); 	//打印缓存区的内容n = atoi(buf); 				// 将进程ID字符串转换为整数printf("n = %d\n", n); 		// 打印下进程的PID}}else {return -1; // popen失败}	pclose(strm); // 关闭popen打开的文件流return n;
}// 获取语音线程
void *pget_voice(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};int len = 0;printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);// 串口未打开,退出线程if (-1 == serial_fd) {printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);// 循环读取串口数据while (1) {len = my_serialGetstring(serial_fd, buffer);printf("%s|%s|%d, len = %d\n", __FILE__, __func__, __LINE__, len);// 检测到特定数据,发出信号唤醒其他线程if (len > 0 && buffer[2] == 0x46) {printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_mutex_lock(&mutex);buffer[2] = 0x00;pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);system(WGET_CMD);}}pthread_exit(0);
}// 发送语音线程
void *psend_voice(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer = (unsigned char *)arg;// 串口未打开,退出线程if (-1 == serial_fd) {printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}// buffer不为空时,通过串口发送数据(分类结果)if (NULL != buffer) {my_serialSendstring(serial_fd, buffer, 6);}pthread_exit(0);
}// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer = (unsigned char *)arg;// 根据垃圾类型控制PWMif (buffer[2] == 0x43) {		// 可回收垃圾printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] == 0x41) {	// 干垃圾printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}else if (buffer[2] == 0x42) {	// 湿垃圾printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);pwm_write(PWM_WET_GARBAGE);delay(2000);pwm_stop(PWM_WET_GARBAGE);}else if (buffer[2] == 0x44) {	// 有害垃圾printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);pwm_stop(PWM_HAZARDOUS_GARBAGE);delay(2000);pwm_write(PWM_HAZARDOUS_GARBAGE);}pthread_exit(0);
}// 垃圾分类线程
void *pcategory(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};char *category = NULL;pthread_t send_voice_tid, trash_tid;while (1) {printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);buffer[2] = 0x00;// 在执行wget命令之前添加调试输出printf("Executing wget command...\n");// 使用系统命令拍照system(WGET_CMD);// 在执行wget命令之后添加调试输出printf("Wget command executed.\n");// 判断垃圾种类if (0 == access(GARBAGE_FILE, F_OK)) {category = garbage_category(category);if (strstr(category, "干垃圾")) {buffer[2] = 0x41;}else if (strstr(category, "湿垃圾")) {buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")) {buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")) {buffer[2] = 0x44;}else {buffer[2] = 0x45; // 未识别到垃圾类型}}else {buffer[2] = 0x45; // 识别失败}// 开垃圾桶开关pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);// 开语音播报线程pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);// buffer[2] = 0x00;// 删除拍照文件remove(GARBAGE_FILE); }pthread_exit(0);
}int main(int argc, char *argv[])
{int ret = -1;int len = 0;char *category = NULL;pthread_t get_voice_tid, category_tid;wiringPiSetup();// 初始化串口和垃圾分类模块garbage_init ();// 用于判断mjpg_streamer服务是否已经启动ret = detect_process ("mjpg_streamer");if (-1 == ret) {printf("detect process failed\n");goto END;}// 打开串口serial_fd = my_serialOpen (SERIAL_DEV, BAUD);if (-1 == serial_fd) {printf("open serial failed\n");goto END;}// 开语音线程printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_create(&get_voice_tid, NULL, pget_voice, NULL);// 开阿里云交互线程printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_create(&category_tid, NULL, pcategory, NULL);// 创建互斥锁和条件变量pthread_join(get_voice_tid, NULL);pthread_join(category_tid, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);// 关闭串口close(serial_fd);
END:// 释放垃圾分类资源garbage_final();return 0;
}

图像识别数据及调试信息

在这里插入图片描述
在这里插入图片描述

wget-log打印日志文件

在这里插入图片描述
wget-log 文件名通常是 wget 命令行工具的默认日志文件名,用于记录 wget 下载命令执行过程中的信息、警告和错误。wget 是一个用于在命令行中下载文件的工具,而 wget-log 文件则用于记录执行 wget 命令时产生的输出。

如果你在使用类似如下的 wget 命令:

wget [URL]

wget 默认会将日志输出到 wget-log 文件中。如果你希望更改日志文件的名称,可以使用 -o 选项,例如:

wget -o mylog.txt [URL]

上述命令将日志输出到名为 mylog.txt 的文件中。因此,wget-log 文件的生成通常取决于 wget 命令的使用方式。

阿里云的相关操作(比如通过 wget 下载文件)也可能产生 wget-log 文件,具体情况可能取决于你执行的命令和阿里云环境的设置。如果有特定的 wget 命令或阿里云操作,你可以提供更多的上下文,以便我更好地理解你的问题。

五、增加OLED显示功能

  1. 环境配置
cat /boot/orangepiEnv.txt
ls -a /dev/i2c-3

在这里插入图片描述

  1. 《OLED屏应用-IIC协议》直接添在garbage项目中添加2个OLED实现代码文件

myoled.h

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>#include "oled.h"
#include "font.h"#ifndef __MYOLED__H
#define __MYOLED__Hint myoled_init(void);
int oled_show(void *arg);#endif

myoled.c:

#include "myoled.h"#define FILENAME "/dev/i2c-3"static struct display_info disp;// 在 OLED 上显示垃圾分类结果
int oled_show(void *arg)
{unsigned char *buffer = (unsigned char *)arg;// 在 OLED 上显示提示信息oled_putstrto(&disp, 0, 9+1, "THis garbage is:");disp.font = font2;// 根据垃圾类型显示相应信息switch(buffer[2]){case 0x41:oled_putstrto(&disp, 0, 20, "Dry_garbage");break;case 0x42:oled_putstrto(&disp, 0, 20, "Wet_garbage");break;case 0x43:oled_putstrto(&disp, 0, 20, "Recycle_garbage");break;case 0x44:oled_putstrto(&disp, 0, 20, "Hazardous_garbage");break;case 0x45:oled_putstrto(&disp, 0, 20, "recognition failed");break;}disp.font = font2;// 发送显示缓冲区到 OLEDoled_send_buffer(&disp);return 0;
}// 初始化 OLED
int myoled_init(void)
{int e;disp.address = OLED_I2C_ADDR;disp.font = font2;// 打开 OLED 设备文件e = oled_open(&disp, FILENAME);// 初始化 OLEDe = oled_init(&disp);return e;
}
  1. 然后修改下main.c文件, 增加OLED线程,用于显示识别后的垃圾类型:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"int serial_fd = -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量,用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁,用于线程之间的互斥访问// 判断进程是否在运行
static int detect_process(const char * process_name)
{int n = -1; // 存储进程PID,默认为-1FILE *strm;char buf[128] = {0}; // 缓冲区// 构造命令字符串,通过ps命令查找进程sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);// 使用popen执行命令并读取输出if ((strm = popen(buf, "r")) != NULL) {if (fgets(buf, sizeof(buf), strm) != NULL) {printf("buf = %s\n", buf); 	//打印缓存区的内容n = atoi(buf); 				// 将进程ID字符串转换为整数printf("n = %d\n", n); 		// 打印下进程的PID}}else {return -1; // popen失败}	pclose(strm); // 关闭popen打开的文件流return n;
}// 获取语音线程
void *pget_voice(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};int len = 0;printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);// 串口未打开,退出线程if (-1 == serial_fd) {printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);// 循环读取串口数据while (1) {len = my_serialGetstring(serial_fd, buffer);printf("%s|%s|%d, len = %d\n", __FILE__, __func__, __LINE__, len);// 检测到特定数据,发出信号唤醒其他线程if (len > 0 && buffer[2] == 0x46) {printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_mutex_lock(&mutex);buffer[2] = 0x00;pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);system(WGET_CMD);}}pthread_exit(0);
}// 发送语音线程
void *psend_voice(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer = (unsigned char *)arg;// 串口未打开,退出线程if (-1 == serial_fd) {printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);pthread_exit(0);}// buffer不为空时,通过串口发送数据(分类结果)if (NULL != buffer) {my_serialSendstring(serial_fd, buffer, 6);}pthread_exit(0);
}// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer = (unsigned char *)arg;// 根据垃圾类型控制PWMif (buffer[2] == 0x43) {		// 可回收垃圾printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] == 0x41) {	// 干垃圾printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}else if (buffer[2] == 0x42) {	// 湿垃圾printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);pwm_write(PWM_WET_GARBAGE);delay(2000);pwm_stop(PWM_WET_GARBAGE);}else if (buffer[2] == 0x44) {	// 有害垃圾printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);pwm_stop(PWM_HAZARDOUS_GARBAGE);delay(2000);pwm_write(PWM_HAZARDOUS_GARBAGE);}pthread_exit(0);
}// 在线程中显示 OLED
void *poled_show(void *arg)
{// 分离线程,使其在退出时能够自动释放资源pthread_detach(pthread_self());// 初始化 OLEDmyoled_init();// 在 OLED 上显示垃圾分类结果oled_show(arg);// 退出线程	pthread_exit(0);
}// 垃圾分类线程
void *pcategory(void *arg)
{unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};char *category = NULL;pthread_t send_voice_tid, trash_tid, oled_tid;while (1) {printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);buffer[2] = 0x00;// 在执行wget命令之前添加调试输出printf("Executing wget command...\n");// 使用系统命令拍照system(WGET_CMD);// 在执行wget命令之后添加调试输出printf("Wget command executed.\n");// 判断垃圾种类if (0 == access(GARBAGE_FILE, F_OK)) {category = garbage_category(category);if (strstr(category, "干垃圾")) {buffer[2] = 0x41;}else if (strstr(category, "湿垃圾")) {buffer[2] = 0x42;}else if (strstr(category, "可回收垃圾")) {buffer[2] = 0x43;}else if (strstr(category, "有害垃圾")) {buffer[2] = 0x44;}else {buffer[2] = 0x45; // 未识别到垃圾类型}}else {buffer[2] = 0x45; // 识别失败}// 开垃圾桶开关pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);// 开语音播报线程pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);//oled显示线程pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);// buffer[2] = 0x00;// 删除拍照文件remove(GARBAGE_FILE); }pthread_exit(0);
}int main(int argc, char *argv[])
{int ret = -1;int len = 0;char *category = NULL;pthread_t get_voice_tid, category_tid;wiringPiSetup();// 初始化串口和垃圾分类模块garbage_init ();// 用于判断mjpg_streamer服务是否已经启动ret = detect_process ("mjpg_streamer");if (-1 == ret) {printf("detect process failed\n");goto END;}// 打开串口serial_fd = my_serialOpen (SERIAL_DEV, BAUD);if (-1 == serial_fd) {printf("open serial failed\n");goto END;}// 开语音线程printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_create(&get_voice_tid, NULL, pget_voice, NULL);// 开阿里云交互线程printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);pthread_create(&category_tid, NULL, pcategory, NULL);// 创建互斥锁和条件变量pthread_join(get_voice_tid, NULL);pthread_join(category_tid, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);// 关闭串口close(serial_fd);
END:// 释放垃圾分类资源garbage_final();return 0;
}

在这里插入图片描述

六、功能点实现语音交互视频

垃圾分类

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

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

相关文章

阅读笔记——《Removing RLHF Protections in GPT-4 via Fine-Tuning》

【参考文献】Zhan Q, Fang R, Bindu R, et al. Removing RLHF Protections in GPT-4 via Fine-Tuning[J]. arXiv preprint arXiv:2311.05553, 2023.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 目录 摘要 一、介绍 二、背景 三、方法…

VSCode 连接远程服务器问题及解决办法

端口号不一样&#xff0c;需要在配置文件中添加Port Host 27.223.26.46HostName 27.223.*.*User userForwardAgent yesPort 14111输入密码后可以连接 在vscode界面&#xff0c;终端&#xff0c;生成公钥&私钥 ssh-keygen可以看到有id_rsa和id_rsa.pub两个文件生成&#…

UniWebView 版本3 版本4 版本5介绍

一、介绍 UniWebView是iOS/Android上的web视图组件的包装器&#xff0c;所以运行时拥有与原生web相似性能。是针对Unity所写的插件&#xff0c;节省了项目的开发时间。 官网地址&#xff1a;UniWebView 二、下载&使用 1、下载 &#xff08;1&#xff09;、Unity Asset …

宝塔 Linux 面板安装一个高大上的论坛程序 —— Flarum

这个是很早搭建的版本,基于宝塔面板,比较复杂,如果想要简单的搭建方法,可以参看咕咕新写的这篇: 【好玩的 Docker 项目】10 分钟搭建一个高大上的论坛程序 购买腾讯云轻量应用服务器 待补充 登录服务器 待补充 BBR 加速脚本 BBR 加速脚本: BASH cd /usr/src &…

支持Arm CCA的TF-A威胁模型

目录 一、简介 二、评估目标 2.1 假定 2.2 数据流图 三、威胁分析 3.1 威胁评估 3.1.1 针对所有固件镜像的一般威胁 3.1.2 引导固件可以缓解的威胁 3.1.3 运行时EL3固件可缓解的威胁 一、简介 本文针对支持Arm Realm Management Extension (RME)、实现Arm Confidentia…

位图的详细讲解

位运算操作符&#xff1a;或&#xff0c;与&#xff0c;异或&#xff0c;按位取反。 操作符 |两个中有一个是一则为一&两个都是一则为一^相同为零&#xff0c;不同为一~零变成一&#xff0c;一变成零 什么是位运算符: 位运算是直接对整型数据的二进制进行运算。 位图概念…

一、TIDB基础

TIDB整个逻辑架构跟MYSQL类似&#xff0c;如下&#xff1a; TIDB集群&#xff1a;相当于MYSQL的数据库服务器&#xff0c;区别是MYSQL数据库服务器为单进程的&#xff0c;TIDB集群为分布式多进程的。 数据库&#xff1a;同MYSQL数据库&#xff0c;数据库属于集群&#xff0c;…

【LeetCode:1457. 二叉树中的伪回文路径 | 二叉树 + DFS +回文数】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

告别百度网盘,搭建自己的专属网盘 ——Cloudreve,不限制下载速度!

Cloudreve 是一个用 Go 语言写的公有网盘程序,我们可以用它来快速搭建起自己的网盘服务,公有云 / 私有云都可。 顺哥博客 先来看看文档介绍吧。 支持多家云存储驱动的公有云文件系统. 演示站 • 讨论社区 • 文档 • 下载 • Telegram 群组 • 许可证 :sparkles: 特性 :cl…

​3ds Max插件CG MAGIC图形板块为您提升线条效率!

​通过3ds Max软件进行绘图操作时&#xff0c;大多绊住各位设计师们作图速度的往往都是一些细微的琐事&#xff0c;重复一变一变的调整修改等问题。 今天说到这个绘图线条来回调整解决方法就是3ds Max插件CG MAGIC。 Max插件CG MAGIC作为一款智能化的辅助插件&#xff0c;致力于…

MySQL-04-InnoDB存储引擎锁和加锁分析

Latch一般称为闩锁&#xff08;轻量级锁&#xff09;&#xff0c;因为其要求锁定的时间必须非常短。在InnoDB存储引擎中&#xff0c;latch又分为mutex&#xff08;互斥量&#xff09;和rwlock&#xff08;读写锁&#xff09;。 Lock的对象是事务&#xff0c;用来锁定的是…

分块矩阵知识点整理:

1.分块方法&#xff1a;横竖线不能拐弯&#xff0c;思想为将矩阵分块看作向量计算 2.标准型 不一定是方的 特殊性&#xff1a;经过分块后会出现单位矩阵和0矩阵 3.分块矩阵的运算: 1.加减乘的运算与向量运算相同 4.分块矩阵求转置&#xff1a; 1.将子块看作普通元素求转置 2…