嵌入式实战场景题第二弹
满满干货,每一道题都是经典!!!
此部分不只包含面经内容,均是实战类型不必全文背诵,有思路即可
后续收录于专栏:https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
1.4K空间其实可以存放很多int类型的数据的,第一个页表项占了一个int类型的一个空间,他怎么找到第二级页表的首地址?4K中存放了1024个地址,怎么找到某一个就是我想要找的那个二级页?
整个问题围绕的就是内存管理的页表,理解页表结构就可以回答这个问题.
知识点:
1.页表存储结构
- 页大小为4KB,每个页表项占用4字节,因此单个页表最多可存储 (4KB ÷ 4B = 1024)12。
2.虚拟地址转物理地址过程
虚拟地址被划分为多个字段。例如:一级页表索引(占10位,可寻址1024项)二级页表索引(同样占10位)页内偏移(占12位,对应4KB页大小)
程序逻辑地址 → 分段转换 → 虚拟地址(线性地址) → 分页转换 → 物理地址
三级页表转换方法:(两步)
(1)逻辑地址转线性地址:段起始地址+段内偏移地址=线性地址
(2)线性地址转物理地址:
每一个32位的线性地址被划分为三部分:页目录索引(10位)、页表索引(10位)、页内偏移(12位)
- 从cr3中取出进程的页目录地址(操作系统调用进程时,这个地址被装入寄存器中)
- 页目录地址 + 页目录索引 = 页表地址
- 页表地址 + 页表索引 = 页地址
- 页地址 + 页内偏移 = 物理地址
举例
页目录物理基地址存储在CR3寄存器中(如0x1000
)
虚拟地址为0x12345678
(32位)
页目录索引=
0x48
,页表索引=0x167
,页内偏移=0x678
1.定位一级页表项
- 页目录索引=高10位:0x12345678 >> 22 = 0x48
- 计算条目地址:CR3基址 + 索引×4 = 0x1000 + 0x48×4 = 0x1120
- 读取该地址内容(如0x3000),此为二级页表物理基地址
2.定位二级页表项
- 页表索引=中间10位:(0x12345678 >> 12) & 0x3FF = 0x167
- 计算条目地址:二级页表基址 + 索引×4 = 0x3000 + 0x167×4 = 0x35BC
- 读取该地址内容(如0x8000),此为物理页框基地址
这里的页面大小 是4kb(2^12),所以页地址 = PFN × 页面大小
页地址 = PFN × 页面大小 =
0x8000 <<12 = 0x8000000
3.计算最终物理地址
- 物理地址=页地址 +偏移(
0x678
) =0x8000678
2.中断为什么不能用互斥锁?一定要用锁用什么锁
知识点:
互斥锁的特点
一、基本使用过程
- 互斥锁的加锁、解锁操作是原子性的,保证执行过程中不会被其他线程中断。同一时刻仅有一个线程能持有锁,其他线程必须等待锁释放后才能获取。
- 若锁已被占用,请求线程会被挂起并进入阻塞状态,不占用CPU资源,直到锁被释放后唤醒。
- 通过对临界区的互斥访问,防止多个线程同时修改共享资源导致数据不一致。
二、功能特性
- 锁必须由持有线程主动释放,其他线程不可强制剥夺或越权操作。
- 某些互斥锁允许同一线程多次获取同一把锁(需解锁次数与加锁次数匹配)。
- 在实时操作系统中,当低优先级线程持有锁时,可临时继承高优先级线程的优先级,减少优先级反转问题的影响。
中断的特性
- 中断事件的发生不受程序控制,可随时由硬件或软件触发(如外部设备请求或程序异常).
- 中断机制使系统能够快速响应紧急事件(如硬件故障、数据到达),立即暂停当前任务并处理中断请求
- 中断的触发与主程序执行无关,可在任意时间点打断当前流程,无需等待其他任务完成。
答案:
由上边的中断和互斥锁的特性可以看到,互斥锁在获取失败时会使线程进入休眠状态(阻塞等待)而中断处理程序必须保持非阻塞、快速响应的特性,休眠会导致中断无法正常返回并引发死锁或系统崩溃。
总结一下如下:
- 互斥锁在获取失败时会触发线程休眠(进入阻塞状态),但中断处理程序运行在中,没有关联的进程或线程,无法保存休眠所需的执行现场(如栈、调度状态),强行休眠会导致内核状态不一致或系统崩溃。
- 中断处理程序必须快速完成并退出,休眠会破坏实时性要求,可能导致后续中断无法响应或引发死锁
- 互斥锁要求“哪个线程申请,就由哪个线程释放”,而中断上下文不绑定任何线程,释放锁的操作可能无法完成,导致资源永久占用
替代方案:自旋锁
自旋锁原理:通过忙等待(循环检查锁状态)避免线程休眠,确保中断处理程序快速完成。
只要中断中不让线程休眠就可以正常使用锁。
3.如何通过代码降低嵌入式系统功耗
1.低功耗模式(现有)
大部分的设备或者现有是有一些低功耗模式。
1.空闲时进入WFI/WFE模式
在空闲任务中循环执行__wfi()
或__wfe()
指令,关闭CPU时钟以消除动态功耗
void OS_Idle(void) { for (;;) { __wfi(); } }
2.启用Tickless模式
在操作系统中配置Tickless模式,通过动态调整系统时钟中断间隔,减少不必要的唤醒次数25。例如FreeRTOS中可通过configUSE_TICKLESS_IDLE
宏实现。
2.关闭没用的硬件资源损耗
1.外设时钟管理
在初始化阶段仅开启必要外设的时钟,闲置外设通过寄存器操作关闭时钟源。例如STM32中调用__HAL_RCC_GPIOA_CLK_DISABLE()
。
2.中断驱动替代轮询
避免使用while(NAND_FLASH_BUSY);
类阻塞代码,改用硬件就绪信号触发中断,或在循环中添加延迟(如OS_TASK_Delay_us(30)
)。
4.STM32的启动过程,如果从FLASH启动,为什么是0x08000000?
我以STM32F103ZE的空间地址映射去看一下,可以看到0x08000000 是位于代码区
首先我们要分清楚一个概念,那就是STM32 单片机跟arm的联系,例如STM32系列大多数使用ARM的Cortex-M3的内核,所以STM32也是基于ARM定下的规矩去运行。
那么这里我们指导Cortex-M3定下的规矩是从0地址启动,SMT32当然不能破坏ARM定下的“规矩”,那为什么STM32还可以从0x08000000去启动,讲道理STM32不应该按照Cortex M3的规矩从0开始启动吗,现在FLash 可是从0x08000000启动。
那我们先了解一个概念什么是重映射。
什么是STM32的重映射
那么我们先看一下 STM32的自举模式看一下这三种的区别。
可以从表上看出来,当你用Flash启动的时候,你的代码段地址会映射到Flash区然后从Flash启动。因为我们平时用的最多的就是从Flash去启动stm32 所以我这里以Flash举例去看一下什么是重映射。
可以从下图看到 如果 BOOT1 =X BOOT0=0 那么
实际上就是 从0x00000000-0x001FFFFF 运行的时候 从 0x08000000-0x081FFFFF(1MB) 读取代码,这就是重映射,然后这样就遵循了Cortex-M内核的启动规则。
提出问题:既然设置到0x0800 0000这么麻烦,为什么不直接使用0x0000 0000?
- 这是因为STM32不仅可以从内部Flash启动,还可以从系统存储器(可以实现串口ISP,USB DFU等程序下载方式,这个程序是ST固化好的程序代码)和从内部SRAM启动,
- 我们将内部Flash安排到0x0000 0000显然是不行的。这样会导致系统存储器或者内部SRAM无法重映射到0x0000 0000了。
5.程序大小超出flash存储,有什么优化方法?
一、代码层面优化
优化变量和一些数据结构
- 尽可能选用最小数据类型(如uint
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
BG双9,目前在某外企。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。