系列文章目录



总结知识点

1.堆和栈的区别,栈里存放的内容
2.内存管理方案
3.任务调度的三种模式和四种任务状态
4.任务控制块:栈顶指针、链表节点;栈溢出检测两种方法
5.任务链表和时间片轮转的流程
6.互斥锁和优先级反转的原理
7.队列及环形缓冲区
8.信号量实现原理
9.任务通知,单播性
10.ZI-data、RO、RW、code和Flash、RAM的联系
11.临界区的含义和使用
12.死锁的条件和解决方法
13.哈佛结构和冯诺依曼、RISC-V和ARM

malloc申请的堆内存空间,同时会存储下申请的大小,free释放的时候,会导致堆碎片化,这个时候空闲内存肯定是通过链表连接在一起的

任务切换时,上下文保护,cpu寄存器只会压栈,以便后续恢复现场
PC(Program Counter 程序计数器):R15,存储下一个将要执行的指令的地址
SP(Stack Pointer 堆栈指针):R13,SP始终会指向栈的下一个空闲空间的地址
LR(Link Resigter 链接寄存器):R14,用于保存子程序或者是中断的返回地址
存储函数中定义的局部变量
保存函数调用时的地址,参数传递等信息
在这里插入图片描述
栈为啥是向下的:

在早期计算机系统中,内存布局通常将堆(heap)放在低地址,栈(stack)放在高地址,这样堆和栈可以相向生长,可以共享同一块内存区域,后来就成了传统

分静态分配和动态分配

// 静态定义任务栈数组和任务句柄
static StackType_t xTaskStack[ configMINIMAL_STACK_SIZE ];
static StaticTask_t xTaskBuffer;
 
// 创建静态任务
TaskHandle_t xTaskCreateStatic(
    TaskFunction_t pxTaskCode,       // 任务函数
    const char * const pcName,       // 任务名称
    uint32_t ulStackDepth,           // 栈深度(单位:字,如32位系统为4字节/字)
    void * const pvParameters,       // 任务参数
    UBaseType_t uxPriority,          // 任务优先级
    StackType_t * const puxStackBuffer, // 栈数组
    StaticTask_t * const pxTaskBuffer // 任务控制块
);

动态创建自动从堆中分配栈空间

**// 创建动态任务
BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    configSTACK_DEPTH_TYPE usStackDepth, // 栈深度(单位:字)
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask
);**

堆总大小定义

#define configTOTAL_HEAP_SIZE (16 * 1024)  // 16KB堆空间

内存管理方案

???
FreeRTOSConfig.h 的 configUSE_MALLOC_FAILED_HOOK
方案 1(heap_1.c)
特点:仅支持内存分配(无释放),速度最快,无内存碎片。无动态分配
适用场景:任务数量固定且无需删除的简单系统。
方案 2(heap_2.c)
特点:支持分配和释放,使用最佳匹配算法,但可能导致碎片化。 支持动态分配和释放,无合并
适用场景:任务创建和删除频繁,但栈大小相近的系统。
方案 3(heap_3.c)
特点:封装标准 C 库的malloc()和free(),线程安全。 标准库,编译器内存管理
适用场景:依赖标准库内存管理的系统。
方案 4(heap_4.c)
特点:支持合并相邻空闲块,减少碎片化,使用首次匹配算法。 最佳匹配算法和碎片合并
适用场景:任务栈大小差异较大的复杂系统。
方案 5(heap_5.c)
特点:支持在不连续的内存区域(如 SRAM 和外部 RAM)分配堆。 支持将多个不连续区域作为堆
适用场景:内存分布分散的系统

单片机的RAM,一部分用于编译时分配了全局变量和静态局部变量,剩下的才是堆和栈

项目里面用的heap4
在这里插入图片描述

任务调度

有三种方法:时间片轮转(同等优先级)、抢占式调度(高优先级抢占,响应快)、协程式调度(无抢占,低优先级任务不会被强制打断,适合共享资源无需锁的场景,变裸机开发?)

//抢占式+时间片轮转
#define configUSE_PREEMPTION     1  // 启用抢占式调度
#define configUSE_TIME_SLICING   1  // 启用时间片轮转(默认值)
#define configTICK_RATE_HZ     1000 // 设置时间片长度(如1ms)

// 纯协作式调度
#define configUSE_PREEMPTION        0
#define configUSE_TIME_SLICING      0

任务状态:运行态、就绪态、阻塞态、挂起态
挂起类似暂停,需要vTaskSuspend进入挂起、调用vTaskResume来解挂

删除态官方文件没有,应该不算,删除对应创建

任务对应的协程,协程不能通信,用于RAM资源严重不足的情况,调用 xCoRoutineCreate() 即可创建协程。协程优先级和任务不混,两个同时存在时,任务优先级始终高于协程,所以只有在没有优先级高于空闲任务的任务可以执行时才会执行协程。

任务状态的内部原理

任务控制块

typedef struct tskTaskControlBlock {
    StackType_t* pxTopOfStack;        // 任务栈顶指针(用于上下文切换)
    ListItem_t xStateListItem;        // 任务状态链表节点(用于挂载到任务链表)
    UBaseType_t uxPriority;           // 任务优先级(可动态修改)
    TickType_t xTicksToDelay;         // 延时 ticks 数(用于阻塞/延时状态)
    BaseType_t xOriginalPriority;     // 原始优先级(优先级继承时使用)
    char pcTaskName[ configMAX_TASK_NAME_LEN ];  // 任务名称
    // ... 其他信息(栈大小、任务状态、事件列表等)
} tskTCB;

Task Control Block,TCB,任务控制块包含了管理任务所需的关键信息:

动态创建任务时会分配两块内存,一块用于TCB、一块用于任务栈!!!所以一部分是任务控制块里面会存放任务切换的上下文;另一部分是任务栈,存放函数参数,包括形参、函数局部变量、返回地址和上下文切换时的CPU寄存器值(如 PC 程序计数器、R0-R15 通用寄存器等)。

TCB,任务栈顶指针,通过这个指针来恢复现场,找到寄存器数据的

TCB 中指向链表节点的指针,用于将任务组织到不同优先级的就绪链表、阻塞链表或挂起链表中,从而实现操作系统对任务的高效管理和调度。

实际情况下,挂载的是节点,通过这个节点,反向寻找任务,这可能是一种面对对象的编程,而且可能能节省链表空间?

// 链表节点结构体(FreeRTOS 中的 ListItem_t)
typedef struct xLIST_ITEM {
    TickType_t xItemValue;      // 节点值(用于排序,如优先级或阻塞时间)
    struct xLIST_ITEM* pxNext;  // 指向下一个节点
    struct xLIST_ITEM* pxPrevious;  // 指向上一个节点
    void* pvOwner;              // 指向该节点所属的 TCB(反向关联)
} ListItem_t;

任务调度机制核心依赖于任务链表(Task Lists),链表通过嵌入在 TCB 中的节点(ListItem_t)来管理任务。

所以它在进行栈溢出检测的时候,会有两种方法,第一个是栈顶指针检测,但有局限性,因为是动态的,像函数嵌套,在任务切换时才检测,可能检测不出来;第二种方法是栈底有几个字节用0x5a来填充的,所以超没超就看有没有覆盖。都有延迟性
在这里插入图片描述
如果你开启了 configCHECK_FOR_STACK_OVERFLOW,FreeRTOS会在栈的最低端(最低地址附近)放一个特殊的哨兵区(如固定值0xA5A5A5A5),周期性去检查它有没有被破坏。

任务本来就需要动态调用函数、分配局部变量,所以大的数据参数,容易把其他的破坏掉。

  [高地址] 栈顶
  | 局部变量       | <- 你的大数组可能从这里开始
  | 寄存器保存区   | <- 如果越界,可能破坏这里
  | 返回地址 (LR)  | <- 关键!被破坏后程序跳飞
  | 调用者栈帧     | <- 可能连带破坏
  | ...           |
  | 栈底哨兵区     | <- FreeRTOS 只检查这里
  [低地址]

栈为啥是向下的

时间片轮转的时候:

当调度器触发时间片轮转时:

  1. 定位当前任务优先级P:通过TCB获取当前任务优先级
  2. 访问优先级P对应的就绪链表:pxReadyTasksLists[P]
  3. 检查该优先级下的任务数:uxCurrentNumberOfTasks[P]
    • 若任务数=1:无需轮转,当前任务继续执行
    • 若任务数>1:执行时间片轮转
  4. 时间片计数器递减:
    • 每个任务运行时,优先级P的时间片计数器(xSchedulerRunning等相关变量)递减
    • 当计数器减为0时,触发轮转
  5. 链表遍历与任务切换:
    • 将当前任务从链表头部移除,插入到链表尾部
    • 取出链表新头部的任务作为下一个运行任务
    • 执行上下文切换(保存当前任务寄存器,恢复新任务状态)

PendSV与操作系统中的SVC协同工作。SVC不能挂起,它将立即被执行;而PendSV可以暂时挂起异常,对于操作系统来说这很有用,它可以等待一个重要的任务执行完毕后再处理该异常。
RTOS上下文切换在PendSv(pendable system call,可挂起系统调用)异常中进行,主要指的任务切换
原因:
1.PendSv优先级非常低,不影响中断执行
2.可挂起,触发延迟执行,确保不影响中断响应,中断完成后,再压栈任务的相关参数

Cortex-M架构中,中断参数的参数压栈和上下文保护是硬件自动完成

任务阻塞是指任务因等待某个事件(如延时、信号量、队列消息)而暂时无法执行的状态。当任务阻塞时,系统会将其从就绪链表移至阻塞链表,并在事件满足后再移回调就绪链表

中断

Cortex-M使用的是8位的寄存器来配置中断的优先级,在STM32中,只使用了高4位来配置中断优先级,所以最大只有16级。
中断优先级要在5-15之间,宏定义设置的

抢占优先级:高优先级可以打断低优先级的中断
子优先级:相同抢占优先级的两个中断同时发生的时候,子优先级数值小的先执行(数值小优先级高)(子优先级的中断之间不会抢占)
在这里插入图片描述
建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理(设置成分组4)

中断数值越小,优先级越大
任务的数值越大,优先级越大,最大优先级数程序里设置了,0-31

在这里插入图片描述
系统中断0-4,,不可调度,HardFault、NMI是两种最高优先级的异常
HardFault(硬件错误中断):数值0,堆栈溢出,访问非法内存,未对其内存访问等
NMI:不可屏蔽中断,优先级高于HardFault,看门狗、电源异常等会触发

PendSV 和 SysTick 设置最低中断优先级(数值最大,15),保证系统任务切换不会阻塞系统其他中断的响应。

临界区保护

任务和中断里面都可以设置临界区保护,只是函数不一样
适用于:
1.外设初始化
2.操作系统的代码有很多不能被打断
3.用户自己的需求

临界区:一段必须独占访问共享资源的代码区域。临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问。
所以诞生了临界区保护的函数,保证是原子操作

任务句柄

任务句柄内容有

任务调度器

osKernelStart()里面有开启任务调度器vTaskStartScheduler() 实现过程:
创建空闲任务
如果使能软件定时器,就创建定时器任务
关闭中断,防止调度器开启前收到中断干扰,运行第一个任务时会打开中断
初始化全局变量,将任务调度器标志设置为已运行
调用函数xPortStartScheduler()

通信方式

队列、信号量、互斥锁、任务通知、事件标志组

信号量有二值信号量和计数信号量(获取和释放操作)

优先级反转

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

互斥信号量也叫互斥锁,优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
这样将优先级翻转的危害降到了最低,防止其他低优先级的任务一直执行,不

互斥锁不能用于中断,因为潜在死锁和休眠的风险,使用自旋锁可以信号量只有非阻塞的能用,反正中断必须运行时间短

队列

队列:就是里面保存着数据的队列,不同的任务可以存放或读取
队列的三大核心:
1.关中断:这样的机制很多地方都有,内核启动中也有这样的操作,就是要确保队列读写不被打断,而且比起用锁等,更加轻量,不需要原子操作指令。(具体的还要深入学习)
2.环形缓冲区:用一段连续内存模拟循环队列,解决普通数组队列「假溢出」问题,这个在C语言学习时候分析了,因为它有个读写的地址,普通数组队列地址会无限往上加,所以到达一定数值后要回到头部。
3.链表:C语言里面提到的链式队列,入队出队通过修改链表指针实现,灵活但略微增加代码开销。
(读写各用一个链表,里面有数据和下一个的指针)
在这里插入图片描述
这一这个union,感觉会涉及到信号量

信号量

复用队列的底层结构,表示资源的数量,不存放数据,是计数值,是通道数,代表可以访问的信道

在这里插入图片描述
记录持有互斥锁的任务句柄。当信号量作为互斥锁(Mutex)时,标记当前哪个任务占据了锁,用于后面的优先级释放,也防止其他任务释放锁。

递归互斥锁的重入计数(同一线程(任务)重复获取锁的次数)。若信号量是递归互斥锁(Recursive Mutex),同一任务可多次「获取锁」,每次获取计数 +1,释放时计数 -1 ;只有计数归 0,锁才真正被释放,避免简单嵌套导致的死锁。(我理解,嵌套锁,需要解开外锁才能解开内锁,可能这里会出现问题)

实现核心:

  1. 关中断(或临界区)
    为避免任务 / 中断并发修改信号量状态(如计数器、等待队列),操作时会关中断(或进入临界区),确保原子性:
    申请信号量(xSemaphoreTake):关中断 → 检查计数器 → 按需阻塞任务 → 开中断;
    释放信号量(xSemaphoreGive):关中断 → 增加计数器 → 唤醒等待任务 → 开中断。

  2. 等待队列
    信号量内部维护等待任务链表(类似队列的 xTasksWaitingTo…)
    若信号量不可用(计数器为 0),申请任务会被移入该链表,进入阻塞态;
    当信号量被释放,从链表中唤醒最高优先级任务,重新竞争资源。

互斥量

1.互斥量有 “持有权” 概念,只有获取锁的任务能释放,其他任务释放无效。
二进制信号量:无严格持有权,任意任务 / 中断都可释放,灵活性高但需人工保证逻辑正确。

2.优先级反转,:互斥量支持,保障实时性
二值信号量不支持,可能出现长时间的阻塞

优先级继承代码:
在这里插入图片描述
代码含义:条件编译,使用了互斥锁才会编译。
判断队列队形是否是互斥锁,队列、信号量、互斥锁)统一用 Queue_t 结构体表示
进入临界区,将任务插入到优先级链表中(每个任务优先级都有对应的任务链表)

事件组

每bit代表了一个事件

在这里插入图片描述
事件组操作时,优先选择 “关调度器” 而非 “关中断” ,原因和实现如下:

关调度器(而非关中断)
FreeRTOS 提供 taskENTER_CRITICAL()(关调度器 + 按需关中断,取决于 configKERNEL_INTERRUPT_PRIORITY 配置 )或 vTaskSuspendAll()(仅关调度器 ),来保护事件组的任务级操作(如任务调用 xEventGroupSetBits )。
关调度器后,中断仍可正常触发(硬件中断不受影响),但任务无法切换,保证事件组操作的原子性。

中断函数中不能直接修改事件组(因为中断设置事件组的时候有可能唤醒多个满足条件的任务导致中断时间不确定),而是通过 xEventGroupSetBitsFromISR 触发 “守护任务”(daemon task) 来异步处理:

中断里向守护任务的队列发消息,请求设置事件位;
守护任务(任务上下文)真正执行 xEventGroupSetBits,此时用关调度器保证安全。

所以分中断还是任务里的事件位设置

任务通知

每个任务的控制块(TCB)中内置通知相关成员
条件:需在 FreeRTOSConfig.h 中定义 configUSE_TASK_NOTIFICATIONS = 1 开启功能

优势
高效性:无需额外结构体,操作直接,比队列、信号量、事件组更快,节省内存(每个任务仅额外占 8 字节存储通知状态和值 )。
灵活更新:支持多种通知值更新方式(如覆盖、保留原值、置位、递增等 ),适配不同场景。
局限
单播特性:仅能指定一个任务接收通知,无法广播给多任务。
数据缓存限制:任务控制块只有一个通知值,无法缓存多个数据,发送方也不能因发送受阻进入阻塞。
中断交互限制:可从中断发通知给任务,但无法给中断发通知(中断无任务结构体 )。

FreeRTOS和Linux的区别

FreeRTOS Linux
RTOS内核大小极简 Linux庞大
内存需求很小 通常几十MB
启动时间ms 启动时间秒级
确定实时性,在严格的时间约束内,可预测地完成特定任务的能力 通用性、高吞吐量、多任务分时处理
开发工具轻量keil等 丰富如GCC、GDB

STM32的Flash和RAM

SRAM是静态存储器,只要有电就能保持,所以适合高速缓存,读取后也不需要复写,KB-MB,L1、L2、L3、cache
DRAM是动态随机存取存储器,数据要周期性刷新才行,读取后要复写,但用到晶体管少,可以大量集成,用在主存储器,GB,内存条

srm32的flash就是ROM,(通常保存着text段、Code、Ro-data、Rw-data)
RAM(通常内部好似堆、栈、bss段、data段、ZI-data、RW-data)

在这里插入图片描述
map文件会有
在这里插入图片描述
ZI-data:即 Zero Init-data,== 0初始化的内存区的大小(该区域3个用途:0初始化的全局和静态变量+堆区+栈区)(RAM)。
RO-data:即 Read Only-data, 表示程序定义的
常量==,如 const 类型( FLASH)。
RW-data:即 Read Write-data, 非0初始化的全局和静态变量占用的RAM大小,同时还要占用等量的ROM大小用于存放这些非0变量的初值(FLASH+RAM)。已初始化的数据需要被存储在掉电不会丢失的FLASH中,上电后会从FLASH搬移到RAM中。所以RW-data两边都占用一些

程序占用FLASH=Code + RO-data + RW-data 即map文件中ROM size
程序占用RAM = RW-data + ZI-data 即map文件中RW size

STM32F103C8T6是64KB Flash 20KB的RAM

这里进行一个细致学习:
先进行一个区别,代码段、数据段、bss段、堆、栈
代码段:存放执行代码区域、只读
数据段:存放已初始化的全局变量和已初始化为非0的静态变态,C语言中的全局变量
.bss:汇编中程序员需要用伪指令(如 .bss)来声明未初始化的内存块,BSS(Block Started by Symbol)BSS 段中的变量没有初始值,但需要符号来标识它们的地址,以便后续代码访问。包括未初始化的全局变量、未初始化的静态变量、初始化为0的静态变量
堆:动态分配内容
栈:程序临时创建变量,函数里面定义的变量,函数调用时其他参数的压栈,栈先进后出的特点也方便用来保存和恢复调用现场
在这里插入图片描述
上图关于bss的全局变量可能不对,应该是下面:

int global_var = 0;  // 存储在.bss段
int global_var;      // 存储在.bss段
int global_var = 10;  // 存储在.data段

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象

四个条件:
互斥条件:要有不能共享的资源,一次只能被一个进程独占使用
占用并等待:进程已经持有至少一个资源,同时又在等待获取其他被占用的资源。
非抢占条件:进程已经获得的资源不能被强行夺走,必须由进程主动释放。
循环等待:多个进程互相等待对方释放资源,形成一个环形依赖链。

假设有两个任务:
Task A:需要先获取 Mutex 1,再获取 Mutex 2。
Task B:需要先获取 Mutex 2,再获取 Mutex 1。
执行顺序可能导致死锁:

Task A 获取了 Mutex 1。
Task B 获取了 Mutex 2。
Task A 尝试获取 Mutex 2,但被 Task B 占用,于是阻塞等待。
Task B 尝试获取 Mutex 1,但被 Task A 占用,于是也阻塞等待。
结果:两个任务互相等待,系统卡死!

处理方法:
互斥条件一般不会更改
比如破坏不抢占条件:当某个进程请求新的资源得不到满足时,便立即释放保持的所有资源
破坏循环等待:固定资源申请顺序,所有任务必须按相同顺序获取锁。规定所有任务必须先申请 Mutex 1,再申请 Mutex 2。
破坏占用并等待:让任务 一次性申请所有需要的资源,否则不分配

超时机制,避免无限等待

死锁检测:在线程和锁的数据结构中,ap、graph里面记录,线程请求失败时查看是否发生死锁,递归查找,看锁被谁占用。死锁发生时,更改线程优先级,线程回退。

总结:加锁顺序、加锁时限、一次性申请、死锁检测

RISC-V和ARM的区别

都是基于精简指令集计算原理建立的指令集架构,ARM是专有指令集架构,RISC-V是开源的。都是基于RISC精简指令集
指令集架构就是处理器如何执行指令的
ARM指令集和寄存器要比RISC-V丰富
RISC-V极简基础指令:仅40余条基础指令(RV32I/RV64I),ARM固定指令集:指令集版本(如ARMv8-A)包含大量预设指令
RISC-V指令集最大可以扩展到128位,目前ARM架构最高只能扩展到64位指令集
RISC-V更注重于执行效率和耗能优化,可以更好的适用于嵌入式和移动设备,ARM更注重于通用性和兼容性
ARM Cortex-M系列是哈佛结构,Cortex-A是冯诺依曼体系,RISC-V采用的是冯诺依曼
X86则是复杂指令集(CISC),指令集复杂,全面,也是冯诺依曼体系

哈佛结构和冯诺依曼结构

存储器:
在冯诺依曼架构中,程序存储器和数据存储器是共享的,即指令和数据都存储在同一个内存区域(通常是RAM)中。
由于指令和数据使用同一条总线来传输,处理器在每次访问内存时必须先读取指令,再读取数据,导致存在“瓶颈”(也称为冯诺依曼瓶颈),即在执行程序时,指令和数据的访问速度受到内存访问速度的限制。
哈佛架构:
在哈佛架构中,程序存储器和数据存储器是分开的,即指令和数据分别存储在不同的内存区域,并且通常有独立的总线进行访问。这种分离的设计使得指令和数据可以同时进行访问,从而提高了系统的吞吐量

指令和数据总线:
冯诺依曼:由于指令和数据共享同一总线,系统在同一时刻只能选择读取指令或数据,不能同时进行两者的访问。这限制了处理器的效率
哈佛:哈佛架构有独立的指令总线和数据总线,使得指令和数据可以并行传输。这种并行传输提升了处理器的效率和执行速度。

对应的哈佛结构需要更高的硬件成本。冯诺依曼灵活性更高,程序可以动态的修改存储数据

冯诺依曼架构通常用于通用计算机和处理复杂程序的系统中,比如台式机、服务器等。由于程序和数据存储在同一内存中,冯诺依曼架构可以较为方便地进行程序的修改和执行。

哈佛架构通常用于嵌入式系统、数字信号处理器(DSP)等应用中。这些应用通常需要更高的处理速度,且内存访问速度至关重要,因此哈佛架构的并行访问能力具有很大优势。

STM32是哈佛结构,程序存储器(Flash)和数据存储器(RAM)有独立的物理总线,允许并行访问,但有冯诺依曼的特性,他们都共享统一的线性地址空间

内存

malloc申请方式

在1G的内存空间中malloc申请1.2G:(windows下)
malloc申请的是虚拟地址空间,具体能申请多大,和操作系统版本、程序本身的大小、用到的动态/共享库数量、大小、程序栈数量、大小等的影响,甚至每次运行的结果都可能存在差异,因为有些操作系统使用了一种叫做随机地址分布的技术,使得进程的堆空间变小

linux下:
malloc申请的是虚拟空间,若物理空间不足,但虚拟空间还够,就能申请,但是标记未访问,实际访问的时候,若物理空间+swap不满足会触发错误,终止进程(自然malloc返回的是虚拟地址)

Swap(交换空间) 是 Linux 系统中用于扩展可用内存的磁盘空间,当物理内存(RAM)不足时,内核会将不活跃的内存页(Pages)临时转移到磁盘上的 Swap 区域,从而腾出物理内存供其他进程使用。

虚拟内存:应用程序可以拥有连续可用的内存,且编写比实际系统拥有内存大的多的程序,通常是被分割为多个物理内存碎片,以及外部磁盘存储器上,需要时进行存储交换

虚拟内存优点是:扩大了地址空间、内存保护、公平分配;但是需要建立很多数据结构,占用额外的内存、虚拟地址和物理地址的转换需要时间、页面的换入换出需要磁盘IO,耗时等

虚拟地址根据段选择符合段内偏移地址组成的

内存管理方式

内存管理的几种方式:
块式管理:即使程序片段字节不大,也会把一整块分给他
页式管理:划分为若干固定大小的区域,页,通过页表来映射物理页,存放内容可以拆分
段式管理:就是现在按照代码段、数据段、bss、堆、栈这样按照功能来划分

上下文

上下文就是现成传递给内核的参数以及内核要保存的变量和寄存器的值和当时的环境等。

用户级上下文、寄存器上下文和系统上下文

用户级上下文:正文、数据、用户堆栈、共享存储区
寄存器上下文:通用寄存器、程序寄存器、处理器寄存器、栈指针
系统上下文:进程控制块task_struct、内存管理信息、内核栈

究其原因,就是因为有两种状态,内核态和用户态,才会产生上下文的概念。
用户空间和内核有着不同的地址映射、寄存器组,所以通过上下文实现用户空间传递变量、参数给内核,内核也要传递用户进程一些寄存器变量,一遍后续返回用户空间继续执行

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐