线程操作系统调度CPU的最小单位一个进程可以有多个线程共享进程资源每个线程有独立的执行流、独立栈、独立寄存器上下文。进程拥有独立的地址空间全局变量、堆。开销大。工程中一个tcb线程控制块即一个任务线程的详细数据结构typedef struct tcb { //sp 放第一个 汇编永远用 [r1] 零偏移 void *sp; /* 当前栈指针 (切换时存/取) */ void *entry; /* 线程入口函数 */ void *parameter; /* 入口函数参数 */ void *stack_addr; /* 栈起始地址 (低地址) */ uint32_t stack_size; /* 栈大小 (字节) */ uint8_t priority; /* 优先级: 0 最高, 越大越低 */ uint8_t base_priority; /* 基础优先级 创建时的静态优先级 (PI 只临时改 priority, 不动它) */ uint8_t state; //线程状态 const char *name; /* 线程名 (栈检查报警 / 将来 ps 命令用) */ list_t list; /* 链表节点: 同一时刻只在 就绪队列 或 睡眠队列 之一 */ uint32_t wake_tick;/* 唤醒时刻 (绝对 tick, 替代旧 remaining_ticks) */ list_t tick_node; /* 定时器链表节点 (独立于 list, 阻塞时同时挂两条队) */ list_t all_node; /* 全局线程链表节点: 栈检查 / ps 遍历所有线程 */ list_t taken_list; /* 持有锁链表: 挂各 mutex_t.taken_node (PI 重算时遍历) */ uint8_t timeout; /* 本次唤醒是否因超时: 1超时, 0正常唤醒 */ int block_result; /* 阻塞 API 返回值: 0成功, -ETIMEDOUT超时 */ uint8_t dynamic; /* 1TCB栈堆分配 (idle 回收时可 free); 0静态分配, 只摘链不释放 */ } tcb_t;其中sp当前栈指针必须放第一个成员方便在汇编中获取sp指针时不需要计算结构体偏移。entry线程的回调函数入口。parameter回调函数参数。stack_addr线程栈起始地址。stack_size线程栈大小。priority线程优先级大小0最高。base_priority基础优先级用于PI处理。state线程状态。name线程名。list链表节点。wake_tick唤醒时间。tick_node定时器链表。all_node全局链表节点。taken_list持锁链表。timeout超时唤醒标志。block_result阻塞的API返回值dynamic动态创建线程的标志线程状态如下typedef enum { THREAD_INIT 0, /* 已创建, 未 thread_start (不在任何队列) */ THREAD_READY, /* 在就绪队列 */ THREAD_BLOCKED, /* 阻塞: 在某 IPC 等待队列 (sem/mutex/mq) */ THREAD_SLEEP, /* 睡眠: 在睡眠队列 */ THREAD_SUSPENDED, /* 被挂起: 在挂起队列 (被 thread_suspend) */ THREAD_DEFUNCT, /* 僵尸: 已退出, 在 g_defunct_list 等 idle 回收 TCB/栈*/ } thread_state_t;THREAD_INIT初始化状态TCB与线程栈已经创建完毕但是没有调用thread_start()该状态下不会在任何队列调度器目前看不到这个线程。THREAD_READY就绪状态执行thread_start()后将该线程添加到就绪队列随时可以被调度器调度到CPU执行也可以通过其他方式进入就绪状态比如唤醒、定时器超时等。THREAD_BLOCKED阻塞状态线程主动等待某个IPC资源比如信号量、互斥锁、消息队列等唤醒后转为READY状态加入就绪队列。THREAD_SLEEP睡眠状态线程主动休息一段时间等待时间到了才可以继续运行时间到转为就绪队列。THREAD_SUSPENDED挂起状态内核调用thread_suspend()挂起不参与调度通过thread_resume()才能唤醒。THREAD_DEFUNCT僵尸状态线程函数已经执行完毕已经退出不再运行但是TCB任务块以及线程栈还不能立即释放放到僵尸链表由idle线程统一回收。状态机如下图所示伪造栈帧的本质新线程从没运行过没有“被打断的现场”可保存但PendSV不区分首次与后续切换只是机械地从sp取16个寄存器恢复伪造栈帧就是造一套假的寄存器快照放在栈里让PendSV第一次恢复无法分辨真假新线程就像“被打断过一样”自然执行。伪造栈帧需要填充寄存器Cortex-M内核存在16个寄存器R0-R15、xPSR具体功能如下表寄存器名称用途是否硬件自动压栈R0R0函数参数1是R1R1函数参数2是R2R2函数参数3是R3R3函数参数4是R4R4通用变量否R5R5通用变量否R6R6通用变量否R7R7通用变量否R8R8通用变量否R9R9通用变量否R10R10通用变量否R11R11通用变量否R12R12是R13SP栈指针栈指针否R14LR链接返回地址是R15PC程序计数器是注xPSR是状态寄存器硬件中断时一起压入栈帧伪造栈帧必须置Thumb位R0-R3、R12、LR、PC、xPSR由硬件自动压栈R4-R11由PendSV手动压栈。SP不会压入栈。上下文切换的核心就是把当前SP存放到tcb-sp切换时候再从tcb-sp取出。SP指针分为MSP与PSPMSP主栈指针复位默认使用中断服务函数默认使用MSP。PSP进程栈指针一般用于任务线程栈。LR链接寄存器正常调用函数bl func相当于LR下一条指令地址函数return跳LR进入异常时硬件自动保存一个特殊的EXC_RETURN值到LR用于异常返回。PC程序计数器PC保存当前正在取指的指令地址CPU没取一条指令PC自动加1.上下文切换逻辑进入PendSV异常硬件自动压栈R0 R1 R2 R3 LR PC xPSRPendSV汇编手动压栈R4-R11将SP存入tcb-sp调度器选择新的tcb任务取出新tcb的spPendSV弹出R4-R11异常返回硬件自动弹出R0 R1 R2 R3 LR PC xPSR跳转线程恢复运行压栈完成如下所示高地址 (栈底)xPSR 0x01000000 ┐pc entry │lr thread_exit │r12 0 │ 硬件异常栈帧r3 0 │ (8 个, bx lr 时硬件自动弹)r2 0 │r1 0 │r0 parameter ┘─────────────────────r11 0xdeadbeef ┐r10 0xdeadbeef │r9 0xdeadbeef │ 软件保存区r8 0xdeadbeef │ (8 个, PendSV 用 ldmia 弹)r7 0xdeadbeef │r6 0xdeadbeef │r5 0xdeadbeef │r4 0xdeadbeef ┘低地址 (栈顶)↑ TCB.sp 指向这里 (指向 r4 的位置)栈向下生长栈的上面是高地址下面是低地址硬件压栈在高地址软件压栈在低地址。压栈硬件自动压栈顺序为xPSR - PC - LR - R12 - R3 - R2 - R1 - R0软件压栈顺序为 R11 - R10 - R9 - R8 - R7 - R6 - R5 - R4出栈软件先弹顺序为 R4 - R5 - R6 - R7 - R8 - R9 - R10 - R11。SP随之到R0位置硬件在出弹顺序为 R0 - R1 - R2 - R3 - R12 - LR - PC - xPSR。PC被填成entry任务从入口开始跑。伪造栈帧过程如下1首先填充整个栈为0xDE2计算栈顶并设置八字节对齐这里通过 ~7UL设置对齐通过低三位0让其向下对齐到八字节如下stk (uint32_t *)((uint32_t)stack_top ~7UL);3R0填充回调函数的参数R1-R3、R12设置为0LR填充退出回调函数PC填充需要执行的回调函数Thumb置1如下frame-exception.r0 (uint32_t)parameter; /* entry 的第一个参数 */ frame-exception.r1 0; frame-exception.r2 0; frame-exception.r3 0; frame-exception.r12 0; frame-exception.lr (uint32_t)exit; /* entry return 后跳这里 */ frame-exception.pc (uint32_t)entry; /* 启动时跳到这里 */ frame-exception.psr 0x01000000UL; /* Thumb 位 1, 必须 */对tcb线程控制块的变量进行初始化后线程初始化完成。上下文切换是由PendSV触发的PendSV具体实现代码如下1获取PSP指针mrs r0, psp2获取当前正在执行的tcb的指针并获取里面内容ldr r1, g_current_tcbldr r1, [r1]3将R4-R11压栈stmdb r0!, {r4-r11}4将正在运行任务的tcb指针保存到tcb-sp中str r0, [r1]5取下一个任务的tcbldr r1, g_next_tcbldr r1, [r1]ldr r0, [r1]6将当前任务的指针指向下一个任务ldr r2, g_current_tcbstr r1, [r2]7弹出R4-R11ldmia r0!, {r4-r11}8设置新的任务栈地址切换栈帧msr psp, r09EXC_RETURN 触发硬件出栈弹出硬件自动压栈寄存器返回线程模式0xFFFFFFFD返回线程模式使用 PSP0xFFFFFFF9返回线程模式使用 MSP0xFFFFFFF1返回异常模式ldr lr, EXC_RETURN_PSPbx lr