一 单项选择题(每题4分,共40分)
1. 在中断服务程序中修改变量时,必须使用哪个关键字?
A) static
B) const
C) volatile
D) extern
答案:C)volatile 解析:volatile 告知编译器该变量可能被意外修改(如中断中修改),禁止编译器优化对该变量的读写操作(如缓存到寄存器)。若不使用,可能导致程序读取过时值或忽略修改。 |
2. 在Cortex-M内核中,以下哪个操作会触发HardFault异常?
A)未对齐的LDRD指令访问
B) SVC指令调用
C) __disable_irq()
D) 嵌套中断
答案:A)未对齐的LDRD指令访问 解析:Cortex-M要求某些访问(如LDRD/STRD)必须地址对齐(4的倍数)。未对齐访问会触发HardFault。SVC、关中断、嵌套中断均合法。 |
3. 在C语言中,如何声明一个指向函数的指针?
A) int *func(int);
B) int (*func)(int);
C) int func(int);
D) int *func;
答案:B)int (*func)(int); 解析:int (*func)(int) 表示 func 是指向函数的指针,该函数接受一个 int 参数并返回 int。A是返回指针的函数,C是函数声明,D是指向整型的指针。 |
4. 对于32位微控制器,以下哪个操作是原子的(不可中断)?
A) 32位变量的读取
B) 32位变量的写入
C) 64位变量的读取
D) 自增操作(如i++)
答案:A)32位变量的读取 解析:32位MCU的数据总线为32位,对齐的32位变量读写通常单指令完成(原子)。64位变量需多条指令(非原子),自增(i++)涉及读-改-写三步(非原子)。 |
5. 在嵌入式系统中,使用位域(bit-field)的优点是什么?
A)节省内存
B) 提高访问速度
C) 便于硬件操作
D) 以上都是
答案:A)节省内存 解析:位域将多个变量压缩到同一字节,节省内存空间(如1位布尔值)。但访问速度可能降低(需位操作),且与硬件寄存器位域对齐时需谨慎。 |
6. I²C总线抗干扰的核心机制是:
A)开漏输出
B) 总线仲裁
C) 上拉电阻
D) 时钟拉伸
答案:A)开漏输出 解析:开漏输出(OD)允许多设备共享总线,通过上拉电阻置高电平。结合线与逻辑,可检测总线冲突(低电平覆盖高电平),实现仲裁。 |
7. 硬实时系统的关键指标是:
A) 平均响应时间
B) 最坏响应时间
C) CPU利用率
D) 内存占用率
答案:B)最坏响应时间 解析:硬实时系统要求任务必须在截止时间前完成,最坏响应时间是关键指标。平均响应时间和资源利用率(如CPU)是软实时系统指标。 |
8. 在实时操作系统中,任务的状态不包括以下哪一种?
A) 运行态
B) 阻塞态
C) 终止态
D) 挂起态
答案:C)终止态 解析:RTOS任务状态通常包括:运行态、就绪态、阻塞态、挂起态。终止态不存在(任务常设计为循环执行或删除后由系统回收资源)。 |
9. uC/OS-III中,就绪表(Ready List)采用的数据结构是:
A) 链表
B) 位图 + 链表
C) 红黑树
D) 队列
答案:B)位图 + 链表 解析:uC/OS-III使用位图(快速查找最高优先级任务)结合多优先级链表(同优先级任务队列),高效管理就绪任务。 |
10. 多核系统中Core间共享数据时,需优先采用的同步机制是:
A) 全局变量
B) Spinlock自旋锁
C) 信号量
D) 消息队列
答案:B)Spinlock自旋锁 解析:自旋锁通过原子指令(如LDREX/STREX)在共享内存上实现忙等待,避免上下文切换开销,适合短期数据保护。全局变量无原子性,信号量/队列可能引发调度。 |
二、填空题(每空5分,共25分)
1. 在Cortex-M的启动文件中,向量表第一个条目是Cortex-M向量表首项为Main Stack Pointer初始值,第二个条目是复位处理函数指针(程序入口)。
2. 以下结构体在ARM Cortex-M4下的内存占用为12字节:
struct
{
uint32_t id;
volatile uint8_t status;
unsigned short value;
void (*handler)(void*);
} dev;
3. Cortex-M中断优先级数值越小表示优先级越高。
4. 在32位MCU中访问GPIOB输出寄存器(假设地址为0x40020400),使用指针操作的代码:#define GPIOB_ODR *((volatile uint32_t *)(0x40020400))
三、简答题(每题8分,共32分)
1. 解释嵌入式系统中什么是临界区(Critical Section),如何保护临界区?
参考答案: 临界区是访问共享资源的代码段,需独占执行(避免数据竞争)。 保护方法: l 关中断:禁止中断(如__disable_irq()),适合裸机系统。 l 调度锁:禁止任务切换(如vTaskSuspendAll()),适用于RTOS。 l 原子操作:利用硬件指令(如LDREX/STREX)实现单步读写。 l 互斥锁:RTOS中的互斥量(Mutex),阻塞其他任务访问。 |
2. 解释内存对齐(Alignment)的概念及其在嵌入式系统中的重要性。
参考答案: 内存对齐要求数据地址是自身大小的整数倍(如4字节变量地址需是4的倍数)。 重要性: l 硬件要求:Cortex-M0等内核不支持未对齐访问,触发HardFault。 l 效率优化:对齐访问可单周期完成,未对齐需多次访问。 l 缓存友好:对齐数据提升缓存命中率。 |
3. 为什么RTOS需要提供“中断安全版”API(如xQueueSendFromISR)?在中断服务程序中调用普通API(如 xQueueSend)可能引发什么问题?
参考答案: l 普通API(如xQueueSend)可能触发任务调度。若在中断中调用,会尝试切换到任务上下文,导致未定义行为(中断无任务控制块)。 l 中断安全API(如xQueueSendFromISR) 特性: 1. 避免阻塞操作。 2. 仅标记需切换任务(如xHigherPriorityTaskWoken),退出中断后调度。 3. 使用中断专属队列或缓冲区。 |
4. 在AMP(非对称多处理)架构下,如何实现双核(Core0 & Core1)间的任务协作与负载均衡?
参考答案: 在非对称多处理(AMP)中(如Cortex-A7+Cortex-M4),各核独立运行OS/裸机。 实现方式: l 共享内存:预留物理内存区域,双核通过地址映射访问。 l 核间中断(IPI):一核触发另一核中断,通知事件(如数据就绪)。 l 消息传递:邮箱/消息队列传递任务信息。 l 静态分配:按功能划分任务(如Core0处理通信,Core1处理算法)。 l 动态负载均衡:主核监控负载,通过IPI通知从核调整任务。 |
四、编程题
1. 表达式解析器(23分)
要求:实现一个支持 + - * / ()的算术表达式解析器
函数原型:double evalExpression(const char* expr)
示例:输入 "3*(4+5)/2" -> 返回 13.5
附加要求:处理浮点数并验证表达式合法性
参考答案: #include <ctype.h> #include <stdlib.h> #include <string.h> #include <math.h> // 栈结构实现 #define MAX_STACK 128 typedef struct { double data[MAX_STACK]; int top; } NumStack; typedef struct { char data[MAX_STACK]; int top; } OpStack; void numPush(NumStack* s, double val) { s->data[++s->top] = val; } double numPop(NumStack* s) { return s->data[s->top--]; } void opPush(OpStack* s, char op) { s->data[++s->top] = op; } char opPop(OpStack* s) { return s->data[s->top--]; } // 运算符优先级比较 int precedence(char op) { if (op == '+' || op == '-') return 1; if (op == '*' || op == '/') return 2; return 0; // 括号优先级最低 } // 执行二元运算 void calculate(NumStack* num_stack, OpStack* op_stack) { char op = opPop(op_stack); double b = numPop(num_stack); double a = numPop(num_stack); switch (op) { case '+': numPush(num_stack, a + b); break; case '-': numPush(num_stack, a - b); break; case '*': numPush(num_stack, a * b); break; case '/': if (fabs(b) < 1e-9) { // 处理除零错误 numPush(num_stack, 0); } else { numPush(num_stack, a / b); } break; } } double evalExpression(const char* expr) { NumStack num_stack = { .top = -1 }; OpStack op_stack = { .top = -1 }; int expect_operand = 1; // 标记是否期望操作数(用于处理负数) // 添加安全终止符防止越界 char buf[1024]; snprintf(buf, sizeof(buf), "%s", expr); const char* p = buf; while (*p) { if (isspace(*p)) { // 跳过空格 p++; continue; } // 处理数字(包括负数) if (expect_operand && (*p == '-' || isdigit(*p)) || isdigit(*p) || *p == '.') { char* end; double num = strtod(p, &end); if (end == p) return NAN; // 无效表达式 numPush(&num_stack, num); p = end; expect_operand = 0; // 下一个期望操作符 continue; } // 处理左括号 if (*p == '(') { opPush(&op_stack, '('); p++; expect_operand = 1; // 括号后期待操作数或负数 continue; } // 处理右括号 if (*p == ')') { while (op_stack.top >= 0 && op_stack.data[op_stack.top] != '(') { calculate(&num_stack, &op_stack); } if (op_stack.top < 0 || op_stack.data[op_stack.top] != '(') { return NAN; // 括号不匹配 } op_stack.top--; // 弹出 '(' p++; expect_operand = 0; // 右括号后期待操作符 continue; } // 处理运算符 if (*p == '+' || *p == '-' || *p == '*' || *p == '/') { // 处理负数:当前是-号且上一个token是运算符或左括号 if (*p == '-' && expect_operand) { // 压入0以处理负号:例如 -3 → 0-3 numPush(&num_stack, 0); opPush(&op_stack, '-'); p++; continue; } // 比较运算符优先级 while (op_stack.top >= 0 && precedence(op_stack.data[op_stack.top]) >= precedence(*p)) { calculate(&num_stack, &op_stack); } opPush(&op_stack, *p++); expect_operand = 1; // 运算符后期待操作数 } else { return NAN; // 无效字符 } } // 完成剩余计算 while (op_stack.top >= 0) { if (op_stack.data[op_stack.top] == '(') return NAN; // 未匹配括号 calculate(&num_stack, &op_stack); } return numPop(&num_stack); // 返回最终结果 } // 测试用例 int main() { printf("%f\n", evalExpression("3*(4+5)/2")); // 13.5 printf("%f\n", evalExpression("-2*(-3+5)")); // -4.0 printf("%f\n", evalExpression("2+3 * 4")); // 14.0 printf("%f\n", evalExpression("(1+2)*(3+4)")); // 21.0 return 0; } |
算法解析: 双栈处理: num_stack 存储操作数 op_stack 存储运算符和括号 负数处理: 当遇到-号且前面没有操作数时(如表达式开头或(后),压入0转换为二元运算 优先级处理: 当前运算符优先级小于等于栈顶运算符时,先计算栈顶操作 括号处理: (直接入栈;遇到)时计算括号内所有操作 合法性验证: 括号匹配检查 无效字符检测(如字母) 除零错误处理 |
2. 动态规划算法(30分)
题目:编写一个函数,计算两个字符串的最长公共子序列(Longest Common Subsequence)的长度。
要求:函数原型 int lcs_length(char *s1, char *s2)。
示例:输入"ABCDGH"和"AEDFHR",输出3(对应"ADH")。
参考答案: int lcs_length(char *s1, char *s2) { int m = strlen(s1); int n = strlen(s2); // 创建二维DP表 (滚动数组优化空间) int dp[2][n + 1]; memset(dp, 0, sizeof(dp)); int idx = 0; // 滚动索引 for (int i = 1; i <= m; i++) { idx = 1 - idx; // 切换行 for (int j = 1; j <= n; j++) { if (s1[i - 1] == s2[j - 1]) { dp[idx][j] = dp[1 - idx][j - 1] + 1; } else { dp[idx][j] = fmax(dp[1 - idx][j], dp[idx][j - 1]); } } } return dp[idx][n]; } // 测试用例 int main() { printf("%d\n", lcs_length("ABCDGH", "AEDFHR")); // 3 (ADH) printf("%d\n", lcs_length("AGGTAB", "GXTXAYB")); // 4 (GTAB) printf("%d\n", lcs_length("ABC", "XYZ")); // 0 return 0; |
算法解析: DP状态定义: dp[i][j] 表示 s1[0..i-1] 和 s2[0..j-1] 的 LCS 长度 状态转移方程: if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 空间优化: 使用滚动数组(两行交替)将空间复杂度从 O(mn) 降至 O(n) 时间效率: 时间复杂度 O(mn),空间复杂度 O(n) |