热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ATF中smc指令详解

1ATF的smc指令调用流程在REE侧调用smc异常之后,会根据中断向量表触发cpu的同步异常sync_exception_aarch6432,然后跳

1 ATF的smc指令调用流程

在REE侧调用smc异常之后,会根据中断向量表触发cpu的同步异常sync_exception_aarch64/32,然后跳转执行到handle_sync_exception->smc_handler64/32中,最后跳转到_RT_SVC_DESCS_START_+RT_SVC_DESC_HANDLE这个工具类中执行具体的操作,最后跳转到el3_exit返回REE侧。
在这里插入图片描述


2.1 REE侧如何调用smc指令

在REE侧调用smc之前,需要对通用寄存器进行赋值传参x0-x8。然后通过 smc #0这一汇编指令进行smc调用

// 在 AArch32中只有个r0-r3才能成功的保存到寄存器中,r4-r6需要被压入到堆栈中保存
func smc/** For AArch32 only r0-r3 will be in the registers;* rest r4-r6 will be pushed on to the stack. So here, we'll* have to load them from the stack to registers r4-r6 explicitly.* Clobbers: r4-r6*/ldm sp, {r4, r5, r6}smc #0
endfunc smc
//在 AArch64中则不需要
func smcsmc #0
endfunc smc

2.2 smc同步异常

在运行时el3采用的异常向量表是runtime_exceptions

(1)首先看两个宏定义


.macro vector_base label .section .vectors, "ax" //指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行.align 11, 0 //地址方式对齐11 其余字节用0填充\label:.endm.macro vector_entry label //label为标号以冒号结尾.section .vectors, "ax"//指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行.align 7, 0 //地址方式对齐7\label:.endm.macro check_vector_size since.if (. - \since) > (32 * 4) //这个.应该是当前位置 - 段的开头地址 如果大于 32条指令.error "Vector exceeds 32 instructions" //向量超过32条指令.endif.endm
.macro no_ret _func:req, skip_nop=0 //其实意思就是bl _funcbl \_func

//这里省略了其他的异常
vector_base runtime_exceptions //定义 .vectors
vector_entry sync_exception_aarch64handle_sync_exceptioncheck_vector_size sync_exception_aarch64
vector_entry sync_exception_aarch32handle_sync_exceptioncheck_vector_size sync_exception_aarch32

这段代码等价于

.section .vectors, "ax" //指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行
.align 11, 0 //地址方式对齐11 其余字节用0填充
runtime_exceptions:.section .vectors, "ax"//指定代码段必须存放在.vectors段里, “ax”表示该段可执行并且可‘a’读和可‘x’执行.align 7, 0 //地址方式对齐7sync_exception_aarch64:handle_sync_exception.if (. - serror_aarch64) > (32 * 4) //这个.应该是当前位置 - 段的开头地址 如果大于 32条指令.error "Vector exceeds 32 instructions" //向量超过32条指令.endifsync_exception_aarch32handle_sync_exception.if (. - serror_aarch64) > (32 * 4) //这个.应该是当前位置 - 段的开头地址 如果大于 32条指令.error "Vector exceeds 32 instructions" //向量超过32条指令.endif

2.3 handle_sync_exception

这个 handle_sync_exception是一个宏定义

.macro handle_sync_exception/* Enable the SError interrupt */msr daifclr, #DAIF_ABT_BIT // #define DAIF_ABT_BIT (U(1) <<2) 将其传输到 daifclr寄存器中str x30, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_LR] // CTX_GPREGS_OFFSET &#61; 0 &#43; #define CTX_GPREG_LR U(0xf0) &#43; sp 的值作为地址 存入 x30的值#if ENABLE_RUNTIME_INSTRUMENTATION/** Read the timestamp value and store it in per-cpu data. The value* will be extracted from per-cpu data by the C level SMC handler and* saved to the PMF timestamp region.*/mrs x30, cntpct_el0 //将cntpct_el0 存入x30str x29, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X29] //保存x29的值到堆栈mrs x29, tpidr_el3 //将tpidr_el3存入x29str x30, [x29, #CPU_DATA_PMF_TS0_OFFSET] //将 cntpct_el0 指写入到tpidr_el3中ldr x29, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X29] //获取x29的值
#endifmrs x30, esr_el3 //将esr_el3存入x30ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH //#define ESR_EC_SHIFT U(26) #define ESR_EC_LENGTH U(6) 相当于 保留 x30的bit[31-26]并将这几位提到bit[6-0]/* Handle SMC exceptions separately from other synchronous exceptions */cmp x30, #EC_AARCH32_SMC //#define EC_AARCH32_SMC U(0x13) 对比b.eq smc_handler32cmp x30, #EC_AARCH64_SMC //#define EC_AARCH64_SMC U(0x17) 对比b.eq smc_handler64/* Other kinds of synchronous exceptions are not handled */no_ret report_unhandled_exception //都不是的话跳转到这里report_unhandled_exception.endm

2.4 smc_handler32/64 以及report_unhandled_exception的执行

在2.3中有三种跳转选项其中smc_handler32/64能够正确触发异常&#xff0c;report_unhandled_exception则是错误的流程
这个函数主要是存储x4-x18寄存器的值&#xff0c;并通过x0也就是smc_id判断该指令是smc_handler32还是smc_handler64&#xff0c;然后进入到rt_svc_descs这个工具结构体中&#xff0c;执行具体的指令。

//report_unhandled_exception 这里不详细研究
func report_unhandled_exceptionprepare_crash_buf_save_x0_x1 //保存adr x0, excpt_msgmov sp, x0/* This call will not return */b do_crash_reporting
endfunc report_unhandled_exception
//smc_handler32/64
smc_handler32:tbnz x0, #FUNCID_CC_SHIFT, smc_prohibited // #define FUNCID_CC_SHIFT U(30) 意思是 x0的第30位若不为0 跳转到smc_prohibited这个函数&#xff0c;x0一般用来做smc_id 第三十位为1时代表时AArch32指令stp x8, x9, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X8]stp x10, x11, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X10]stp x12, x13, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X12]stp x14, x15, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X14]stp x16, x17, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X16] //保存现场&#xff0c;将寄存器的值压入堆栈
smc_handler64: //不知道为什么AArch64指令不保存上述寄存器的值stp x4, x5, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X4] stp x6, x7, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X6] //保存现场&#xff0c;将寄存器的值压入堆栈/* Save rest of the gpregs and sp_el0*/save_x18_to_x29_sp_el0 //保存x18-x29到sp栈中后面有代码mov x5, xzr //xzr存入x5mov x6, sp //将sp指针存入x6/* Get the unique owning entity number */ // #define FUNCID_OEN_WIDTH U(6) #define FUNCID_OEN_SHIFT U(24) #define FUNCID_OEN_SHIFT U(24) #define FUNCID_TYPE_WIDTH U(1)ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH //x0为smd_id 指令的bit[24-30]为OEN 存入到x16中ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH //x0为smd_id x15 存储着 0或者1 代表是AArch32指令还是AArch64指令orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH //将x15左移6为存入到x16中adr x11, (__RT_SVC_DESCS_START__ &#43; RT_SVC_DESC_HANDLE) //读取KEEP(*(rt_svc_descs))指令的地址adr x14, rt_svc_descs_indices //读取rt_svc_descs_indices这个描述符索引ldrb w15, [x14, x16] //将地址为x14&#43;x16的数据读入到w15&#xff0c;并清除w15的高24位 这里没明白啥意思ldr x12, [x6, #CTX_EL3STATE_OFFSET &#43; CTX_RUNTIME_SP] //x6之前是指针的地址 在加上el3环境的偏移地址和其sp的所在地址&#xff0c;可以将CTX_RUNTIME_SP的sp地址给x12tbnz w15, 7, smc_unknown //如果 w15的第7位不是0&#xff0c;则进入到smc_unknown msr spsel, #0 //切换到SP_EL0 spsel &#61; 0//* Get the descriptor using the index x11 &#61; (base &#43; off), x15 &#61; index handler &#61; (base &#43; off) &#43; (index <lsl w10, w15, #RT_SVC_SIZE_LOG2 //#define RT_SVC_SIZE_LOG2 5 w15左移5位给w10ldr x15, [x11, w10, uxtw]//x11是KEEP(*(rt_svc_descs))指令的地址 &#43; w10//保存现场mrs x16, spsr_el3 //x16 &#61; spsr_el3mrs x17, elr_el3 //x17 &#61; elr_el3 mrs x18, scr_el3 //x18 &#61; scr_el3 stp x16, x17, [x6, #CTX_EL3STATE_OFFSET &#43; CTX_SPSR_EL3] //x6为sp指针地址将数据存入对应的堆栈位置str x18, [x6, #CTX_EL3STATE_OFFSET &#43; CTX_SCR_EL3] //取这个安全寄存器的地址bfi x7, x18, #0, #1 //这个跟SCR_EL3的安位有关mov sp, x12 //没看懂着啥操作blr x15 //跳转执行rt_svc_descs结构体的成员地址b el3_exit //退出安全环境

//这里是__RT_SVC_DESCS_START__ 和__RT_SVC_DESCS_END__ 的作用 ld文件里的应该就是存储指针的意思吧__RT_SVC_DESCS_START__ &#61; .;KEEP(*(rt_svc_descs))//__RT_SVC_DESCS_END__ &#61; .;

.macro save_x18_to_x29_sp_el0
stp x18, x19, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X18]
stp x20, x21, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X20]
stp x22, x23, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X22]
stp x24, x25, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X24]
stp x26, x27, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X26]
stp x28, x29, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X28]
mrs x18, sp_el0
str x18, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_SP_EL0]
.endm

//smc_unknown:
smc_unknown:/*恢复x4-x18寄存器&#xff0c;返回到REE侧*/mov w0, #SMC_UNKb restore_gp_registers_callee_eret

2.5 std_svc_smc_handler &#xff08;这只是调用psic的smc_handler&#xff09;

在2.4 中最后跳转到x15寄存器所存储的地址

ldr x15 &#61; [x11, w10, uxtw] &#xff1b;//x11 是(__RT_SVC_DESCS_START__ &#43; RT_SVC_DESC_HANDLE) //RT_SVC_DESC_HANDLE &#61; 24 &#xff08;也就是sizeof(rt_svc_desc_t) &#61; 24&#xff09;
adr x11, (__RT_SVC_DESCS_START__ &#43; RT_SVC_DESC_HANDLE) //读取KEEP(*(rt_svc_descs))指令的地址
lsl w10, w15, #RT_SVC_SIZE_LOG2 //#define RT_SVC_SIZE_LOG2 5 w15左移5位给w10
ldrb w15, [x14, x16] //将地址为x14&#43;x16的数据读入到w15&#xff0c;并清除w15的高24位 这里没明白啥意思
adr x14, rt_svc_descs_indices //读取rt_svc_descs_indices这个描述符索引
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH //x0为smd_id 指令的bit[24-30]为OEN 存入到x16中
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH //将x15左移6为存入到x16中
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH //x0为smd_id x15 存储着 0或者1 代表是AArch32指令还是AArch64指令

应该就是跳入了__RT_SVC_DESCS_这个的对应函数地址

uint8_t rt_svc_descs_indices[MAX_RT_SVCS]; //MAX_RT_SVCS &#61; 128 代表最多有128个服务 它存储着服务id
static rt_svc_desc_t *rt_svc_descs;

其中 rt_svc_desc_t 的成员及初始化参数为

typedef struct rt_svc_desc {uint8_t start_oen; //该服务的起始编号uint8_t end_oen; //该服务的末尾编号 end_oen 和 start_oen可以相同 只是在在程序上能看出这一类的服务结尾是什么编号uint8_t call_type; //异常种类 分为SMC_TYPE_YIELD&#xff0c;SMC_TYPE_STD&#xff0c;SMC_TYPE_FASTconst char *name; //异常名字rt_svc_init_t init; //异常函数初始化地址rt_svc_handle_t handle; //异常处理函数句柄
} rt_svc_desc_t;

在其他博主的文章中搜索到是跳入了std_svc_smc_handler这个函数
首先通过上述函数了解到 只有初始的x0-x3的值没有压入到中断也就是只能传输过去smc_fid、x1、x2、x3这四个值

uintptr_t std_svc_smc_handler(uint32_t smc_fid,u_register_t x1,u_register_t x2,u_register_t x3,u_register_t x4,void *COOKIE,void *handle, //这里的handle正好对应了之前REE侧sp堆栈的值u_register_t flags)
{/** Dispatch PSCI calls to PSCI SMC handler and return its return* value*/
#ifdef ENABLE_QOS_SETTING //这里不用管unsigned long qos_ret &#61; 0;
#endifif (is_psci_fid(smc_fid)) { //根据smc_fid的值判断是否是psci的smv指令 smc_fid & 0xffe0 &#61;&#61; 0 所以判断 psci的smc_fid 在 0x0000 - 0x001fuint64_t ret;#if ENABLE_RUNTIME_INSTRUMENTATION //应该是时间戳上报&#xff0c;没啥用&#xff0c;没有被使能PMF_WRITE_TIMESTAMP(rt_instr_svc,RT_INSTR_ENTER_PSCI,PMF_CACHE_MAINT,get_cpu_data(cpu_data_pmf_ts[CPU_DATA_PMF_TS0_IDX]));
#endifret &#61; psci_smc_handler(smc_fid, x1, x2, x3, x4,COOKIE, handle, flags);//这里直接调用#if ENABLE_RUNTIME_INSTRUMENTATION //同理时间抽&#xff0c;看这个handle的运行时间应该是PMF_CAPTURE_TIMESTAMP(rt_instr_svc,RT_INSTR_EXIT_PSCI,PMF_NO_CACHE_MAINT);
#endifSMC_RET1(handle, ret); //将返回值存入handle的r0中}switch (smc_fid) { //几种其他的smc_fid指令case ARM_STD_SVC_CALL_COUNT:SMC_RET1(handle, PSCI_NUM_CALLS); //这个就是将PSCI_NUM_CALLS的数据存储到REE侧的r0中case ARM_STD_SVC_UID:/* Return UID to the caller */SMC_UUID_RET(handle, arm_svc_uid);case ARM_STD_SVC_VERSION:/* Return the version of current implementation */SMC_RET2(handle, STD_SVC_VERSION_MAJOR, STD_SVC_VERSION_MINOR);#ifdef ENABLE_QOS_SETTING //没用到case SPRD_QOS_READ:qos_ret&#61; qos_register_read(x1);SMC_RET1(handle, qos_ret);case SPRD_QOS_WRITE:qos_ret &#61; qos_register_write(x1, x2);SMC_RET1(handle, qos_ret);
#endifdefault: //都不是返回 SMC_UNK 0xffffffffWARN("Unimplemented Standard Service Call: 0x%x \n", smc_fid);SMC_RET1(handle, SMC_UNK);}
}

从这个函数中可以看出smc现在只挂载了psci相关的指令其他的都没有涉及


2.6 el3_exit

执行完std_svc_smc_handler 之后又回到原先的汇编函数接下来执行el3_exit

func el3_exitmov x17, sp //将sp的值给x17msr spsel, #1 //返回非安全将spsel置1str x17, [sp, #CTX_EL3STATE_OFFSET &#43; CTX_RUNTIME_SP] //把之前REE侧的sp地址给x17ldr x18, [sp, #CTX_EL3STATE_OFFSET &#43; CTX_SCR_EL3] //读取[sp, #CTX_EL3STATE_OFFSET &#43; CTX_SCR_EL3]到x18ldp x16, x17, [sp, #CTX_EL3STATE_OFFSET &#43; CTX_SPSR_EL3] //读取[sp, #CTX_EL3STATE_OFFSET &#43; CTX_SPSR_EL3] 应该是elr_e13,spsr_el3 的值给 x16, x17msr scr_el3, x18 //还原REE侧的scr_el3msr spsr_el3, x16 //还原REE侧的spsr_el3msr elr_el3, x17 //还原REE侧的elr_el3b restore_gp_registers_eret //还原REE侧的x0-x30
endfunc el3_exit

func restore_gp_registers_eretldp x0, x1, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X0]ldp x2, x3, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X2]b restore_gp_registers_callee_eret
endfunc restore_gp_registers_eret
func restore_gp_registers_callee_eretldp x4, x5, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X4]ldp x6, x7, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X6]ldp x8, x9, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X8]ldp x10, x11, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X10]ldp x12, x13, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X12]ldp x14, x15, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X14]ldp x18, x19, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X18]ldp x20, x21, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X20]ldp x22, x23, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X22]ldp x24, x25, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X24]ldp x26, x27, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X26]ldp x28, x29, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X28]ldp x30, x17, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_LR] //这里将链接寄存器的值给了x17msr sp_el0, x17 //sp_el0 &#61; x17 也就是之前REE侧的pc值ldp x16, x17, [sp, #CTX_GPREGS_OFFSET &#43; CTX_GPREG_X16] //还原REE侧的x16&#xff0c;17eret
endfunc restore_gp_registers_callee_eret
//死循环&#xff1f;
func ereteret //这里的eret 是 arm v8中的 eret指令后面讲
endfunc eret

这里是armv8手册中用于描述eret的语句
在这里插入图片描述
使用ELR和SPSR返回当前异常级别。在执行的时候&#xff0c;PE从SPSR恢复PSTATE&#xff0c;和分支到ELR里的地址。
PE检查SPSR的当前异常级别是否存在非法返回事件&#xff0c;
如果在EL0中执行&#xff0c;ERET将导致未定义指令异常。
大概意思就是回到REE侧了


3 smc指令如何书写

从之前std_svc_smc_handler函数中的handle对应REE侧sp来说&#xff0c;程序根据SMC_RET1、SMC_RET2等参数可以将之前堆栈的中值改变&#xff0c;从而影响REE侧返回的x0-x12&#xff0c;以起堆栈中所存储的其他寄存器。
所以 REE侧触发smc #0指令时&#xff0c;x0-x3的值是有用的。x0代表smc_id x1-x3代表其他需要传输的形参。返回REE侧时能都返回的数据可通过handle存储到x0-x12中。


4 bl31启动的runtime_svc_init函数

在bl31中会执行runtime_svc_init函数,该函数会调用注册到EL3中所有服务的初始化函数,其中有一个服务项就是TEE服务,该服务项的初始化函数会将TEE OS的初始化函数赋值给bl32_init变量,当所有服务项执行完初始化后,在bl31中会调用b32_init执行的函数来跳转到TEE OS中并开始执行TEE OS的启动。

void bl31_main(void)
{NOTICE("BL31: %s\n", version_string);NOTICE("BL31: %s\n", build_message);bl31_platform_setup(); //通用和安全时钟初始化&#xff0c;其他芯片相关功能初始化bl31_lib_init(); //空函数INFO("BL31: Initializing runtime services\n");runtime_svc_init(); //重点 下面展开分析if (bl32_init) { INFO("BL31: Initializing BL32\n");(*bl32_init)();}bl31_prepare_next_image_entry(); //加载下一阶段的入口地址console_flush(); //控制台刷新bl31_plat_runtime_setup(); //空函数
}

//注册smc指令相关的服务
void runtime_svc_init(void)
{int rc &#61; 0;unsigned int index, start_idx, end_idx;/* Assert the number of descriptors detected are less than maximum indices */assert((RT_SVC_DESCS_END >&#61; RT_SVC_DESCS_START) &&(RT_SVC_DECS_NUM < MAX_RT_SVCS)); //这句话表明 RT_SVC_DECS_NUM时当前加载的服务数量&#xff0c;define RT_SVC_DECS_NUM ((RT_SVC_DESCS_END - RT_SVC_DESCS_START)\/ sizeof(rt_svc_desc_t))if (RT_SVC_DECS_NUM &#61;&#61; 0) //如果没有服务要注册return;memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices)); //初始化rt_svc_descs_indicesrt_svc_descs &#61; (rt_svc_desc_t *) RT_SVC_DESCS_START; //建立一个注册表结构体for (index &#61; 0; index < RT_SVC_DECS_NUM; index&#43;&#43;) {rt_svc_desc_t *service &#61; &rt_svc_descs[index];rc &#61; validate_rt_svc_desc(service); //判断每一个服务的各项参数是否正确if (rc) {ERROR("Invalid runtime service descriptor %p\n",(void *) service);panic(); //不正确}if (service->init) { //该服务是否需要初始化rc &#61; service->init(); //进行初始化if (rc) { //初始化是否成功ERROR("Error initializing runtime service %s\n",service->name);continue;}}start_idx &#61; get_unique_oen(rt_svc_descs[index].start_oen,service->call_type); //八位的id号assert(start_idx < MAX_RT_SVCS);end_idx &#61; get_unique_oen(rt_svc_descs[index].end_oen,service->call_type); //八位的id号assert(end_idx < MAX_RT_SVCS);for (; start_idx <&#61; end_idx; start_idx&#43;&#43;)rt_svc_descs_indices[start_idx] &#61; index;//证明可以根据rt_svc_descs_indices[?]的值找到其对应的rt_svc_descs[index]中index值}
}

具体注册宏指令

#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \static const rt_svc_desc_t __svc_desc_ ## _name \__section("rt_svc_descs") __used &#61; { \.start_oen &#61; _start, \.end_oen &#61; _end, \.call_type &#61; _type, \.name &#61; #_name, \.init &#61; _setup, \.handle &#61; _smch }
//其中__setion("rt_svc_descs")的意思就时注册到rt_svc_descs段中

然后添加服务时只需要调用这个宏指令就可以了

DECLARE_RT_SVC(std_svc,OEN_STD_START,OEN_STD_END,SMC_TYPE_FAST,std_svc_setup,std_svc_smc_handler
);
这个的意思就是注册
static const rt_svc_desc_t __svc_desc_std_svc服务。其服务id为SMC_TYPE_FAST << 6 &#43; OEN_STD_START,结束服务的id为SMC_TYPE_FAST << 6 &#43; OEN_STD_END

推荐阅读
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 在2022年,随着信息化时代的发展,手机市场上出现了越来越多的机型选择。如何挑选一部适合自己的手机成为了许多人的困扰。本文提供了一些配置及性价比较高的手机推荐,并总结了选择手机时需要考虑的因素,如性能、屏幕素质、拍照水平、充电续航、颜值质感等。不同人的需求不同,因此在预算范围内找到适合自己的手机才是最重要的。通过本文的指南和技巧,希望能够帮助读者节省选购手机的时间。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
author-avatar
豹女无爱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有