做360网站优化ui设计培训机构哪家好

当前位置: 首页 > news >正文

做360网站优化,ui设计培训机构哪家好,建设网站需要多长时间,c 语言能开发做网站吗1. 前置知识 本文基于arm cortex-m架构描述#xff0c; 关于arm cortex-m的一些基础知识可以参考我另外几篇文章#xff1a; arm cortex-m 架构简述arm异常处理分析c语言函数调用规范-基于arm 分析 2 SVC指令 2.1 SVC指令位域表示 bit15 - bit12#xff1a;条件码#…1. 前置知识 本文基于arm cortex-m架构描述 关于arm cortex-m的一些基础知识可以参考我另外几篇文章 arm cortex-m 架构简述arm异常处理分析c语言函数调用规范-基于arm 分析 2 SVC指令 2.1 SVC指令位域表示 bit15 - bit12条件码Condition Code用于控制指令的条件执行(EQNE… )。bit11- bit8固定编码0111用于标识这是一个SVC指令。bit7- bit08位立即数imm8svc指令一般用于实现系统调用imm8用来指定系统调用号。 2.2 SVC指令用途简述 ARM中的SVCSupervisor Call指令一般用于实现系统调用 在执行SVC指令时会触发一个SVC异常。在cortex-m架构中异常/中断服务 总是处于总是处于 Privileged(特权) 模式系统调用也是利用这一特性从用户态 进入 内核态 然后根据系统调用号执行特定的系统调用。 关于cortex-m架构和中断的其他信息可以参考我的另外两篇文章: arm cortex-m 架构简述arm异常处理分析 站在硬件的角度SVC指令就是实现了一个软件中断SVC异常和其他异常处理并无不同,硬件并没有做类似 传递系统调用号 这样的操作系统调用号imm8也只是和指令码打包在一起放在代码段在进入内核态之后 获取系统调用号 的操作是由 软件完成 的。 2.3 SVC使用示例 SVC中断触发示例SVC #0x01 ; 执行SVC指令SVC号为0x01 SVC #0x02 ; 执行SVC指令SVC号为0x02SVC中断服务函数示例 我在代码中加了注释如果还是不理解可以看看我 第一节 所推荐的文章。 SVC_Handler:; TST指令解释; 将 LR 与 立即数4 进行 按位与 操作; 结果更新条件标志寄存器(CPSR); 注在发生异常时 LR 存储的是 EXC_RETURN 这条代码主要看在发生异常时使用的是哪个栈MSP或者PSPTST LR, #4; 依据TST LR, #4 的执行结果选择将 MSP或PSP加载到R0中; ITE: If-Then-Else 的缩写用于创建一个条件执行块IT块。; EQ: 条件码表示 Equal相等即当条件码寄存器CPSR的零标志Z被设置时条件为真。; 用C语言描述一下下面三行汇编; if(Z) {; MRS R0, MSP; } else {; MRS R0, PSP; }; 举一反三: ITTE: If-Then-Then-Else ITE EQMRS R0, MSP ; LR4 或者说 EXC_RETURN4 为 0 说明在在执行SVC异常时使用的栈是 MSPMRS R0, PSP ; LR4 或者说 EXC_RETURN4 为 0 说明在在执行SVC异常时使用的栈是 PSP; 获取返回地址 (原理是与发生异常时硬件压栈的顺序相关); 这里获得返回地址的原因是为了定位产生异常前执行的最后一条指令也就是SVC指令LDR R1, [R0, #24] ; 获取SVC指令的低8位也就是系统调用号返回地址的上一条就是SVC指令LDRB R1, [R1, #-2] ; 依据不同的系统调用号执行不同的系统调用CMP R1, #0x01BEQ SVC_01_HandlerCMP R1, #0x02BEQ SVC_02_HandlerSVC_01_Handler:; 处理SVC号为0x01的服务请求SVC_02_Handler:; 处理SVC号为0x02的服务请求3. SVC指令在freertos中的应用 freertos只使用了一次 SVC指令 也可以理解为 freertos中只实现了一个系统调用且只使用一次就是在完成任务创建后发起的第一次任务调度时使用。 3.1 在freertos中SVC中断的触发位置 freertos使用 SVC指令是在 prvPortStartFirstTask 函数中使用目的是开始第一次调度发生在创建任务完成后的第一次调度我会为这段代码加上注释如果还存在看不懂的情况建议先看看我的其他文章或者自行查阅资料学习。在freertos中的调用关系是 vTaskStartScheduler — — xPortStartScheduler — — prvPortStartFirstTaskstatic void prvPortStartFirstTask( void ) {asm volatile (/* * - 0xE000ED08是 SCB 模块 VTOR 寄存器的地址* - 这句汇编目的是把 0xE000ED08(VTOR的地址) 保存到 R0 寄存器* - 执行完这句指令(伪指令)后 R0 寄存器保存的是 0xE000ED08(VTOR的地址)/ ldr r0, 0xE000ED08 \n/ * - 将 0xE000ED08 地址的值(中断向量表的地址) 放到 R0 寄存器中* - 执行完这句指令后 R0 寄存器保存的是 中断向量表的地址/ ldr r0, [r0] \n/ * - 将 中断向量表 第一项的值 放到 R0 寄存器中* - 中断向量表第一个字保存的 是栈顶地址(cortex-m是满减栈栈顶即栈的开始)* - 初始转态下使用的是MSP* - 执行完这句指令后 R0 寄存器保存的是栈顶地址/ ldr r0, [r0] \n/ * - 将 R0的值写入 MSP 中。* - 此时 R0 内存储的是 栈顶地址这一步其实就是把 MSP 中已经存好的内容完全销毁。* - 因为在这段代码中在执行完SVC指令后会由调度器完全接管代码这里的SVC异常永远也不会返回/ msr msp, r0 \n/ * - 将 control 寄存器清零* - control 寄存器有三位分别是* . FPCA - 标志位用于指示在前文中是否使用过 FPU(浮点运算单元)如果使用过dang发生异常硬件压栈的时候会基于此位决定是否保存浮点运算单元上下文又此时使用的SVC异常是不会返回的所以也不必保存保存了也是浪费空间。* 0前文没有使用浮点运算单元产生异常时硬件不用保存浮点上下文* 1前文使用了浮点运算单元产生异常时硬件要保存浮点上下文* . SPSEL - 控制位控制在 Thread mode 下使用 MSP 还是 PSP , Handler mode 一定使用 PSP* 0: MSP* 1: PSP* . nPRIV - 控制位控制在 Thread mode 下是 Privileged(特权级) 还是 Unprivileged(非特权级) , Handler mode 下一定是 Privileged(特权级)* 0: Privileged(特权级)* 1: Unprivileged(非特权级)/ mov r0, #0 \n msr control, r0 \n/ 开启全局中断 / cpsie i \n / 开启fpu / cpsie f \n / 数据同步隔离,确保上面的配置生效 / dsb \n / 指令同步隔离,清空流水线 / isb \n / 触发0号系统调用永远也不会返回 从此处开始代码由freertos全面接管 / svc 0 \n/ svc 不会返回永远也不会运行到这里 / nop \n/ 这是一条伪指令由汇编器处理目的是告诉汇编器把字面量池(常量)放到这个位置, 避免字面量池离使用它的指令太远,超出了寻址范围而导致错误。ARM的ldr指令寻址范围有限/ .ltorg \n); }3.2 freertos中断服务函数解析 我会为这段代码加上注释如果还存在看不懂的情况建议先看看我的其他文章或者自行查阅资料学习。void vPortSVCHandler( void ) {/** 思考在汇编中能用C语言的变量吗为什么C语言中的变量和符号在汇编中代表了什么 答案应考虑如下几点* - C语言和汇编在整个编译过程中是在不同的阶段进行的* - C语言是在 编译阶段 进行处理的C语言会被编译为汇编* - 汇编 是在汇编阶段进行的会生成可重定位的目标文件最终会在 链接阶段 由链接器为所有符号分配 绝对地址。* - 我们在写汇编时会用一些符号代表地址那些符号会由 链接器 最终分配绝对地址。* - c语言中定义的 变量名 函数名 在经过 编译器 编译为 汇编 之后那些变量名 函数名都代表一个地址最后由 链接器 分配绝对地址。* 所以在汇编中使用的C语言符号可以理解为那个变量或者函数的 地址。//** 先看一下最后一句汇编 pxCurrentTCBConst2: .word pxCurrentTCB \n - pxCurrentTCB:在c语言中定义它是一个指针一个描述 任务 的结构体指针* - 在汇编中使用 pxCurrentTCB 这个符号 就相当于使用这个符号的地址在这里可以理解为一个二重指针* - 上述汇编可以这么理解* . 使用 .word 分配一个字的空间用来放 “下一个要执行的任务结构体” 指针的指针(二重指针).* . 将这个二重指针 使用符号 pxCurrentTCBConst2 表示*/asm volatile (/* * - 将要执行任务的结构体 的 指针的指针(二重指针) 保存到 R3寄存器 / ldr r3, pxCurrentTCBConst2 \n/ * - 将要执行任务的结构体 的 指针(一重指针) 保存到 R1寄存器 * - 也可以理解为将 要执行任务的结构体 的第一个元素的地址保存到 R1 寄存器* - 结构体第一个元素 保存将要执行任务的栈指针* - 所以这一句是将 栈指针(SP) 的地址(是栈指针的存放地址而不是栈指针) 放到R1中。/ ldr r1, [r3] \n/ * - 将栈指针 SP 保存到 R0寄存器 / ldr r0, [r1] \n/ * - 将栈指针中的地址按顺序弹栈(要执行的任务的栈)到{r4-r11, r14}* - 这里是要弹栈的内容是 创建任务时 伪造的上下文 (此处先不展开后面降到任务的创建过程会展开来讲)/ ldmia r0!, {r4-r11, r14} \n/ 将 PSP 指向弹完栈后的地址 / msr psp, r0 \n/ 指令同步隔离,清空流水线 / isb \n/ 将basepri寄存器设为0,打开所有中断 / mov r0, #0 \n msr basepri, r0 \n/ * - 异常返回, 这时r14(LR)中存放的是一个 EXC_RETURN 值这个值是在创建任务时伪造的。* - EXC_RETURN 的值决定了任务使用的栈* - 伪造栈的内容会在后面将创建任务时讲*/ bx r14 \n \n .align 4 \npxCurrentTCBConst2: .word pxCurrentTCB \n); }