RTOS随笔之FreeRTOS启动与同步方法
RTOS启动与同步机制
- RTOS启动
- 任务切换场景
- 任务同步机制
- 队列
- 信号量
- 事件组
- 任务通知
- 任务延时
RTOS启动
FreeRTOS在任务创建完成后调用函数vTaskStartScheduler()启动任务调度器。
vTaskStartScheduler()任务启动函数详解
void vTaskStartScheduler( void )
{BaseType_t xReturn;xReturn = xTaskCreate( prvIdleTask,"IDLE", configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),&xIdleTaskHandle );//创建空闲任务,空闲任务优先级最低#if ( configUSE_TIMERS == 1 )//是否使用软件定时器{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();//创建软件定时器任务}else{mtCOVERAGE_TEST_MARKER();}}#endif if( xReturn == pdPASS ){portDISABLE_INTERRUPTS();//关闭中断#if ( configUSE_NEWLIB_REENTRANT == 1 ){_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;//调度器开始运行xTickCount = ( TickType_t ) 0U;portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();//实现任务统计功能,具体由用户实现if( xPortStartScheduler() != pdFALSE )//初始化任务硬件相关的功能,Systick/PendSV/FPU{//如果调度器启动成功,xPortStartScheduler()没有返回值,不会运行到这里}else{//不会运行到这里,除非调用xTaskEndScheduler()}}else{//创建空闲任务或定时器任务失败,内存不足configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}//防止编译器报错,INCLUDE_xTaskGetIdleTaskHandle定义为0,编译器会提示( void ) xIdleTaskHandle;
}
xPortStartScheduler()任务硬件相关函数详解
BaseType_t xPortStartScheduler( void )
{portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;//设置PendSV中断为最低优先级portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;//设置Systick中断为最低优先级vPortSetupTimerInterrupt();//设置Systick周期uxCriticalNesting = 0;//初始化临界区嵌套计数prvEnableVFP();//使能FPU浮点运算*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;//采用惰性压栈的特性自动保存寄存器/*惰性压栈猜测:惰性压栈指的是异常产生时只将通用寄存器的值进行压栈,浮点寄存器值不压栈,除非异常处理函数中存在浮点运算才会将浮点寄存器压栈。具有浮点运算的处理器,总线错误和MemManage错误等异常可能会在惰性压栈期间产生。惰性压栈特性推迟了浮点寄存器的压栈,只有当异常处理中存在浮点运算时才会将浮点寄存器的值先压栈,后执行异常处理中的浮点运算。*/prvStartFirstTask();//在SVC异常中启动第一个任务return 0;
}
prvStartFirstTask()启动第一个任务函数详解
__asm void prvStartFirstTask( void )
{PRESERVE8 ldr r0, =0xE000ED08 //向量表偏移寄存器VTOR的地址,存储栈顶地址,对该寄存器VTOR的值进行解引用获得栈顶地址ldr r0, [r0] //取R0地址处的值赋给R0,R0地址处的值是一个地址ldr r0, [r0] //取R0地址处的值赋给R0, R0地址处的值是栈顶msr msp, r0 //复位栈顶MSPcpsie i //使能中断(清除PRIMASK)cpsie f //使能中断(清除FAULTMASK)dsb //数据同步屏障isb //指令同步屏障svc 0 //触发SVC异常nopnop
}
vPortSVCHandler()SVC异常处理函数详解
__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, =pxCurrentTCB //获取pxCurrentTCB指针的地址赋值R3ldr r1, [r3] //获取pxCurrentTCB指针指向的值赋值R1ldr r0, [r1] //获取R1地址处的值赋值给R0,获取任务栈栈顶指针,即任务控制块的第一个数据就是任务栈顶指针ldmia r0!, {r4-r11, r14}//R4~R11,R14寄存器出栈,栈顶移动36=4x9个字节,用户手动出栈msr psp, r0 //设置进程栈指针,PSP=R0isb //指令同步屏障mov r0, #0 //R0=0msr basepri, r0 //basepri寄存器=0,开中断bx r14 //硬件自动恢复R0~R3,R12,LR,PC,xPSP值,PC指向下一条指令
}
任务切换场景
任务切换场景:
1.执行系统调用接口(含有taskYIELD()的API)
2.Systick定时器中断
任务在哪里切换:任务在PendSV的异常中切换
__asm void xPortPendSVHandler( void )
{extern uxCriticalNesting;extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8mrs r0, psp //读取进程栈指针,保存在R0寄存器中isb ldr r3, =pxCurrentTCB //获取当前任务控制块的指针ldr r2, [r3] //将当前任务控制块的地址保存在R2寄存器中tst r14, #0x10it eq //判断是否使用FPU,使用的话在进行任务切换的时候要手动保存FPU寄存器到任务栈中vstmdbeq r0!, {s16-s31}//保存S16~S31这16个FPU寄存器stmdb r0!, {r4-r11, r14}//保存R4~R11,R14寄存器str r0, [r2]//将R0的值保存到R2所保存的地址中去,R2保存当前任务控制块的地址,等于更新任务块栈顶指针stmdb sp!, {r3}//将R3的值入栈mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITYmsr basepri, r0//关闭中断进入临界区dsbisbbl vTaskSwitchContext//调用vTaskSwitchContext()获取下一个运行的任务mov r0, #0msr basepri, r0//打开中断,退出临界区ldmia sp!, {r3}//R3的值出栈,新的任务控制块ldr r1, [r3]ldr r0, [r1]//获取新的任务的栈顶指针,保存到R0中ldmia r0!, {r4-r11, r14}//R4~R11,R14出栈tst r14, #0x10it eqvldmiaeq r0!, {s16-s31}//判断当前任务是否使用FPU,是的话手动恢复FPU寄存器msr psp, r0//跟新进程栈指针isb#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */#if WORKAROUND_PMU_CM001 == 1push { r14 }pop { pc }nop#endif#endifbx r14/*硬件自动恢复R0~R3,R12,LR,PC,xPSP值,确定异常返回后进入处理器模式还是进程模式,使用主栈指针MSP还是进程栈指针PSP。后续进入进程模式,使用进程栈指针,PC的值指向新任务的函数,新任务开始运行*/
}
任务同步机制
- 队列
- 信号量(二值/计数/互斥)
- 事件组
- 任务通知(消息邮箱)
队列
队列传输数据的两种方式:
- 拷贝:把数据复制进队列
- 引用:把数据地址复制进队列,数据要做保护
多任务发送队列,怎么区别数据来源:
- 结构体:ID+数据形式
- 位拼接:高位表示ID,低位表示数据
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
BaseType_t xQueueReset( QueueHandle_t pxQueue);
void vQueueDelete( QueueHandle_t xQueue );
BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxTaskWoken);
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );//返回队列已有数据空间个数
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );//返回队列剩余数据空间个数
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);//队列覆盖写
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t*pxHigherPriorityTaskWoken);
BaseType_t xQueuePeek(QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait);//复制队列中的数据,不删除数据,队列中无数据时阻塞
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);
信号量
- 通知作用
- 计数型信号,信号初值自定义,发送加1,接收减1
- 二进制信号,信号初值是0,发送加1,接收减1
- 互斥信号,防止同时访问共享资源(共享资源:全局变量,静态变量,公共函数)
- 互斥信号禁止中断中使用
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
SemaphoreHandle_t xSemaphoreCreateBinary( void );
SemaphoreHandle_t xSemaphoreCreateMutex( void );//互斥锁,他人也能解锁
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );//递归锁,自己上锁自己解锁,他人无法解锁
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
事件组
- 事件发生,广播所有任务
- 事件可以与或组合
- 多任务可以同步
EventGroupHandle_t xEventGroupCreate( void );
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
EventBits_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet ,BaseType_t * pxHigherPriorityTaskWoken);
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );
任务通知
- 明确通知的任务,数据任务独享
- 不额外开辟内存
- 效率更高
- 中断中只能发送数据给任务
/*任务通知计数功能函数*/
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
/*任务通知复杂功能函数*/
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyActioneAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
任务延时
vTaskDelay(n):进入vTaskDelay到退出间隔至少n个Tick中断
xTaskDelayUntil(&Pre, n):两次退出 xTaskDelayUntil 的时间至少是n个Tick中断,也就是任务运行时的Tick+Delay的Tick
相关文章:
RTOS随笔之FreeRTOS启动与同步方法
RTOS启动与同步机制RTOS启动任务切换场景任务同步机制队列信号量事件组任务通知任务延时RTOS启动 FreeRTOS在任务创建完成后调用函数vTaskStartScheduler()启动任务调度器。 vTaskStartScheduler()任务启动函数详解 void vTaskStartScheduler( void ) {BaseType_t xReturn;xR…...
【AI/NLP】InstructGPT数据标注问题
文章目录1 背景介绍2 标记员筛选2.1 标记员筛选标准3 数据集及其标注3.1 预训练3.2 微调3.2.1 SFT-demonstration data3.2.2 RM-comparison data3.3 数据集大小4 模型实现1 背景介绍 ChatGPT的训练过程与InstructGPT相近,大致分为三步: SFT:…...
三次握手和四次挥手
文章目录TCP三次握手为什么要三次握手三次握手可以携带数据吗?三次握手失败,服务端会如何处理?ISN代表什么,意义,何要动态随机什么是半连接队列第2次握手传回了ACK,为什么还要传回SYN?为什么要四次挥手TCP…...
Jmeter常用断言之响应断言详解
响应断言是最常用的一种断言方法,主要是对响应结果中的文本内容进行断言,比如响应结果是否包含指定的值,或者是否等于指定的值。响应断言可以适用各种返回类型的响应结果,如:Test、html、application/json、applicatio…...
【Python学习笔记】36.Python3 MySQL - mysql-connector 驱动(1)
前言 MySQL 是最流行的关系型数据库管理系统,本章节为大家介绍使用 mysql-connector 来连接使用 MySQL, mysql-connector 是 MySQL 官方提供的驱动器。 Python3 MySQL - mysql-connector 驱动 我们可以使用 pip 命令来安装 mysql-connector࿱…...
计算机SCI论文课题设计需要注意什么? - 易智编译EaseEditing
课题设计就要本着严谨性和可行性来进行。实验设计的类型要选择准确,统计学的方法要运用合理,研究对象和观察指标的选择也要符合研究目的的要求,技术路线要清晰明了。 关于课题的设计的可行性也要综合考虑,比如前期的相关工作基础…...
Quartz入门教程
本文参考文章编写 Quartz 官网 Quartz 是 OpenSymphony 开源组织在 Job Scheduling 领域又一个开源项目,是完全由 Java 开发的一个开源任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时ÿ…...
TypeScript 学习之 function
函数可以实现抽象层,模拟类,信息隐藏和模块。 函数有:有名字的函数、匿名函数 在 JavaScript 中的函数 // 有名字的函数 function add(x, y) {return x y; }// 匿名函数 let myAdd function (x, y) {return x y; };函数类型 typescript 可…...
【云计算自学路线】
云计算包含的技术内容和涉及的方向比较多,一定要进行系统化的学习才能更好的掌握这门技术。 云计算作为互联网新技术领域,现阶段也是出于高速发展期,想学习加入云计算行业的小伙伴可以抓紧机会了,跟着小课一起来了解云计算以及它…...
code01 v2黑屏、花屏、死机、断电重启、休眠死机的进来
症状解决 长话简说,症状如下: 使用浏览器、播放视频等,遇到突然死机或花屏死机的情况 关闭硬件加速,如:浏览器中设置关闭硬件加速,出现这种症状的软件都需要设置 开机电流音、播放与暂停时喇叭吱吱想、打…...
分享107个HTML电子商务模板,总有一款适合您
分享107个HTML电子商务模板,总有一款适合您 107个HTML电子商务模板下载链接:https://pan.baidu.com/s/1VW67Wjso1BRpH7O3IlbZwg?pwd0d4s 提取码:0d4s Python采集代码下载链接:采集代码.zip - 蓝奏云 Aplustemplates 购物模板…...
Barra模型因子的构建及应用系列三之Momentum因子
一、摘要 在之前的Barra模型系列文章中,我们已经初步讲解、构建了Size因子和Beta因子,并分别创建了对应的单因子策略。通过回测发现,其中Size因子的小市值效应具有很强的收益能力。而本篇文章将在该系列下进一步构建Momentum因子。 二、模型…...
8.2.1.3 索引合并优化
索引合并访问方法检索具有多个范围扫描的行,并将其结果合并为一个。此访问方法仅合并来自单个表的索引扫描,而不是跨多个表的扫描。合并可以生成其基础扫描的合并、交叉或交叉的合并。 可以使用索引合并的查询示例: SELECT * FROM tbl_name…...
水雨情在线小能手-雨量水位报警站
雨量水位报警站由水位探测器、雨量传感器、报警灯、扩音器、太阳能板和采集传输控制器组成。实时采集水位等级,三个水位探测器对应3个水位等级,当现场水面浸没相应探测器时,本机会实时发出语音报警,同时可发送相应的预警/报警等级…...
【蓝桥杯集训4】双指针专题(6 / 6)
目录 3768. 字符串删减 - 滑动窗口ac 799. 最长连续不重复子序列 - 滑动窗口 800. 数组元素的目标和 - 二分ac 2816. 判断子序列 - 双指针 1238. 日志统计 - 滑动窗口 1240. 完全二叉树的权值 - 双指针 1、前缀和 - 通过了 5/12个数据 2、双指针 3768. 字符串删减 -…...
文件流,gzip解压,压缩
目录 文件画布 写入 (空文件Foutnew File(Parent,entry.getName());)FileOutputStream outnew FileOutputStream(Fout);BufferedOutputStream Boutnew BufferedOutputStream(out);其他流量基于基础包装文件--文件流---字节流 顺序pbf一般是形成后再压缩目…...
在线开会,来开开圆桌会议吧~
圆桌会议应用场景:适合内部培训、部门会议亦或是头脑风暴等较为轻松的场景,有兴趣的朋友可以联系我来测试哦~~ 上图: 图:圆桌会议应用截图 在圆桌布局之下,企业可以将每一位参会者和座位绑定,1:1模拟线下圆…...
使用营销自动化的 7 大主要优势
对于大多数企业家来说,自动化已成为在数字时代简化业务的必要条件。那么,您可以采取哪些步骤来实施营销自动化呢? 1. 社交媒体整合 拥有吸引人的社交媒体形象是成功的先决条件。您不可能完成所有社交媒体营销任务,使用自动化软件&…...
【图像分类】基于PyTorch搭建GRU实现MNIST手写数字体识别(单/双向GRU,附完整代码和数据集)
写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 在https://blog.csdn.net/AugustMe/article/details/128969138文章中,我们使用了基于PyTorch搭建LSTM实现MNIST手…...
day14_oop_抽象_接口
今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、抽象 三、接口 零、 复习昨日 多态的好处: 扩展性强.加入新的功能,不需要改动代码降低代码耦合度(解耦合或者松耦合) 一、抽象类 1.1 抽象类…...
模式识别 | MATLAB实现DNN深度神经网络模式分类识别
分类预测 | MATLAB实现DNN全连接神经网络多特征分类预测 目录 分类预测 | MATLAB实现DNN全连接神经网络多特征分类预测基本介绍任务描述程序设计参考资料基本介绍 DNN的结构不固定,一般神经网络包括输入层、隐藏层和输出层,一个DNN结构只有一个输入层,一个输出层,输入层和输…...
【C++】类和对象三大特性--继承
文章目录1.继承的概念及定义1.1继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化2.基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6. 继承与静态成员7.复杂的菱形继承及菱形虚拟继承虚拟继承解决数…...
MySQL的存储引擎
目录 一.概念 二.分类 操作 修改默认存储引擎 一.概念 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能。现在许多不…...
工程项目管理系统源码-简洁+好用+全面-工程项目管理系统
工程项目管理系统是指从事工程项目管理的企业(以下简称工程项目管理企业)受业主委托,按照合同约定,代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 系统定义 工程项目管理企业不直接与该工程项目的总承…...
什么是STAR原则?
文章目录📋前言🔥省流版🎯什么是STAR原则🎯进行过程📋前言 对于大部分还在学习阶段的学生们来说,可能并不了解这个原则的含义,这里的star并不是指英文单词星星。这个原则我也是前段时间才认识到…...
前置知识-初值问题、显式隐式龙格库塔方法、Butcher阵列
1.1.4 龙格一库塔法 将向前欧拉法写成式 (1-37) 的形式, 可以看出它实际上利用了 f ( x , u ) f(x, u) f(x,u) 在 x n...
PythonWeb Django PostgreSQL创建Web项目(二)
安装数据库PostgreSQL并创建数据库 我第一次尝试使用PostgreSQL数据库,why?我喜欢它提供的丰富的数据类型,例如货币类型、枚举类型、几何类型(点、直线、线段、矩形等等)、网络地址类型、文本搜索类型、XML类型JSON类型等等,非常…...
Python学习笔记:使用字符串
使用字符串 使用字符串格式:精简版 百分号 % # 指定要设置其格式的值时,可使用单个值(如字符串或数字),可使用元组(如果要设置多个值得格式),还可使用字典 >>> format …...
echarts饼图封装
1. 组件 <template> <div :id"id" class"main" :style"{ width: width, height: height }" :ref"id" ></div> </template> <script> import * as echarts from "echarts"; export default { …...
Web3.0 教学基础一
目录 什么是web3.0 Web 1.0 概念 Web 2.0 概念 Web 3.0 概念 Web 3.0 的优势 什么是DAPP 什么是web3.0 在了解web3.0之前我们需要了解下前面的web1.0与web2.0。 Web 1.0 概念 Web1.0是万维网最初的版本,而静态网站则被认为是全网Web 1.0的起源,用…...
网站制作产品优化/搜索引擎优化的基本内容
Vue.js入门知识day2添加新品牌删除品牌(根据id删除数据)方法一:方法二根据条件筛选品牌方法一方法二Vue全局过滤器过滤器的定义语法过滤器调用时候的格式定义一个 Vue全局的过滤器:所谓的全局过滤器,就是所有的vm实例都…...
做类似猪八戒网的网站/广告搜索引擎
文章目录1.sudo !!2.mtr 命令3.nl 命令4.shulf 和tree 、pstreeshulf 命令tree命令pstree 这个是进程按树形结构显示,显示当前进程以及相关子进程,输出信息跟“tree”类似5.last 命令6.curl ifconfig.me7.lsof -i:端口号8.cut 命令9.seq 命令11.关于 脚本…...
成都手机号码销售网站建设/北京seo公司工作
原文地址:Go-翻过的一些面试题目 1、以下代码会输出什么?请简要说明。 var c make(chan int) var a intfunc f() {a 1<-c }func main() {go f()c <- 0print(a) } 能正确输出1,不过主协程会阻塞 f() 函数的执行。 2、以下代码会输…...
创建自己的网站/怎么设计网站
这段时间泡论坛,听到了很多跳槽程序员的困惑和迷茫。“工作太无聊了,每天没有成就感,想换却找不到方向……”“35岁了,曾经的同学都年薪百万了,自己还是不上不下的……”“晋升好难,看不到上升空间了……”…...
湖北省建设银行网站6/电子商务主要干什么
对于数据分析师来说,可视化永远是一门不过时的学问,不仅因为上到企业领导、下到业务分析都要用到可视化,更因为它是分析师手中的优秀工具,它向我们揭示了数据背后的规律。 但很多人又会问,自己做的数据可视化丑出天际&…...
wordpress如何添加首页图片/抖音黑科技引流推广神器
今天调试代码的过程中,F8失效 解决办法 关掉有道词典!...