15_FreeRtos计数信号量优先级翻转互斥信号量

news/2024/12/25 3:46:50/

目录

计数型信号量

计数型信号量相关API函数

计数型信号量实验源码

优先级翻转简介

优先级翻转实验源码

互斥信号量

互斥信号量相关API函数

互斥信号量实验源码


计数型信号量

计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的

计数型信号量适用场合:

事件计数

当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1) ,其他任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为 0

资源管理

信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)信号量创建时计数值应等于最大资源数目

计数型信号量相关API函数

使用计数型信号量的过程:创建计数型信号量 → 释放信号量 → 获取信号量

 计数型信号量的释放和获取与二值信号量相同!

此函数用于创建一个计数型信号量。

#definexSemaphoreCreateCounting( uxMaxCount , uxInitialCount )  \
xQueueCreateCountingSemaphore( ( uxMaxCount), ( uxlnitialCount ))

此函数用于获取信号量当前计数值大小 

#defineuxSemaphoreGetCount( xSemaphore) \uxQueueMessagesWaiting( (QueueHandle_t) (xSemaphore ))

计数型信号量实验源码

将设计三个任务: start_task、task1、task2

start_task :用来创建task1和task2任务

Task1:用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量

task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值

/********************************************************************************* @file           : user_mian.h* @brief          : V1.00******************************************************************************* @attention********************************************************************************//* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define  定义----------------------------------------------------------------*/
/* Macro   宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t count_semphore_handle;
/* Variables 变量--------------------------------------------------------------*/ 
/* Constants 常量--------------------------------------------------------------*/
/* Function  函数--------------------------------------------------------------*///任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define TASK1_PRIO			3
//任务堆栈大小	
#define TASK1_STK_SIZE 		100  
//任务句柄
TaskHandle_t Task1_Handler;
//任务函数
void task1(void *pvParameters);//任务优先级
#define TASK2_PRIO			3
//任务堆栈大小	
#define TASK2_STK_SIZE 		100  
//任务句柄
TaskHandle_t Task2_Handler;
//任务函数
void task2(void *pvParameters);int main(void){	/*配置系统中断分组为4位抢占*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/*延时函数初始化*/delay_init();/*RCC配置*/Rcc_config();/*GPIO初始化*/ Gpio_Init();/*USART1初始化*/Uart1_Init(9600);/*创建计数型信号量最大值100,初始化值为0*/count_semphore_handle = xSemaphoreCreateCounting(100,0); if(count_semphore_handle != NULL){printf("计数型信号量创建成功初始值为0\r\n\r\n");}/*创建开始任务*/xTaskCreate((TaskFunction_t )start_task,            //任务函数(const char*    )"start_task",          //任务名称(uint16_t       )START_STK_SIZE,        //任务堆栈大小(void*          )NULL,                  //传递给任务函数的参数(UBaseType_t    )START_TASK_PRIO,       //任务优先级(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              vTaskStartScheduler();          //开启任务调度}/*!\brief		开始任务函数\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void start_task(void *pvParameters)
{taskENTER_CRITICAL();           //进入临界区//创建任务1xTaskCreate((TaskFunction_t )task1,     	(const char*    )"task1",   	(uint16_t       )TASK1_STK_SIZE, (void*          )NULL,				(UBaseType_t    )TASK1_PRIO,	(TaskHandle_t*  )&Task1_Handler);   //创建任务2xTaskCreate((TaskFunction_t )task2,     (const char*    )"task2",   (uint16_t       )TASK2_STK_SIZE, (void*          )NULL,(UBaseType_t    )TASK2_PRIO,(TaskHandle_t*  )&Task2_Handler); vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL();            //退出临界区
}/*!\brief		task1释放计数型信号量\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void task1(void *pvParameters)
{uint8_t key = 0;while(1){	/*获取按键值*/key = Key_Scan(0);if(key == KEY0_PRES){if(count_semphore_handle != NULL){					 if(xSemaphoreGive(count_semphore_handle)){taskENTER_CRITICAL();           //进入临界区printf("计数型信号量释放成功当前值为%d\r\n\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));taskEXIT_CRITICAL();            //退出临界区}}		}vTaskDelay(10);}
} /*!\brief		task2获取计数型信号量\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void task2(void *pvParameters)
{BaseType_t err;while(1){/*获取信号量死等,进入阻塞态*/err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY);	if(err == pdTRUE){taskENTER_CRITICAL();           //进入临界区	printf("信号量的计数值为:%d\r\n\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));		taskEXIT_CRITICAL();            //退出临界区}vTaskDelay(1000);}
}/************************************************************** END OF FILE ****/

 

优先级翻转简介

优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行

优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。

在使用二值信号量的时候,经常会遇到优先级翻转的问题。

举例:

任务H 优先级最高 任务M优先级中等  任务L优先级最低

假设任务L正在运行获取了信号量,其他2任务在阻塞状态,此时任务H就绪抢占了任务L,任务H也是获取信号量,发现信号量没有了进入阻塞态,继续执行任务L(优先级翻转了),然后任务M就绪抢占了任务L,任务M执行完后,进入阻塞态,任务H一直在等信号量所以一直在阻塞态,任务L继续运行,直到释放了信号量后,任何H才会从阻塞态变成就绪态执行。

高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度,但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)

优先级翻转实验源码

在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响

将设计四个任务:start_task、high_task、middle_task, low_task

start_task:用来创建其它任务

high_task:高优先级任务,会获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量

middle_task:中等优先级任务,简单的应用任务

low_task:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号量的时间久一点

/********************************************************************************* @file           : user_mian.h* @brief          : V1.00******************************************************************************* @attention********************************************************************************//* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define  定义----------------------------------------------------------------*/
/* Macro   宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t semphore_handle;
/* Variables 变量--------------------------------------------------------------*/ 
/* Constants 常量--------------------------------------------------------------*/
/* Function  函数--------------------------------------------------------------*///任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define HIGH_PRIO			4
//任务堆栈大小	
#define HIGH_STK_SIZE 		100  
//任务句柄
TaskHandle_t HIGH_Handler;
//任务函数
void high_task(void *pvParameters);//任务优先级
#define MIDDLE_PRIO			3
//任务堆栈大小	
#define MIDDLE_STK_SIZE 		100  
//任务句柄
TaskHandle_t MIDDLE_Handler;
//任务函数
void middle_task(void *pvParameters);//任务优先级
#define LOW_PRIO			2
//任务堆栈大小	
#define LOW_STK_SIZE 		100  
//任务句柄
TaskHandle_t LOW_Handler;
//任务函数
void low_task(void *pvParameters);int main(void){	/*配置系统中断分组为4位抢占*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/*延时函数初始化*/delay_init();/*RCC配置*/Rcc_config();/*GPIO初始化*/ Gpio_Init();/*USART1初始化*/Uart1_Init(9600);/*创建二值信号量*/semphore_handle = xSemaphoreCreateBinary(); if(semphore_handle == NULL){printf("二值信号量创建不成功\r\n\r\n");}else{printf("二值信号量创建成功\r\n\r\n");}/*二值释放信号量*/xSemaphoreGive(semphore_handle);/*创建开始任务*/xTaskCreate((TaskFunction_t )start_task,            //任务函数(const char*    )"start_task",          //任务名称(uint16_t       )START_STK_SIZE,        //任务堆栈大小(void*          )NULL,                  //传递给任务函数的参数(UBaseType_t    )START_TASK_PRIO,       //任务优先级(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              vTaskStartScheduler();          //开启任务调度}/*!\brief		开始任务函数\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void start_task(void *pvParameters)
{taskENTER_CRITICAL();           //进入临界区//创建高优先级任务xTaskCreate((TaskFunction_t )high_task,     	(const char*    )"high_task",   	(uint16_t       )HIGH_STK_SIZE, (void*          )NULL,				(UBaseType_t    )HIGH_PRIO,	(TaskHandle_t*  )&HIGH_Handler);   //创建中优先级任务xTaskCreate((TaskFunction_t )middle_task,     (const char*    )"middle_task",   (uint16_t       )MIDDLE_STK_SIZE, (void*          )NULL,(UBaseType_t    )MIDDLE_PRIO,(TaskHandle_t*  )&MIDDLE_Handler); //创建低优先级任务xTaskCreate((TaskFunction_t )low_task,     (const char*    )"low_task",   (uint16_t       )LOW_STK_SIZE, (void*          )NULL,(UBaseType_t    )LOW_PRIO,(TaskHandle_t*  )&LOW_Handler); vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL();            //退出临界区
}/*!\brief		高优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void high_task(void *pvParameters)
{while(1){	taskENTER_CRITICAL();           //进入临界区printf("high_task获取信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区/*获取二值信号量,并死等方式*/xSemaphoreTake(semphore_handle,portMAX_DELAY);taskENTER_CRITICAL();           //进入临界区printf("high_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区delay_xms(1000);/*释放二值信号量*/taskENTER_CRITICAL();           //进入临界区printf("high_task释放信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区xSemaphoreGive(semphore_handle);vTaskDelay(10);}
} /*!\brief		中优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void middle_task(void *pvParameters)
{while(1){taskENTER_CRITICAL();           //进入临界区printf("middle_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区vTaskDelay(1000);}
}/*!\brief		低优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void low_task(void *pvParameters)
{while(1){    taskENTER_CRITICAL();           //进入临界区printf("low_task获取信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区/*获取二值信号量,并死等方式*/xSemaphoreTake(semphore_handle,portMAX_DELAY);taskENTER_CRITICAL();           //进入临界区		printf("low_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区delay_xms(3000);/*释放二值信号量*/taskENTER_CRITICAL();           //进入临界区printf("low_task释放信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区xSemaphoreGive(semphore_handle);vTaskDelay(1000);		}}/************************************************************** END OF FILE ****/

互斥信号量

互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!

优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。

 此时任务H的阻塞时间仅仅是任务L的执行时间,将优先级翻转的危害降到了最低

优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响

注意:互斥信号量不能用于中断服务函数中,原因如下:

  1. 互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用与任务中,不能用于中断服务函数。
  2. 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。

互斥信号量相关API函数

使用互斥信号量:首先将宏configUSE_MUTEXES置一

使用流程:创建互斥信号量→ (task)获取信号量→ (give)释放信号量

创建互斥信号量函数

 互斥信号量的释放和获取函数与二值信号量相同!只不过互斥信号量不支持中断中调用

注意:创建互斥信号量时,会主动释放一次信号量

#define xSemaphoreCreateMutex()  xQueueCreateMutex( queueQUEUE_TYPE_MUTEX)

此函数用于创建互斥信号量

互斥信号量实验源码

在优先级翻转实验的基础,加入互斥信号量,解决优先级翻转问题

/********************************************************************************* @file           : user_mian.h* @brief          : V1.00******************************************************************************* @attention********************************************************************************//* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define  定义----------------------------------------------------------------*/
/* Macro   宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t mutex_semphore_handle;
/* Variables 变量--------------------------------------------------------------*/ 
/* Constants 常量--------------------------------------------------------------*/
/* Function  函数--------------------------------------------------------------*///任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define HIGH_PRIO			4
//任务堆栈大小	
#define HIGH_STK_SIZE 		100  
//任务句柄
TaskHandle_t HIGH_Handler;
//任务函数
void high_task(void *pvParameters);//任务优先级
#define MIDDLE_PRIO			3
//任务堆栈大小	
#define MIDDLE_STK_SIZE 		100  
//任务句柄
TaskHandle_t MIDDLE_Handler;
//任务函数
void middle_task(void *pvParameters);//任务优先级
#define LOW_PRIO			2
//任务堆栈大小	
#define LOW_STK_SIZE 		100  
//任务句柄
TaskHandle_t LOW_Handler;
//任务函数
void low_task(void *pvParameters);int main(void){	/*配置系统中断分组为4位抢占*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/*延时函数初始化*/delay_init();/*RCC配置*/Rcc_config();/*GPIO初始化*/ Gpio_Init();/*USART1初始化*/Uart1_Init(9600);/*创建互斥信号量,默认释放一次*/mutex_semphore_handle = xSemaphoreCreateMutex(); if(mutex_semphore_handle == NULL){printf("互斥信号量创建不成功\r\n\r\n");}else{printf("互斥信号量创建成功\r\n\r\n");}/*创建开始任务*/xTaskCreate((TaskFunction_t )start_task,            //任务函数(const char*    )"start_task",          //任务名称(uint16_t       )START_STK_SIZE,        //任务堆栈大小(void*          )NULL,                  //传递给任务函数的参数(UBaseType_t    )START_TASK_PRIO,       //任务优先级(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              vTaskStartScheduler();          //开启任务调度}/*!\brief		开始任务函数\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void start_task(void *pvParameters)
{taskENTER_CRITICAL();           //进入临界区//创建高优先级任务xTaskCreate((TaskFunction_t )high_task,     	(const char*    )"high_task",   	(uint16_t       )HIGH_STK_SIZE, (void*          )NULL,				(UBaseType_t    )HIGH_PRIO,	(TaskHandle_t*  )&HIGH_Handler);   //创建中优先级任务xTaskCreate((TaskFunction_t )middle_task,     (const char*    )"middle_task",   (uint16_t       )MIDDLE_STK_SIZE, (void*          )NULL,(UBaseType_t    )MIDDLE_PRIO,(TaskHandle_t*  )&MIDDLE_Handler); //创建低优先级任务xTaskCreate((TaskFunction_t )low_task,     (const char*    )"low_task",   (uint16_t       )LOW_STK_SIZE, (void*          )NULL,(UBaseType_t    )LOW_PRIO,(TaskHandle_t*  )&LOW_Handler); vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL();            //退出临界区
}/*!\brief		高优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void high_task(void *pvParameters)
{while(1){	taskENTER_CRITICAL();           //进入临界区printf("high_task获取信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区/*获取二值信号量,并死等方式*/xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);taskENTER_CRITICAL();           //进入临界区printf("high_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区delay_xms(1000);/*释放二值信号量*/taskENTER_CRITICAL();           //进入临界区printf("high_task释放信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区xSemaphoreGive(mutex_semphore_handle);vTaskDelay(10);}
} /*!\brief		中优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void middle_task(void *pvParameters)
{while(1){taskENTER_CRITICAL();           //进入临界区printf("middle_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区vTaskDelay(1000);}
}/*!\brief		低优先级任务\param[in]	传递形参,创建任务时用户自己传入\param[out]	none\retval 	none
*/
void low_task(void *pvParameters)
{while(1){    taskENTER_CRITICAL();           //进入临界区printf("low_task获取信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区/*获取二值信号量,并死等方式*/xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);taskENTER_CRITICAL();           //进入临界区		printf("low_task正在运行\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区delay_xms(3000);/*释放二值信号量*/taskENTER_CRITICAL();           //进入临界区printf("low_task释放信号量\r\n\r\n");taskEXIT_CRITICAL();            //退出临界区xSemaphoreGive(mutex_semphore_handle);vTaskDelay(1000);		}}/************************************************************** END OF FILE ****/


http://www.ppmy.cn/news/26009.html

相关文章

实验10 拓扑排序与最短路径2022

A. DS图—图的最短路径&#xff08;无框架&#xff09;题目描述给出一个图的邻接矩阵&#xff0c;输入顶点v&#xff0c;用迪杰斯特拉算法求顶点v到其它顶点的最短路径。输入第一行输入t&#xff0c;表示有t个测试实例第二行输入顶点数n和n个顶点信息第三行起&#xff0c;每行输…

杭州电子科技大学2023年MBA招生考试成绩查询和复查申请的通知

根据往年的情况&#xff0c;2023杭州电子大学MBA考试初试成绩可能将于2月21日公布&#xff0c;最早于20号出来&#xff0c;为了广大考生可以及时查询到自己的分数&#xff0c;杭州达立易考教育为大家汇总了信息。根据教育部和浙江省教育考试院关于硕士研究生招生考试工作的统一…

JVM详解——类的加载

文章目录类的加载1、Java程序如何运行2、Java字节码文件3、类加载4、类加载的过程5、类加载器6、类的加载方式7、类的加载机制8、双亲委派机制9、破坏双亲委派机制类的加载 1、Java程序如何运行 首先通过Javac命令将.java文件编译生成.class字节码文件。 Javac是Java编译命令&a…

机试_3_数据结构(一)_习题

数据结构&#xff08;一&#xff09;——练习题 学习完第三章-数据结构&#xff08;一&#xff09;之后&#xff0c;当然要做相应地练习啦~ 注&#xff1a;上述习题都可以在牛客进行测试。 例如&#xff0c;第2题链接&#xff1a;计算表达式_牛客题霸_牛客网 (nowcoder.com)…

剑指 Offer 53 - II. 0~n-1中缺失的数字

摘要 剑指 Offer 53 - II. 0&#xff5e;n-1中缺失的数字 一个长度为n-1的递增排序数组中的所有数字都是唯一的&#xff0c;并且每个数字都在范围0&#xff5e;n-1之内。在范围0&#xff5e;n-1内的n个数字中有且只有一个数字不在该数组中&#xff0c;请找出这个数字。 一、…

51单片机最强模块化封装(5)

文章目录 前言一、创建timer文件,添加timer文件路径二、timer文件编写三、模块化测试总结前言 今天这篇文章将为大家封装定时器模块,定时器是工程项目中必不可少的,希望大家能够将定时器理解清楚并且运用自如。 一、创建timer文件,添加timer文件路径 这里的操作就不过多…

【图神经网络】图拉普拉斯滤波器如何实现全通、低通、高通滤波

【图神经网络】图拉普拉斯滤波器如何实现全通、低通、高通滤波 文章目录【图神经网络】图拉普拉斯滤波器如何实现全通、低通、高通滤波1. 前言2. 符号说明3. 三种滤波3.1 全通滤波3.2 低通滤波3.2.1 平滑信号分析3.2.2 广义拉普拉斯平滑滤波器3.3 高通滤波4. 总结1. 前言 GCN&…

贴吧手机端防删图GIF动态图制作解析

贴吧存活 思路技术运气 1&#xff1a;防删图不是存活的绝对因素&#xff0c;除了防删图&#xff0c;还有账号&#xff0c;ip&#xff0c;内容&#xff0c;吧的问题 2&#xff1a;一个图不是每个吧都可以发 3&#xff1a;一个贴不被删不仅仅看图片 4&#xff1a;有时候运气也很…