FreeRTOS内存管理

news2024/12/26 10:56:35

内存管理是一个系统基本组成部分,FreeRTOS 中大量使用到了内存管理,比 如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使 用 FreeRTOS 提供的内存管理函数来申请和释放内存。本章要实现的功能是:使 用 heap_4.c 方案进行内存管理测试,创建了两个任务,分别是 LED 任务与内 存管理测试任务,内存管理测试任务通过检测按键是否按下来申请内存或释放内 存,当申请内存成功就向该内存写入一些数据,如当前系统的时间等信息,并且 通过串口输出相关信息;LED 任务是将 LED 翻转,表示系统处于运行状态。

1. 内存管理简介

1.1 内存管理介绍

FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申 请所需的 RAM。一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使 用静态方法的函数一般以“Static”结尾,比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户定义任务堆栈, 本章我们不讨论这种静态方法。 使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候 会动态的申请 RAM。标准 C 库中的 malloc()和 free()也可以实现动态内存管 理,但是如下原因限制了其使用: ● 这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。 ● 它们的实现可能非常的大,占据了相当大的一块代码空间。 ● 他们几乎都不是安全的。 ● 它们并不是确定的,每次调用这些函数执行的时间可能都不一样。 ● 它们有可能产生碎片。 ● 这两个函数会使得链接器配置得复杂。 不同的嵌入式系统对于内存分配和时间要求不同,因此一个内存分配算法可 以作为系统的可选选项。FreeRTOS 将内存分配作为移植层的一部分,这样 FreeRTOS 使用者就可以使用自己的合适的内存分配方法。 当内核需要 RAM 的时候可以使用 pvPortMalloc()来替代 malloc()申请内 存,不使用内存的时候可以使用 vPortFree()函数来替代 free()函数释放内存。 函数 pvPortMalloc()、vPortFree()与函数 malloc()、free()的函数原型类似。 FreeRTOS 对内存管理做了很多事情,FreeRTOS 的 V9.0.0 版本为我们提供 了 5 种内存管理算法,分别是 heap_1.c、heap_2.c、heap_3.c、heap_4.c、 heap_5.c,源文件存放于 FreeRTOS\Source\portable\MemMang 路径下,在使用 的时候选择其中一个添加到我们的工程中去即可

1.2 内存碎片

在看 FreeRTOS 的内存分配方法之前我们先来看一下什么叫做内存碎片,看 名字就知道是小块的、碎片化的内存。那么内存碎片是怎么来的呢?内存碎片是 伴随着内存申请和释放而来的,如下图所示

标记(1):此时内存堆还没有经过任何操作,为全新的。 标记(2):此时经过第一次内存分配,一共分出去了 4 块内存块,大小分别 为 80B、80B、10B 和 100B。 标记(3):有些应用使用完内存,进行了释放,从左往右第一个 80B 和后面 的 10B 这两个内存块就是释放的内存。如果此时有个应用需要 50B 的内存,那 么它可以从两个地方来获取到,一个是最前面的还没被分配过的剩余内存块,另 一个就是刚刚释放出来的 80B 的内存块。但是很明显,刚刚释放出来的这个 10B 的内存块就没法用了,除非此时有另外一个应用所需要的内存小于 10B。 标记(4):经过很多次的申请和释放以后,内存块被不断的分割、最终导致 大量很小的内存块。也就是图中 80B 和 50B 这两个内存块之间的小内存块,这 些内存块由于太小导致大多数应用无法使用,这些没法使用的内存块就沦为了内 存碎片! 内存碎片是内存管理算法重点解决的一个问题,否则的话会导致实际可用的 内存越来越少,最终应用程序因为分配不到合适的内存而崩溃。FreeRTOS 的 328 heap_4.c 就给我们提供了一个解决内存碎片的方法,那就是将内存碎片进行合 并组成一个新的可用的大内存块。

2. 内存分配方 法

FreeRTOS 规定了内存管理的函数接口,如下:

void *pvPortMalloc( size_t xSize ); //内存申请函数
void vPortFree( void *pv ); //内存释放函数
void vPortInitialiseBlocks( void ); //初始化内存堆函数
size_t xPortGetFreeHeapSize( void ); //获取当前未分配的内存堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ); //获取未分配的内存堆历史最
小值

对于 heap_1.c、heap_2.c 和 heap_4.c 这三种内存管理方案,内存堆实际 上是一个很大的数组,定义为 static uint8_t ucHeap[configTOTAL_HEAP_SIZE],而宏定义 configTOTAL_HEAP_SIZE 则表示系 统管理内存大小,单位为字,在 FreeRTOSConfig.h 中由用户设定。 对于 heap_3.c 这种内存管理方案,它封装了 C 标准库中的 malloc()和 free()函数,封装后的 malloc()和 free()函数具备保护,可以安全在嵌入式系 统中执行。因此,用户需要通过编译器或者启动文件设置堆空间。 heap_5.c 方案允许用户使用多个非连续内存堆空间,每个内存堆的起始地 址和大小由用户定义。这种应用其实还是很大的,比如做图形显示、GUI 等,可 能芯片内部的 RAM 是不够用户使用的,需要外部 SDRAM,那这种内存管理方案 则比较合适

12.2.1 heap_1 内存分配方法

heap_1 实现起来就是当需要 RAM 的时候就从一个大数组(内存堆)中分一 小块出来,大数组(内存堆)的容量为 configTOTAL_HEAP_SIZE,上面已经说了。 使用函数 xPortGetFreeHeapSize()可以获取内存堆中剩余内存大小。 heap_1 特性如下: 1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实 际上大多数的 FreeRTOS 应用都是这样的。 2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内 存碎片。 3、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到 的,也就是适合于那些不需要动态内存分配的应用

12.2.2 heap_2 内存分配方法

heap_2 提供了一个更好的分配算法,不像 heap_1 那样,heap_2 提供了内存 释放函数。heap_2 不会把释放的内存块合并成一个大块,这样有一个缺点,随 着你不断的申请内存,内存堆就会被分为很多个大小不一的内存(块),也就是会 导致内存碎片!heap_4 提供了空闲内存块合并的功能。 heap_2 的特性如下: 1、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要 334 注意有内存碎片产生! 2、如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下 面的示例: ● 如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都 是一样的,那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都是不同, 那么 heap_2 就不适合了,因为这样会导致内存碎片产生,最终导致任务分配不 到合适的堆栈!不过 heap_4 就很适合这种场景了。 ● 如果一个应用中所使用的队列存储区域每次都不同,那么 heap_2 就不 适合了,和上面一样,此时可以使用 heap_4。 ● 应用需要调用 pvPortMalloc()和 vPortFree()来申请和释放内存,而 不是通过 FreeRTOS 的其它 API 函数来间接的调用,这种情况下 heap_2 不适 合。 3、如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需 的内存大小不能确定,每次所需的内存都不相同,或者说大多数情况下所需的内 存都是不同的)的话可能会导致内存碎片。虽然这是小概率事件,但是还是要引 起我们的注意! 4、具有不可确定性,但是也远比标准 C 中的 mallo()和 free()效率高! heap_2 基本上可以适用于大多数的需要动态分配内存的工程中,而 heap_4 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能

12.2.3 heap_3 内存分配方法

heap_3.c 方法只是简单的封装了标准 C 库中的 malloc()和 free()函数, 并且能满足常用的编译器。重新封装后的 malloc()和 free()函数具有保护功 能,采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。 heap_3.c 具有以下特点: 1、需要链接器设置一个堆,malloc()和 free()函数由编译器提供。 2、具有不确定性。 3、很可能增大 RTOS 内核的代码大小。 要注意的是在使用 heap_3.c 方案时,FreeRTOSConfig.h 文件中的 configTOTAL_HEAP_SIZE 宏定义不起作用。在 STM32 系列的工程中,这个由编 译器定义的堆都在启动文件里面设置,单位为字节,我们具体以 STM32F10x 系 列为例 ,具体如下。而其它系列的都差不多。

12.2.4. heap_4分配方法简介

heap_4 提供了一个最优的匹配算法,不像 heap_2,heap_4 会将内存碎片 合并成一个大的可用内存块,它提供了内存块合并算法。内存堆为 ucHeap[], 大小同样为 configTOTAL_HEAP_SIZE。可以通过函数 xPortGetFreeHeapSize() 来获取剩余的内存大小。 heap_4 特性如下: 1、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等 的应用中。 2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机 的。 3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。 heap_4 非常适合于那些需要直接调用函数 pvPortMalloc()和 vPortFree() 来申请和释放内存的应用,注意,我们移植 FreeRTOS 的时候就选择的 heap_4。 heap_4 也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。 heap_4 也定义了两个局部静态变量 xStart 和 pxEnd 来表示链表头和尾,其中 pxEnd 是指向 BlockLink_t 的指针

ps:(本次代码包括之前的都使用的是heap_4)

2.5 heap_5 内存分配方法

heap_5 使用了和 heap_4 相同的合并算法,内存管理实现起来基本相同, 359 但是 heap_5 允许内存堆跨越多个不连续的内存段。比如 STM32 的内部 RAM 可 以作为内存堆,但是 STM32 内部 RAM 比较小,遇到那些需要大容量 RAM 的应 用就不行了,如音视频处理。不过 STM32 可以外接 SRAM 甚至大容量的 SDRAM, 如果使用 heap_4 的话你就只能在内部 RAM 和外部 SRAM 或 SDRAM 之间二选 一了,使用 heap_5 的话就不存在这个问题,两个都可以一起作为内存堆来用。 如果使用 heap_5 的话,在调用 API 函数之前需要先调用函数 vPortDefineHeapRegions ()来对内存堆做初始化处理,在 vPortDefineHeapRegions()未执行完之前禁止调用任何可能会调用 pvPortMalloc()的 API 函数!比如创建任务、信号量、队列等函数。函数 vPortDefineHeapRegions()只有一个参数,参数是一个 HeapRegion_t 类型的数 组,HeapRegion 为一个结构体,此结构体在 portable.h 中有定义,定义如下:

typedef struct HeapRegion
{
    uint8_t *pucStartAddress; //内存块的起始地址
    size_t xSizeInBytes; //内存段大小
} HeapRegion_t;

上面说了,heap_5 允许内存堆跨越多个不连续的内存段,这些不连续的内 存段就是由结构体 HeapRegion_t 来定义的。比如以 STM32F103 开发板为例, 现在有 2 个内存段:内部 SRAM、外部 SRAM,起始分别为:0X20000000、 0x68000000,大小分别为:64KB、1MB,那么数组就如下:

HeapRegion_t xHeapRegions[] =
{
    { ( uint8_t * ) 0X20000000UL, 0x10000 },//内部 SRAM 内存,起始地址
    0X20000000,
    //大小为 64KB
    { ( uint8_t * ) 0X68000000UL, 0x100000},//外部 SRAM 内存,起始地址
    0x68000000,
    //大小为 1MB
    { NULL, 0 } //数组结尾
};

注意,数组中成员顺序按照地址从低到高的顺序排列,而且最后一个成员必 须使用 NULL。heap_5 允许内存堆不连续,说白了就是允许有多个内存堆。在 heap_2 和 heap_4 中只有一个内存堆,初始化的时候只也只需要处理一个内存 堆。 heap_5 有多个内存堆,这些内存堆会被连接在一起,和空闲内存块链表类 360 似,这个处理过程由函数 vPortDefineHeapRegions()完成。 使用 heap_5 的时候在一开始就应该先调用函数 vPortDefineHeapRegions() 完成内存堆的初始化!然后才能创建任务、信号量这些东西。 heap_5 的内存申请和释放函数和 heap_4 基本一样,这里就不详细讲解了, 大家可以对照着前面 heap_4 的相关内容来自行分析。 至此,FreeRTOS 官方提供的 5 种内存分配方法已经讲完了,heap_1 最简 单,但是只能申请内存,不能释放。heap_2 提供了内存释放函数,用户代码也 可以直接调用函数 pvPortMalloc()和 vPortFree()来申请和释放内存,但是 heap_2 会导致内存碎片的产生!heap_3 是对标准 C 库中的函数 malloc()和 free()的简单封装,并且提供了线程保护。heap_4 相对与 heap_2 提供了内存 合并功能,可以降低内存碎片的产生,我们移植 FreeRTOS 的时候就选择了 heap_4。heap_5 基本上和 heap_4 一样,只是 heap_5 支持内存堆使用不连续 的内存块。

3.整体代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "limits.h"

//任务优先级
#define START_TASK_PRIO        1
//任务堆栈大小    
#define START_STK_SIZE         128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define LED1_TASK_PRIO        2
//任务堆栈大小    
#define LED1_STK_SIZE         50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);


//任务优先级
#define KEY_TASK_PRIO        4
//任务堆栈大小    
#define KEY_STK_SIZE         512  
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);


uint8_t *Test_Ptr = NULL;


/*******************************************************************************
* 函 数 名         : main
* 函数功能           : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
int main()
{
    SysTick_Init(72);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
    LED_Init();
    KEY_Init();
    USART1_Init(115200);
    printf("FreeRTOS内存管理实验\r\n");
    printf("按下KEY1申请内存,按下KEY2释放内存\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();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
                             
    //创建LED1任务
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler); 
    
    //创建KEY任务
    xTaskCreate((TaskFunction_t )key_task,     
                (const char*    )"key_task",   
                (uint16_t       )KEY_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )KEY_TASK_PRIO,
                (TaskHandle_t*  )&KEYTask_Handler);
    
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

//LED1任务函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}

//KEY任务函数
void key_task(void *pvParameters)
{
    u8 key=0;
    uint32_t g_memsize;
    
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY1_PRESS)
        {
            if(NULL == Test_Ptr)
            {
                /* 获取当前内存大小 */
                g_memsize = xPortGetFreeHeapSize();
                printf("系统当前内存大小为 %d 字节,开始申请内存\n",g_memsize);
                Test_Ptr = pvPortMalloc(1024);
                if(NULL != Test_Ptr)
                {
                    printf("内存申请成功\n");
                    printf("申请到的内存地址为%#x\n",(int)Test_Ptr);

                    /* 获取当前内剩余存大小 */
                    g_memsize = xPortGetFreeHeapSize();
                    printf("系统当前内存剩余存大小为 %d 字节\n",g_memsize);
                          
                    //向Test_Ptr中写入当数据:当前系统时间
                    sprintf((char*)Test_Ptr,"当前系统TickCount = %d \n",xTaskGetTickCount());
                    printf("写入的数据是 %s \n",(char*)Test_Ptr);
                }
            }
            else
                printf("请先按下KEY2释放内存再申请\n");
        }
        
        if(key==KEY2_PRESS)
        {
            if(NULL != Test_Ptr)
            {
                printf("释放内存\n");
                vPortFree(Test_Ptr);//释放内存
                Test_Ptr=NULL;
                /* 获取当前内剩余存大小 */
                g_memsize = xPortGetFreeHeapSize();
                printf("系统当前内存大小为 %d 字节,内存释放完成\n",g_memsize);
            }
            else
                printf("请先按下KEY1申请内存再释放\n");
        }
        vTaskDelay(20);
    }
}

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

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

相关文章

剑指Offer 第21天 不用加减乘除做加法 二进制中1的个数

剑指 Offer 65. 不用加减乘除做加法 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用 “”、“-”、“*”、“/” 四则运算符号。 int add(int a, int b) {while(b ! 0){unsigned int c (unsigned)(a & b)<<1;a a ^ b;b c;}return a;} 剑…

Linux安装Mysql5.5

链接&#xff1a;https://pan.baidu.com/s/146KA6VfB4NW6mWSRRwXsMg 提取码&#xff1a;ib17 rpm安装Mysql5.5 检测Mysql是否安装 强制卸载原来的Mysql 安装Mysql服务端 安装Mysql客户端 启动Mysql------> service mysql start 连接Mysql------->mysql -u ro…

时序数据库

时序数据库(TSDB) 接下来就到了&#xff0c;自己所适应行业的数据库了&#xff0c;时许数据库&#xff0c;这类对物联网传感器数据有着很好的支持。 https://blog.csdn.net/firewater23/article/details/125697248 时序数据是随时间不断产生的一系列数据&#xff0c;简单来说…

AD936x_增益控制AGC详解

增益控制概述 所有AGC模式都可用于TDD和FDD场景。AD936x具有手动增益控制选项&#xff0c;允许基带处理器控制接收机的增益。 上图为AD936x接收信号路径示意图&#xff0c;每个接收机都有自己的增益表&#xff0c;将增益控制字映射到每个可变增益块。无论使用AGC还是手动增益控…

ABAP IDOC 测试及使用相关事务代码

WE02:查看IDOC日志和清单 WE19:测试IDOC 可以进入debug模式 WE20:维护伙伴的一些属性&#xff0c;比如如果加了增强结构&#xff0c;在这里可以增加 WE30:查看并且修改IDOC types 结构 WE31:查看SEGMENT 内的字段和版本。也可以新建segment WE82: 新增输出类型和assignment…

中间件Canal之Canal简单使用

一. 简单介绍 Canal是Java开发的基于数据库增量日志解析&#xff0c;提供增量数据订阅&消费的中间件。目前&#xff0c;Canal主要支持了MySQL的Binlog解析&#xff0c;解析完成后才能利用Canal Client来处理获得的相关数据。 二. MySQL的Binlog 2.1. Binlog是什么&#…

代码随想录算法训练营第37天 回溯算法 java :134. 加油站 135. 分发糖果 1005.K次取反后最大化的数组和

文章目录LeetCode 134. 加油站思路AC代码LeetCode135. 分发糖果思路AC代码LeetCode 1005.K次取反后最大化的数组和思路AC代码总结LeetCode 134. 加油站 思路 两个数组一个是 增加汽油量 gas[ ] 一个耗费汽油量 cost[ ] 可以换一个思路&#xff0c;首先如果总油量减去总消耗大…

OpenStack Yoga安装使用kolla-ansible

基本上是按照官网文档快速入门进行安装&#xff0c;不过还有很多地方需要换源。重点在换源这块。如果说你的网关有魔法&#xff0c;那就不用看这篇文章了&#xff0c;直接复制官网命令安装。 支持的操作系统 注意&#xff1a;不再支持 CentOS 7 作为主机操作系统。Train 版本同…

Java 的 IDEA 神级插件!

安装插件 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句&#xff0c;这也太智能了&#xff0c;还显示了每条语句使用频率。原因是它学习了我的项目代码&#xff0c;总结出了我的代码偏好。 如果让它再加上机器学习&#xff0c;人工智能写代码的时代还会远吗…

tkinter绘制组件(39)——滑动控件

tkinter绘制组件&#xff08;39&#xff09;——滑动控件引言布局函数结构响应按钮框架响应按钮的表示文本响应移动完整函数代码效果测试代码最终效果github项目pip下载结语引言 swipecontrol直译滑动控件&#xff0c;参考WinUI的SwipeControl。 虽然&#xff0c;这个控件在平…

凡人修C传——专栏从凡人到成仙系列目录

这里先感谢博主THUNDER王给我提出来的一个创作建议&#xff0c;让我有了创作的灵感来创建这一篇博客以及凡人修C传这一个系列的文章。 本文最主要的目的就是给大家一个凡人修C传的一个目录&#xff0c;让大家更加容易学到自己想学的地方。 &#x1f4dd;【个人主页】&#xff1…

js实现滑动进度条

效果图 完整代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevic…

TOOM舆情分析和报告工具,大数据决策免费舆情监控辅助工具?

大数据舆情工具是一种利用大数据技术进行舆情监控、分析、评估和预测的工具&#xff0c;以提高企业舆情应对能力。舆情监控工具可以帮助企业提高舆情应对能力&#xff0c;提升企业形象&#xff0c;以更好地处理各种舆情问题&#xff0c;TOOM舆情分析和报告工具&#xff0c;大数…

使用Python+Tensorflow的CNN技术快速识别验证码

近年来&#xff0c;机器学习变得愈加火热&#xff0c;中国选手柯洁与AlphaGo的人机大战更是引起热议。目前&#xff0c;在图像识别和视觉分析研究中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;技术的使用越来越多。Tensorflow 是由 Google 团队开发的神经网络模块&am…

三级管集电极开路电路工作原理详细分析

今天给大家分享的是&#xff1a;集电极开路电路、集电极开路晶体管电路、集电极开路工作原理、集电极开路TTL、集电极开路输出接线图、集电极开路优缺点。 在数字芯片设计、微控制器应用和运算放大器中&#xff0c;集电极开始输出通常用于驱动继电器等高负载或用于连接其他电路…

从双钻模型看产品规划

作为产品经理&#xff0c;我们在进行产品规划的时候&#xff0c;往往是采用“探索→执行”的思维进行规划&#xff0c;然而这类方法虽然有效&#xff0c;但不全面&#xff0c;也不一定能够科学地指引我们去进行合理的产品规划。那么&#xff0c;有什么方式或模型能够让我们合理…

笔记_html

目录什么是 HTML?HTML元素(定义)骨架HTML元素a标签语法使用1-超链接使用2-锚点定位使用3-文件下载使用4-阻止a标签的默认事件HTML5新增元素HTML5新增元素属性什么是 HTML? HTML是由一系列元素组成的超文本标记语言。 tips: html标签不区分大小写&#xff01; HTML元素(定义)…

多核异构处理器对共享外设和资源的调配方法-飞凌嵌入式

来源&#xff1a;飞凌嵌入式官网www.forlinx.com在多核异构CPU中&#xff0c;多个内核就如同多个大脑&#xff0c;而外设和内存等资源就如同手足&#xff0c;那么多个大脑该如何控制手足才能保证它们正常有序地运行呢&#xff1f;以NXP i.MX8M Plus处理器的A核和M核为例&#x…

为HTML网页添加喜庆气氛的诸多方法

为HTML网页添加喜庆气氛的诸多方法 节假日&#xff0c;如春节&#xff0c;为网页&#xff08;或网站的主网页&#xff09;营造欢乐祥和氛围的手段&#xff0c;还是比较多的&#xff0c;下面介绍。 先给出未加喜庆气氛修饰的网页源码如下&#xff0c;特意做的简单&#xff0c;意…

Docker - 10. 本地镜像发布到阿里云

将本地镜像发布到阿里云&#xff0c;具体步骤如下&#xff1a; 1. 注册并登录阿里云控制台&#xff1a;阿里云登录平台 2. 进入容器镜像服务&#xff1a;阿里云 - 容器镜像服务 3. 创建个人实例&#xff0c;未创建前如下图1&#xff0c;创建后见下图2 4. 打开并创建命名空间…