嵌入式实战场景题第二弹

满满干货,每一道题都是经典!!!

此部分不只包含面经内容,均是实战类型不必全文背诵,有思路即可

后续收录于专栏:https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM

1.4K空间其实可以存放很多int类型的数据的,第一个页表项占了一个int类型的一个空间,他怎么找到第二级页表的首地址?4K中存放了1024个地址,怎么找到某一个就是我想要找的那个二级页?

整个问题围绕的就是内存管理的页表,理解页表结构就可以回答这个问题.

知识点:

1‌.页表存储结构

  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.中断为什么不能用互斥锁?一定要用锁用什么锁

知识点:

互斥锁的特点

一、基本使用过程

  1. 互斥锁的加锁、解锁操作是原子性的,保证执行过程中不会被其他线程中断‌。同一时刻仅有一个线程能持有锁,其他线程必须等待锁释放后才能获取‌。
  2. 若锁已被占用,请求线程会被挂起并进入阻塞状态,不占用CPU资源,直到锁被释放后唤醒‌。
  3. 通过对临界区的互斥访问,防止多个线程同时修改共享资源导致数据不一致‌。

二、功能特性

  1. 锁必须由持有线程主动释放,其他线程不可强制剥夺或越权操作‌。
  2. 某些互斥锁允许同一线程多次获取同一把锁(需解锁次数与加锁次数匹配)‌。
  3. 在实时操作系统中,当低优先级线程持有锁时,可临时继承高优先级线程的优先级,减少优先级反转问题的影响‌。

中断的特性

  1. 中断事件的发生不受程序控制,可随时由硬件或软件触发(如外部设备请求或程序异常).
  2. 中断机制使系统能够快速响应紧急事件(如硬件故障、数据到达),立即暂停当前任务并处理中断请求‌
  3. 中断的触发与主程序执行无关,可在任意时间点打断当前流程,无需等待其他任务完成‌。

答案:

由上边的中断和互斥锁的特性可以看到,互斥锁在获取失败时会使线程进入休眠状态(阻塞等待)而中断处理程序必须保持非阻塞、快速响应的特性,休眠会导致中断无法正常返回并引发死锁或系统崩溃‌。

总结一下如下:

  1. 互斥锁在获取失败时会触发线程休眠(进入阻塞状态),但中断处理程序运行在中,没有关联的进程或线程,无法保存休眠所需的执行现场(如栈、调度状态),强行休眠会导致内核状态不一致或系统崩溃‌。
  2. 中断处理程序必须快速完成并退出,休眠会破坏实时性要求,可能导致后续中断无法响应或引发死锁‌
  3. 互斥锁要求“哪个线程申请,就由哪个线程释放”,而中断上下文不绑定任何线程,释放锁的操作可能无法完成,导致资源永久占用‌

替代方案:自旋锁

自旋锁原理:通过忙等待(循环检查锁状态)避免线程休眠,确保中断处理程序快速完成‌。

只要中断中不让线程休眠就可以正常使用锁。

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?

  1. 这是因为STM32不仅可以从内部Flash启动,还可以从系统存储器(可以实现串口ISP,USB DFU等程序下载方式,这个程序是ST固化好的程序代码)和从内部SRAM启动,
  2. 我们将内部Flash安排到0x0000 0000显然是不行的。这样会导致系统存储器或者内部SRAM无法重映射到0x0000 0000了。

5.程序大小超出flash存储,有什么优化方法?

一、代码层面优化

优化变量和一些数据结构

  1. 尽可能选用最小数据类型(如uint

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

c++/嵌入式面经专栏 文章被收录于专栏

BG双9,目前在某外企。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。

全部评论
遇到这种题我这小菜鸟连题目都看不懂
点赞 回复 分享
发布于 03-13 00:06 广东

相关推荐

评论
1
6
分享

创作者周榜

更多
牛客网
牛客企业服务