首页 文章 精选 留言 我的

精选列表

搜索[harmonyos开发],共10000篇文章
优秀的个人博客,低调大师

鸿蒙内核源码分析(信号消费篇) | 谁让CPU连换四次栈运行 | 百篇博客分析HarmonyOS源码 | v49.04

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony > 信号消费 本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下: v48.xx (信号生产篇) | 信号它老人家已经五十多岁了 < csdn | 51cto | harmony > v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > 本篇有相当的难度,涉及用户栈和内核栈的两次切换,寄存器改值,将围绕下图来说明. 解读 从图中可以看出信号消费让CPU四次换栈运行. 为本篇理解方便,把图做简化标签说明: user:用户空间 kernel:内核空间 source(...):源函数 sighandle(...):信号处理函数, syscall(...):系统调用,参数为系统调用号,如sigreturn,N(表任意) user.source():表示在用户空间运行的源函数 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈. 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复. 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题. 顺着这个思路可以推到以下几点,实际也是这么做的: kernel.syscall(N) 中必须要再次保存user.source()的上下文sig_switch_context,为何已经保存了一次还要再保存一次? 因为第一次是保存在内核栈中,而内核栈这部分数据会因回到用户态user.sighandle()运行而被恢复现场出栈了.保存现场/恢复现场是成双出队的好基友,注意有些文章说会把整个内核栈清空,这是不对的. 第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的.两次保存的都是user.source()运行时现场信息,再回顾下相关的结构体.关键是sig_switch_context typedef struct { // ... sig_cb sig;//信号控制块,用于异步通信 } LosTaskCB; typedef struct {//信号控制块(描述符) sigset_t sigFlag; //不屏蔽的信号集 sigset_t sigPendFlag; //信号阻塞标签集,记录那些信号来过,任务依然阻塞的集合.即:这些信号不能唤醒任务 sigset_t sigprocmask; /* Signals that are blocked */ //任务屏蔽了哪些信号 sq_queue_t sigactionq; //信号捕捉队列 LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 请查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解 sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待哪些信号的到来 siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息 sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换 } sig_cb; 还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数. 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:__NR_sigreturn,这也是个系统调用.回来后还原sig_switch_context,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行. 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数 OsArmA32SyscallHandle,OsSaveSignalContext,OsRestorSignalContext本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context. sig_switch_context //任务中断上下文 #define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//信号切换上下文 TASK_IRQ_CONTEXT unsigned int R7; //存放系统调用的ID unsigned int count; //记录是否保存了信号上下文 } sig_switch_context; 保存user.source()现场的结构体,USP,ULR代表用户栈指针和返回地址. CPSR寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看 v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > 谈论的用户态(usr普通用户)和内核态(sys超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行. 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存. R7是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数. count记录是否保存了信号上下文 OsArmA32SyscallHandle 系统调用总入口 /* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号 regs:参数就是所有寄存器 注意:本函数在用户态和内核态下都可能被调用到 //MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 ******************************************************************/ LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值 case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序. /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程. 关于系统调用可查看 v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > 本篇不详细说系统调用过程,只说跟信号相关的部分. OsArmA32SyscallHandle总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext,第二次进入OsArmA32SyscallHandle后再执行OsRestorSignalContext. 看OsSaveSignalContext,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. 在函数的开头,碰到系统调用号__NR_sigreturn,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } OsSaveSignalContext 保存信号上下文 有了上面的铺垫,就不难理解这个函数的作用. /********************************************** 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息 改写PC寄存器的值 **********************************************/ void OsSaveSignalContext(unsigned int *sp) { UINTPTR sigHandler; UINT32 intSave; LosTaskCB *task = NULL; LosProcessCB *process = NULL; sig_cb *sigcb = NULL; unsigned long cpsr; OS_RETURN_IF_VOID(sp == NULL); cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值 OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇 task = OsCurrTaskGet(); process = OsCurrProcessGet(); sigcb = &task->sig;//获取任务的信号控制块 //1.未保存任务上下文任务 //2.任何的信号标签集不为空或者进程有信号要处理 if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) { sigHandler = OsGetSigHandler();//获取信号处理函数 if (sigHandler == 0) {//信号没有注册 sigcb->sigFlag = 0; process->sigShare = 0; SCHEDULER_UNLOCK(intSave); PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID); return; } /* One pthread do the share signal */ sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集 unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet(process, signo);//设置进程退出信号 sigcb->context.CPSR = cpsr; //保存状态寄存器 sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值 sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈 sigcb->context.ULR = sp[REG_LR];//用户栈返回地址 sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值 sigcb->context.R1 = sp[REG_R1]; sigcb->context.R2 = sp[REG_R2]; sigcb->context.R3 = sp[REG_R3]; sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613 sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */ sigcb->sigFlag ^= 1ULL << (signo - 1); sigcb->context.count++; //代表已保存 } SCHEDULER_UNLOCK(intSave); } 解读 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号signo,注意可不是系统调用号,有区别的.信号编号长这样. #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 系统调用号长这样,是不是看到一些很熟悉的函数. #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在_osExceptSwiHdl中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 OsRestorSignalContext 恢复信号上下文 /**************************************************** 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用. 为什么要恢复呢? 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容, 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场 所以函数的功能就变成了还原寄存器的值 ****************************************************/ void OsRestorSignalContext(unsigned int *sp) { LosTaskCB *task = NULL; /* Do not adjust this statement */ LosProcessCB *process = NULL; sig_cb *sigcb = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); task = OsCurrTaskGet(); sigcb = &task->sig;//获取当前任务信号控制块 if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复 SCHEDULER_UNLOCK(intSave); PRINT_ERR("sig error count : %d\n", sigcb->context.count); return; } process = OsCurrProcessGet();//获取当前进程 sp[REG_PC] = sigcb->context.PC;//指令寄存器 OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程序状态寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 sp[REG_R0] = sigcb->context.R0; sp[REG_R1] = sigcb->context.R1; sp[REG_R2] = sigcb->context.R2; sp[REG_R3] = sigcb->context.R3; sp[REG_R7] = sigcb->context.R7; sp[REG_R12] = sigcb->context.R12; sigcb->context.count--; //信号上下文的数量回到减少 process->sigShare = 0; //回到用户态,信号共享清0 OsProcessExitCodeSignalClear(process);//清空进程退出码 SCHEDULER_UNLOCK(intSave); } 解读 在信号处理函数完成之后,内核会触发一个__NR_sigreturn的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle. 恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句. sp[REG_PC] = sigcb->context.PC;//指令寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.USP和ULR指向的是用户栈的位置.一旦PC,USP,ULR从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. 真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着OsArmA32SyscallHandle @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行 @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解 SUB SP, SP, #(4 * 16) @先申请16个栈空间单元用于处理本次软中断 STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放R0 .. R12 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 => TaskContext.regPSR ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LR和SP SUB SP, SP, #4 @ => TaskContext.resved PUSH_FPU_REGS R1 @保存中断模式(用户模式) @保存任务上下文(TaskContext) 结束 MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/ CPSID I @执行后续指令前必须先关中断 @恢复任务上下文(TaskContext) 开始 POP_FPU_REGS R1 @弹出FPU值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 @恢复任务上下文(TaskContext) 结束 具体也可看这两篇: v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v51.xx (ELF反汇编篇) | 程序的入口函数并不是main < csdn | 51cto | osc > v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc > v49.xx (信号消费篇) | 谁让CPU连换四次栈运行 < csdn | 51cto | osc > v48.xx (信号生产篇) | 信号它老人家已经五十多岁了 < csdn | 51cto | osc > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 < csdn | 51cto | osc > v44.xx (中断管理篇) | 硬中断的实现像观察者模式 < csdn | 51cto | osc > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc > v42.xx (中断切换篇) | 中断切换又在切换什么 < csdn | 51cto | osc > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc > v40.xx (汇编汇总篇) | 所有的汇编代码都在呢 < csdn | 51cto | osc > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc > v38.xx (寄存器篇) | 看完再也不对寄存器怕怕 < csdn | 51cto | osc > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆 < csdn | 51cto | osc > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航 < csdn | 51cto | osc > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 < csdn | 51cto | osc > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc > v31.xx (定时器篇) | 内核最高优先级任务是谁 < csdn | 51cto | osc > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc > v29.xx (信号量篇) | 信号量解决任务的什么问题 < csdn | 51cto | osc > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc > v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | 51cto | osc > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | 51cto | osc > v25.xx (并发并行篇) | 两个字永远记住二者区别 < csdn | 51cto | osc > v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | 51cto | osc > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数 < csdn | 51cto | osc > v22.xx (汇编基础篇) | cpu在哪里打卡上班 < csdn | 51cto | osc > v21.xx (线程概念篇) | 是谁在不断的折腾cpu < csdn | 51cto | osc > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个 < csdn | 51cto | osc > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc > v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | 51cto | osc > v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | 51cto | osc > v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | 51cto | osc > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础 < csdn | 51cto | osc > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | 51cto | osc > v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | 51cto | osc > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处 < csdn | 51cto | osc > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc > v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | 51cto | osc > v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | 51cto | osc > v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | 51cto | osc > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc > v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc > v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc > 进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github 鸿蒙内核源码分析系列篇.原创不易,欢迎转载,但请注明出处. 热爱是所有的理由和答案 - turing 不经过深度思考的人生不值得一过 - turing

优秀的个人博客,低调大师

| 百篇博客分析HarmonyOS源码 | v48.01

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 信号 关于信号篇,本来只想写一篇,但发现把它想简单了,内容不多,难度极大.看完鸿蒙这部分源码后理解了为何<<深入理解linux内核>>要单独为它开一章. 1.是信号相关的结构体多,而且还容易搞混.所以看本篇要注意结构体的名字和作用. 2.是系统调用太多了,涉及面广,信号的来源分硬件和软件.相当于软中断和硬中断,信号的处理就会涉及汇编代码,运行过程需要在用户空间和内核空间来回的捯饬,频繁的切换任务上下文和栈空间. 本篇试图将任务和信号的关系比成男孩和女孩互追的过程.希望能把问题简化说明白,爱我的人和我爱的人这是永恒的话题.确实难搞. 所以分成信号分发和信号处理两篇: 本篇是信号分发,说概念,说结构体,理清信号和进程,任务的关系.最后说发送过程. 另外一篇专门说信号处理过程,围绕信号切换上下文sig_switch_context来说.是会很有意思的一篇. 信号,为系统提供了一种处理异步事件的方法,一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。一般来说,我们只需要在进程中设置信号相应的处理函数,当有信号到达的时候,由内核空间异步回调用户空间的信号处理函数即可。 信号分类 每个信号都有一个名字和编号,这些名字都以SIG开头,例如SIGQUIT、SIGCHLD等等。 信号定义在signal.h头文件中,信号名都定义为正整数。 具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。不过kill对于信号0有特殊的应用。啥用呢? 可用来查询进程是否还在. 敲下 kill 0 pid 就知道了. 信号分为两大类:可靠信号与不可靠信号,前32种信号为不可靠信号,后32种为可靠信号。 不可靠信号: 也称为非实时信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31; 可靠信号: 也称为实时信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64 #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 #define SIGUSR1 10 //用户自定义信号1 #define SIGSEGV 11 //无效的内存引用, 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置 #define SIGUSR2 12 //用户自定义信号2 #define SIGPIPE 13 //向某个非读管道中写入数据 #define SIGALRM 14 //由alarm(2)发出的信号,默认行为为进程终止 #define SIGTERM 15 //终止信号 #define SIGSTKFLT 16 //栈溢出 #define SIGCHLD 17 //子进程结束信号 #define SIGCONT 18 //进程继续(曾被停止的进程) #define SIGSTOP 19 //终止进程 | 不能被忽略、处理和阻塞 #define SIGTSTP 20 //控制终端(tty)上 按下停止键 #define SIGTTIN 21 //进程停止,后台进程企图从控制终端读 #define SIGTTOU 22 //进程停止,后台进程企图从控制终端写 #define SIGURG 23 //I/O有紧急数据到达当前进程 #define SIGXCPU 24 //进程的CPU时间片到期 #define SIGXFSZ 25 //文件大小的超出上限 #define SIGVTALRM 26 //虚拟时钟超时 #define SIGPROF 27 //profile时钟超时 #define SIGWINCH 28 //窗口大小改变 #define SIGIO 29 //I/O相关 #define SIGPOLL 29 // #define SIGPWR 30 //电源故障,关机 #define SIGSYS 31 //系统调用中参数错,如系统调用号非法 #define SIGUNUSED SIGSYS//不使用 #define _NSIG 65 信号产生 信号来源分为硬件类和软件类: 硬件类 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号; 硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程; 软件类 通过系统调用,发送signal信号:kill(),raise(),sigqueue(),alarm(),setitimer(),abort() kill 命令就是一个发送信号的工具,用于向进程或进程组发送信号.例如: kill 9 PID (SIGKILL)来杀死PID 进程. sigqueue():只能向一个进程发送信号,不能像进程组发送信号;主要针对实时信号提出,与sigaction()组合使用,当然也支持非实时信号的发送; alarm():用于调用进程指定时间后发出SIGALARM信号; setitimer():设置定时器,计时达到后给进程发送SIGALRM信号,功能比alarm更强大; abort():向进程发送SIGABORT信号,默认进程会异常退出。 raise():用于向进程自身发送信号; 信号与进程相关结构体 typedef struct ProcessCB {//PCB中关于信号的信息 UINTPTR sigHandler; /**< signal handler */ //捕捉信号后的处理函数 sigset_t sigShare; /**< signal share bit */ //信号共享位 }LosProcessCB; typedef unsigned _Int64 sigset_t; //一个64位的变量,每个信号代表一位. struct sigaction {//信号处理机制结构体 union { void (*sa_handler)(int); //信号处理函数——普通版 void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理函数——高级版 } __sa_handler; sigset_t sa_mask;//指定信号处理程序执行过程中需要阻塞的信号; int sa_flags; //标示位 // SA_RESTART:使被信号打断的syscall重新发起。 // SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 // SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵 尸进程。 // SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 // SA_RESETHAND:信号处理之后重新设置为默认的处理方式。 // SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。 void (*sa_restorer)(void); }; typedef struct sigaction sigaction_t; 解读 每个信号都对应一个位. 信号从1开始编号 [1 ~ 64] 对应 sigShare的[0 ~ 63]位,所以中间会差一个.记住这点,后续代码会提到. sigHandler信号处理函数的设置过程,由系统调用sigaction(用户空间) -> OsSigAction(内核空间)得到 #include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); int OsSigAction(int sig, const sigaction_t *act, sigaction_t *oact) { UINTPTR addr; sigaction_t action; if (!GOOD_SIGNO(sig) || sig < 1 || act == NULL) { return -EINVAL; } //将数据从用户空间拷贝到内核空间 if (LOS_ArchCopyFromUser(&action, act, sizeof(sigaction_t)) != LOS_OK) { return -EFAULT; } if (sig == SIGSYS) {//系统调用中参数错,如系统调用号非法 addr = OsGetSigHandler();//获取进程信号处理函数 if (addr == 0) {//进程没有设置信号处理函数时 OsSetSigHandler((unsigned long)(UINTPTR)action.sa_handler);//设置进程信号处理函数——普通版 return LOS_OK; } return -EINVAL; } return LOS_OK; } sigaction()第一个参数是要捕捉的信号; 第二个参数与sigaction函数同名的结构体, 结构体内定义了信号处理方法;第三个为输出参数,将信号的当前的sigaction结构地址输出.但鸿蒙显然没有认真对待第三个参数. sigaction()可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体. 鸿蒙目前支持信号处理函数——普通版,sa_handler表示自定义函数捕捉信号,或者说用户进程向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,具体看注释 sa_sigaction是实时信号的处理函数,union二选一.鸿蒙暂时不支持这种方式. 信号与任务相关结构体 typedef struct {//TCB中关于信号的信息 sig_cb sig; //信号控制块,用于异步通信,类似于 linux singal模块 } LosTaskCB; typedef struct {//信号控制块(描述符) sigset_t sigFlag; //不屏蔽的信号标签集 sigset_t sigPendFlag; //信号阻塞标签集,记录因哪些信号被阻塞 sigset_t sigprocmask; /* Signals that are blocked */ //进程屏蔽了哪些信号 sq_queue_t sigactionq; //信号捕捉队列 LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 可查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解 sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待阻塞信号 siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息 sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换 } sig_cb; 解读 系列篇已多次说过,进程只是管理资源的容器,真正让cpu干活的是任务task,所以发给进程的信号最终还是需要分发给具体任务来处理.所以能想到的是关于任务部分会更复杂. context信号处理很复杂的原因在于信号的发起在用户空间,发送需要系统调用,而处理信号的函数又是用户空间提供的, 所以需要反复的切换任务上下文.而且还有硬中断的问题,比如 ctrl + c ,需要从硬中断中回调用户空间的信号处理函数,处理完了再回到内核空间,最后回到用户空间.没听懂吧,我自己都说晕了,所以需要专门的一篇来说清楚信号的处理问题.本篇不展开说. sig_cb结构体是任务处理信号的结构体,要响应,屏蔽哪些信号等等都由它完成,这个结构体虽不复杂,但是很绕,很难搞清楚它们之间的区别.笔者是经过一番痛苦的阅读理解后才明白各自的含义.并想通过用打比方的例子试图让大家明白. 用追女孩子这种永恒的爱情话题来理解吧.任务相当于某个男孩子,没错说的就是屏幕前的你,除了我们这些苦逼的码农谁会有耐心看到这里.信号就是女孩子.理解如下: waitList等待信号的任务链表,上面挂的是因等待信号而被阻塞的任务,都在排队追心爱的女孩,处于无所事事的挂起的状态,二流子,等待猎物们的出现. sigwaitmask任务在等待的信号集合,只有这些信号能唤醒任务.相当于你爱慕的诸多女孩,只要出现一位就能让你满血复活. sigprocmask指任务对哪些信号不感冒.来了也不处理.相当于你明确告知了哪些女孩不喜欢. sigPendFlag信号到大但并未唤醒任务.相当于爱你的女孩来找你了,但你不搭理人家继续挂起,你这得多贱的. sigFlag记录不屏蔽的信号集合,相当于你并不反感的女孩们,不说喜欢也不说不喜欢.渣男! 信号发送过程 用户进程调用kill()的过程如下: kill(pid_t pid, int sig) - 系统调用 | 用户空间 --------------------------------------------------------------------------------------- | 内核空间 SysKill(...) |---> OsKillLock(...) |---> OsKill(.., OS_USER_KILL_PERMISSION) |---> OsDispatch() //鉴权,向进程发送信号 |---> OsSigProcessSend() //选择任务发送信号 |---> OsSigProcessForeachChild(..,ForEachTaskCB handler,..) |---> SigProcessKillSigHandler() //处理 SIGKILL |---> OsTaskWake() //唤醒所有等待任务 |---> OsSigEmptySet() //清空信号等待集 |---> SigProcessSignalHandler() |---> OsTcbDispatch() //向目标任务发送信号 |---> OsTaskWake() //唤醒任务 |---> OsSigEmptySet() //清空信号等待集 流程 通过 系统调用 kill 陷入内核空间 因为是用户态进程,使用OS_USER_KILL_PERMISSION权限发送信号 #define OS_KERNEL_KILL_PERMISSION 0U //内核态 kill 权限 #define OS_USER_KILL_PERMISSION 3U //用户态 kill 权限 鉴权之后进程轮询任务组,向目标任务发送信号.这里分三种情况: SIGKILL信号,将所有等待任务唤醒,拉入就绪队列等待被调度执行,并情况信号等待集 非SIGKILL信号时,将通过sigwaitmask和sigprocmask过滤,找到一个任务向它发送信号OsTcbDispatch. 代码细节 int OsKill(pid_t pid, int sig, int permission) { siginfo_t info; int ret; /* Make sure that the para is valid */ if (!GOOD_SIGNO(sig) || pid < 0) {//有效信号 [0,64] return -EINVAL; } if (OsProcessIDUserCheckInvalid(pid)) {//检查参数进程 return -ESRCH; } /* Create the siginfo structure */ //创建信号结构体 info.si_signo = sig; //信号编号 info.si_code = SI_USER; //来自用户进程信号 info.si_value.sival_ptr = NULL; /* Send the signal */ ret = OsDispatch(pid, &info, permission);//发送信号 return ret; } OsDispatch() 函数的代码如下: //信号分发 int OsDispatch(pid_t pid, siginfo_t *info, int permission) { LosProcessCB *spcb = OS_PCB_FROM_PID(pid);//找到这个进程 if (OsProcessIsUnused(spcb)) {//进程是否还在使用,不一定是当前进程但必须是个有效进程 return -ESRCH; } #ifdef LOSCFG_SECURITY_CAPABILITY //启用能力安全模式 LosProcessCB *current = OsCurrProcessGet();//获取当前进程 /* If the process you want to kill had been inactive, but still exist. should return LOS_OK */ if (OsProcessIsInactive(spcb)) {//如果要终止的进程处于非活动状态,但仍然存在,应该返回OK return LOS_OK; } /* Kernel process always has kill permission and user process should check permission *///内核进程总是有kill权限,用户进程需要检查权限 if (OsProcessIsUserMode(current) && !(current->processStatus & OS_PROCESS_FLAG_EXIT)) {//用户进程检查能力范围 if ((current != spcb) && (!IsCapPermit(CAP_KILL)) && (current->user->userID != spcb->user->userID)) { return -EPERM; } } #endif if ((permission == OS_USER_KILL_PERMISSION) && (OsSignalPermissionToCheck(spcb) < 0)) { return -EPERM; } return OsSigProcessSend(spcb, info);//给参数进程发送信号 } //给参数进程发送参数信号 int OsSigProcessSend(LosProcessCB *spcb, siginfo_t *sigInfo) { int ret; struct ProcessSignalInfo info = { .sigInfo = sigInfo, //信号内容 .defaultTcb = NULL, //以下四个值将在OsSigProcessForeachChild中根据条件完善 .unblockedTcb = NULL, .awakenedTcb = NULL, .receivedTcb = NULL }; //总之是要从进程中找个至少一个任务来接受这个信号,优先级 //awakenedTcb > receivedTcb > unblockedTcb > defaultTcb /* visit all taskcb and dispatch signal */ //访问所有任务和分发信号 if ((info.sigInfo != NULL) && (info.sigInfo->si_signo == SIGKILL)) {//需要干掉进程时 SIGKILL = 9, #linux kill 9 14 (void)OsSigProcessForeachChild(spcb, SigProcessKillSigHandler, &info);//进程要被干掉了,通知所有task做善后处理 OsSigAddSet(&spcb->sigShare, info.sigInfo->si_signo); OsWaitSignalToWakeProcess(spcb);//等待信号唤醒进程 return 0; } else { ret = OsSigProcessForeachChild(spcb, SigProcessSignalHandler, &info);//进程通知所有task处理信号 } if (ret < 0) { return ret; } SigProcessLoadTcb(&info, sigInfo); return 0; } //让进程的每一个task执行参数函数 int OsSigProcessForeachChild(LosProcessCB *spcb, ForEachTaskCB handler, void *arg) { int ret; /* Visit the main thread last (if present) */ LosTaskCB *taskCB = NULL;//遍历进程的 threadList 链表,里面存放的都是task节点 LOS_DL_LIST_FOR_EACH_ENTRY(taskCB, &(spcb->threadSiblingList), LosTaskCB, threadList) {//遍历进程的任务列表 ret = handler(taskCB, arg);//回调参数函数 OS_RETURN_IF(ret != 0, ret);//这个宏的意思就是只有ret = 0时,啥也不处理.其余就返回 ret } return LOS_OK; } 解读 OsSigProcessSend 如果是 SIGKILL信号,让spcb的所有任务执行SigProcessKillSigHandler函数,查看旗下的所有任务是否又在等待这个信号的,如果有就将任务唤醒,放在就绪队列等待被调度执行. //进程收到 SIGKILL 信号后,通知任务tcb处理. static int SigProcessKillSigHandler(LosTaskCB *tcb, void *arg) { struct ProcessSignalInfo *info = (struct ProcessSignalInfo *)arg;//转参 if ((tcb != NULL) && (info != NULL) && (info->sigInfo != NULL)) {//进程有信号 sig_cb *sigcb = &tcb->sig; if (!LOS_ListEmpty(&sigcb->waitList) && OsSigIsMember(&sigcb->sigwaitmask, info->sigInfo->si_signo)) {//如果任务在等待这个信号 OsTaskWake(tcb);//唤醒这个任务,加入进程的就绪队列,并不申请调度 OsSigEmptySet(&sigcb->sigwaitmask);//清空信号等待位,不等任何信号了.因为这是SIGKILL信号 } } return 0; } 非SIGKILL信号,让spcb的所有任务执行SigProcessSignalHandler函数 static int SigProcessSignalHandler(LosTaskCB *tcb, void *arg) { struct ProcessSignalInfo *info = (struct ProcessSignalInfo *)arg;//先把参数解出来 int ret; int isMember; if (tcb == NULL) { return 0; } /* If the default tcb is not setted, then set this one as default. */ if (!info->defaultTcb) {//如果没有默认发送方的任务,即默认参数任务. info->defaultTcb = tcb; } isMember = OsSigIsMember(&tcb->sig.sigwaitmask, info->sigInfo->si_signo);//任务是否在等待这个信号 if (isMember && (!info->awakenedTcb)) {//是在等待,并尚未向该任务时发送信号时 /* This means the task is waiting for this signal. Stop looking for it and use this tcb. * The requirement is: if more than one task in this task group is waiting for the signal, * then only one indeterminate task in the group will receive the signal. */ ret = OsTcbDispatch(tcb, info->sigInfo);//发送信号,注意这是给其他任务发送信号,tcb不是当前任务 OS_RETURN_IF(ret < 0, ret);//这种写法很有意思 /* set this tcb as awakenedTcb */ info->awakenedTcb = tcb; OS_RETURN_IF(info->receivedTcb != NULL, SIG_STOP_VISIT); /* Stop search */ } /* Is this signal unblocked on this thread? */ isMember = OsSigIsMember(&tcb->sig.sigprocmask, info->sigInfo->si_signo);//任务是否屏蔽了这个信号 if ((!isMember) && (!info->receivedTcb) && (tcb != info->awakenedTcb)) {//没有屏蔽,有唤醒任务没有接收任务. /* if unblockedTcb of this signal is not setted, then set it. */ if (!info->unblockedTcb) { info->unblockedTcb = tcb; } ret = OsTcbDispatch(tcb, info->sigInfo);//向任务发送信号 OS_RETURN_IF(ret < 0, ret); /* set this tcb as receivedTcb */ info->receivedTcb = tcb;//设置这个任务为接收任务 OS_RETURN_IF(info->awakenedTcb != NULL, SIG_STOP_VISIT); /* Stop search */ } return 0; /* Keep searching */ } 函数的意思是,当进程中有多个任务在等待这个信号时,发送信号给第一个等待的任务awakenedTcb. 如果没有任务在等待信号,那就从不屏蔽这个信号的任务集中随机找一个receivedTcb接受信号. 只要不屏蔽 unblockedTcb就有值,随机的. 如果上面的都不满足,信号发送给defaultTcb. 寻找发送任务的优先级是 awakenedTcb > receivedTcb > unblockedTcb > defaultTcb 信号相关函数 信号集操作函数 sigemptyset(sigset_t *set):信号集全部清0; sigfillset(sigset_t *set): 信号集全部置1,则信号集包含linux支持的64种信号; sigaddset(sigset_t *set, int signum):向信号集中加入signum信号; sigdelset(sigset_t *set, int signum):向信号集中删除signum信号; sigismember(const sigset_t *set, int signum):判定信号signum是否存在信号集中。 信号阻塞函数 sigprocmask(int how, const sigset_t *set, sigset_t *oldset)); 不同how参数,实现不同功能 SIG_BLOCK:将set指向信号集中的信号,添加到进程阻塞信号集; SIG_UNBLOCK:将set指向信号集中的信号,从进程阻塞信号集删除; SIG_SETMASK:将set指向信号集中的信号,设置成进程阻塞信号集; sigpending(sigset_t *set)):获取已发送到进程,却被阻塞的所有信号; sigsuspend(const sigset_t *mask)):用mask代替进程的原有掩码,并暂停进程执行,直到收到信号再恢复原有掩码并继续执行进程。 鸿蒙源码百篇博客 往期回顾 v48.xx (信号分发篇) | 任务和信号能比喻成什么关系? < csdn | 51cto | harmony > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | harmony > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | 51cto | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | harmony > v38.xx (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony > v37.xx (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | 51cto | harmony > v36.xx (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > v35.xx (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | 51cto | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony > v32.xx (cpu篇) | 内核是如何描述cpu的? < csdn | 51cto | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | harmony > v28.xx (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | 51cto | harmony > v27.xx (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | 51cto | harmony > v26.xx (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | 51cto | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | harmony > v22.xx (汇编基础篇) | cpu在哪里打卡上班? < csdn | 51cto | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾cpu? < csdn | 51cto | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | harmony > v13.xx (源码注释篇) | 热爱是所有的理由和答案 < csdn | 51cto | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | harmony > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | harmony > v06.xx (调度队列篇) | 内核有多少个调度队列? < csdn | 51cto | harmony > v05.xx (任务管理篇) | 任务池是如何管理的? < csdn | 51cto | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | harmony > v03.xx (时钟任务篇) | 调度最大的源动力来自哪里? < csdn | 51cto | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | 51cto | harmony > 参与贡献 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处. 进入 >> oschina | csdn | 51cto | 简书 | 掘金 | harmony

优秀的个人博客,低调大师

鸿蒙内核源码分析(进程通讯篇) | 九大通讯方式一网打尽 | 中文注解HarmonyOS源码 | v28.01

鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇开始介绍进程通讯(IPC) 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇幅. 进程间为何要通讯 ? 鸿蒙内核默认支持 64个进程 和 128个任务. 内核设计尽量不去打扰它们,让各人过好各自的日子, 但大家毕竟在一口锅里吃饭, 不可能不与外界联系, 联系就得有方法,有规矩. 举两个应用场景说明下通讯的必要性: 一.被动式 广为熟知的shell命令 kill 9 13 ,是通过 shell任务给 13号进程发送一个干掉它的信号. #define SIGKILL 9 //常用的命令 kill 9 13 这是被动式通讯的场景,至于为什么要干掉你,原因可能很多啊,很可能是检测到13占用内存太多了,也可能13太低调长期不活跃,启动新进程发现没位置了,得收了你.反正系统必须得有对付你的抓手. 二.主动式的 ,比如要访问某些公共资源(全局变量,消息队列),而资源有限或具有排他性,别人正在使用导致你不能用, 所以需统一管理,要用就必须要先申请,按规矩办事,毕竟和谐社会没规矩不成方圆. 总之大概有以下几种需求需要通讯: (1).数据传输: 一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 (2).共享数据: 多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。 (3).通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 (4).资源共享: 多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。 (5).进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 内核目录和系列篇更新 内核有个专门的IPC目录,详见如下. 可直接点击查看注解源码. 进程间通讯方式 查看源码注解点击一下 .c 文件 1.管道pipe(fs_syscall.c) 管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。 调用pipe系统函数即可创建一个管道。有如下特质: 其本质是一个伪文件(实为内核缓冲区) 由两个文件描述符引用,一个表示读端,一个表示写端。 规定数据从管道的写端流入管道,从读端流出。 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。 管道的局限性: ① 数据自己读不能自己写。 ② 数据一旦被读走,便不在管道中存在,不可反复读取。 ③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。 ④ 只能在有公共祖先的进程间使用管道。 常见的通信方式有,单工通信、半双工通信、全双工通信。 鸿蒙的管道实现很简单, 详细看SysPipe函数. 2.信号(los_signal.c) 信号是用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。 如果该进程当前并未处于执行状态,则该信号就由内核保存起来,直到该进程被调度执行并传递给它为止。 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。 进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。 #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(如break键被按下) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 13 #define SIGUSR1 10 //用户自定义信号1 #define SIGSEGV 11 //无效的内存引用, 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置 #define SIGUSR2 12 //用户自定义信号2 #define SIGPIPE 13 //向某个非读管道中写入数据 #define SIGALRM 14 //由alarm(2)发出的信号,默认行为为进程终止 #define SIGTERM 15 //软件终止(software termination) #define SIGSTKFLT 16 #define SIGCHLD 17 //子进程结束信号 #define SIGCONT 18 //进程继续(曾被停止的进程) #define SIGSTOP 19 //终止进程 #define SIGTSTP 20 //控制终端(tty)上 按下停止键 3.消息队列(los_queue.c) 基本概念 队列又称消息队列,是一种常用于任务间通信的数据结构。队列接收来自任务或中断的 不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。 任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时, 挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时, 挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将 读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。 消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。 队列特性 消息以先进先出的方式排队,支持异步读写。 读队列和写队列都支持超时机制。 每读取一条消息,就会将该消息节点设置为空闲。 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。 一个任务能够从任意一个消息队列接收和发送消息。 多个任务能够从同一个消息队列接收和发送消息。 创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式。 4.共享内存(shm.c) 共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存 不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候, 其它进程都会察觉到这个更改。 (共享内存篇)| 正在更新中... 5.信号量(los_sem.c) 基本概念 信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。 一个信号量的数据结构中,通常有一个计数值,用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况: 0,表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。 正值,表示该信号量当前可被获取。 以同步为目的的信号量和以互斥为目的的信号量在使用上有如下不同: 用作互斥时,初始信号量计数值不为0,表示可用的共享资源个数。在需要使用共享资源前,先获取信号量, 然后使用一个共享资源,使用完毕后释放信号量。这样在共享资源被取完,即信号量计数减至0时,其他 需要获取信号量的任务将被阻塞,从而保证了共享资源的互斥访问。另外,当共享资源数为1时, 建议使用二值信号量,一种类似于互斥锁的机制。 用作同步时,初始信号量计数值为0。任务1获取信号量而阻塞,直到任务2或者某中断释放信号量, 任务1才得以进入Ready或Running态,从而达到了任务间的同步。 使用场景 在多任务系统中,信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能, 也能方便的用于任务与任务,中断与任务的同步中。信号量常用于协助一组相互竞争的任务访问共享资源。 (信号量篇)|正在更新中... 6.互斥锁 (los_mux.c) : 基本概念 互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对临界资源的独占式处理。 另外,互斥锁可以解决信号量存在的优先级翻转问题。 任意时刻互斥锁只有两种状态,开锁或闭锁。当任务持有时,这个任务获得该互斥锁的所有权, 互斥锁处于闭锁状态。当该任务释放锁后,任务失去该互斥锁的所有权,互斥锁处于开锁状态。 当一个任务持有互斥锁时,其他任务不能再对该互斥锁进行开锁或持有。 详见: (互斥锁篇) | 为何任务会因拥有锁而被迫改变优先级 ? 7.快锁 (los_futex.c) futex 是Fast Userspace muTexes的缩写(快速用户空间互斥体),是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。 8.事件 (los_event.c) 基本概念 事件(Event)是一种任务间通信的机制,可用于任务间的同步。 多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。 一对多同步模型:一个任务等待多个事件的触发。可以是任意一个事件发生时唤醒任务处理事件,也可以是几个事件都发生后才唤醒任务处理事件。 多对多同步模型:多个任务等待多个事件的触发。 事件特点 任务通过创建事件控制块来触发事件或等待事件。 事件间相互独立,内部实现为一个32位无符号整型,每一位标识一种事件类型。第25位不可用,因此最多可支持31种事件类型。 事件仅用于任务间的同步,不提供数据传输功能。 多次向事件控制块写入同一事件类型,在被清零前等效于只写入一次。 多个任务可以对同一事件进行读写操作。 支持事件读写超时机制。 事件可应用于多种任务同步场景,在某些同步场景下可替代信号量。 使用场景 队列用于任务间通信,可以实现消息的异步处理。同时消息的发送方和接收方不需要彼此联系,两者间是解耦的。 (事件驱动篇)|正在更新中... 9.文件消息队列 (hm_liteipc.c) 基于文件实现的消息队列,特点是队列中消息数量多(256个),传递消息内容大(可达到1K) #define IPC_MSG_DATA_SZ_MAX 1024 //最大的消息内容 1K ,posix最大消息内容 64个字节 #define IPC_MSG_OBJECT_NUM_MAX 256 //最大的消息数量256 ,posix最大消息数量 16个 (消息队列篇)|正在更新中... 以上为鸿蒙内核的九种进程间通讯方式,源码注解已基本完成,可前往Fork查看. 每一种通讯方式博客将详细说明,正在更新中... 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

(Portal 开发读书笔记) Portal开发常用概念

Portal是什么? Portal是一组小的web应用(也就是Portlet)的集合。它提供了如下的功能比如:个人化,内容聚合(从各个不同的信息源),认证,定制。换句话说,它提供了整体页面。Portal不仅仅聚合Portlet,还要聚合其他页帧片断,这也片断也许根本juice不是Portlet. Portlet是什么? Portlet是一个可以插入的UI组件,它可以提供指定的内容,这些内容可以来自于信息系统。它的主要责任是产生标记语言的片断,并且显示于Portlet Window中,然后多个Portlet Window共同被Portal页面所包含。换句话说,它提供了页面的一个区域。 Portal架构: Portal架构包含Portlet 容器和Portal服务器: Portlet 容器: 它负责管理Portlet实例的生命周期,并且负责发送Portlet产生的页帧到Portal服务器去聚合。 Portal服务器: 它负责提交来自Portal页面上的用户请求到Portlet容器,并且负责聚合多个Portlet产生的页帧到Portal页面,还负责为Portal页面提供一致的外观。 这些概念关系如下图所示: 本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/847767,如需转载请自行联系原作者

优秀的个人博客,低调大师

鸿蒙内核源码分析(信号消费篇) | 用户栈到内核栈的两次切换 | 百篇博客分析HarmonyOS源码 | v49.02

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 信号消费 本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下: v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | harmony > v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > 本篇有相当的难度,涉及用户栈和内核栈的两次切换,寄存器改值,将围绕下图来说明. 解读 为本篇理解方便,把图做简化标签说明: user:用户空间, kernel:内核空间, source:源函数 sighandle:信号处理函数,syscall:任意系统调用号-N, syscall:信号处理完成的系统调用号-sigreturn 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈. 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复. 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题. 顺着这个思路可以推到以下几点,实际也是这么做的: kernel.syscall(N) 中必须要再次保存user.source()的上下文sig_switch_context,为什么已经保存了一遍还要再保存一次? 因为第一次是保存在内核栈中,而内核栈的数据会因回到用户态user.sighandle()运行而被清空数据.第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的. typedef struct { // ... sig_cb sig;//信号控制块,用于异步通信 } LosTaskCB; 还必须要改变原有PC/R0/R1寄存器的值.因为要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数. 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:__NR_sigreturn,这也是个系统调用.回来后还原sig_switch_context,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()打断处继续执行. 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数 OsArmA32SyscallHandle,OsSaveSignalContext,OsRestorSignalContext本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context. sig_switch_context //任务中断上下文 #define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//信号切换上下文 TASK_IRQ_CONTEXT unsigned int R7; //存放系统调用的ID unsigned int count; //记录是否保存了信号上下文 } sig_switch_context; 这是保存用户栈的结构体,USP,ULR代表用户栈指针和返回地址. 其他寄存器没有保存的原因是系统调用不会用到那些寄存器,所以不需要保存. R7是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数. count记录是否保存了信号上下文 OsArmA32SyscallHandle 系统调用总入口 /* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号 regs:参数就是所有寄存器 注意:本函数在用户态和内核态下都可能被调用到 //MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 ******************************************************************/ LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值 case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序. /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程. 关于系统调用可查看 v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > 本篇不详细说系统调用过程,只说跟信号相关的部分. OsArmA32SyscallHandle总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext,第二次进入OsArmA32SyscallHandle后再执行OsRestorSignalContext. 看OsSaveSignalContext,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. 在函数的开头,碰到系统调用号__NR_sigreturn,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } OsSaveSignalContext 保存信号上下文 有了上面的铺垫,就不难理解这个函数的作用. /********************************************** 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息 改写PC寄存器的值 **********************************************/ void OsSaveSignalContext(unsigned int *sp) { UINTPTR sigHandler; UINT32 intSave; LosTaskCB *task = NULL; LosProcessCB *process = NULL; sig_cb *sigcb = NULL; unsigned long cpsr; OS_RETURN_IF_VOID(sp == NULL); cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值 OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇 task = OsCurrTaskGet(); process = OsCurrProcessGet(); sigcb = &task->sig;//获取任务的信号控制块 //1.未保存任务上下文任务 //2.任何的信号标签集不为空或者进程有信号要处理 if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) { sigHandler = OsGetSigHandler();//获取信号处理函数 if (sigHandler == 0) {//信号没有注册 sigcb->sigFlag = 0; process->sigShare = 0; SCHEDULER_UNLOCK(intSave); PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID); return; } /* One pthread do the share signal */ sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集 unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet(process, signo);//设置进程退出信号 sigcb->context.CPSR = cpsr; //保存状态寄存器 sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值 sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈 sigcb->context.ULR = sp[REG_LR];//用户栈返回地址 sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值 sigcb->context.R1 = sp[REG_R1]; sigcb->context.R2 = sp[REG_R2]; sigcb->context.R3 = sp[REG_R3]; sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613 sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */ sigcb->sigFlag ^= 1ULL << (signo - 1); sigcb->context.count++; //代表已保存 } SCHEDULER_UNLOCK(intSave); } 解读 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号signo,注意可不是系统调用号,有区别的.信号编号长这样. #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 系统调用号长这样,是不是看到一些很熟悉的函数. #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在_osExceptSwiHdl中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 OsRestorSignalContext 恢复信号上下文 /**************************************************** 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用. 为什么要恢复呢? 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容, 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场 所以函数的功能就变成了还原寄存器的值 ****************************************************/ void OsRestorSignalContext(unsigned int *sp) { LosTaskCB *task = NULL; /* Do not adjust this statement */ LosProcessCB *process = NULL; sig_cb *sigcb = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); task = OsCurrTaskGet(); sigcb = &task->sig;//获取当前任务信号控制块 if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复 SCHEDULER_UNLOCK(intSave); PRINT_ERR("sig error count : %d\n", sigcb->context.count); return; } process = OsCurrProcessGet();//获取当前进程 sp[REG_PC] = sigcb->context.PC;//指令寄存器 OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程序状态寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 sp[REG_R0] = sigcb->context.R0; sp[REG_R1] = sigcb->context.R1; sp[REG_R2] = sigcb->context.R2; sp[REG_R3] = sigcb->context.R3; sp[REG_R7] = sigcb->context.R7; sp[REG_R12] = sigcb->context.R12; sigcb->context.count--; //信号上下文的数量回到减少 process->sigShare = 0; //回到用户态,信号共享清0 OsProcessExitCodeSignalClear(process);//清空进程退出码 SCHEDULER_UNLOCK(intSave); } 解读 在信号处理函数完成之后,内核会触发一个__NR_sigreturn的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle. 恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句. sp[REG_PC] = sigcb->context.PC;//指令寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.USP和ULR指向的是用户栈的位置.一旦PC,USP,ULR从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. 真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着OsArmA32SyscallHandle @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行 @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解 SUB SP, SP, #(4 * 16) @先申请16个栈空间单元用于处理本次软中断 STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放R0 .. R12 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 => TaskContext.regPSR ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LR和SP SUB SP, SP, #4 @ => TaskContext.resved PUSH_FPU_REGS R1 @保存中断模式(用户模式) @保存任务上下文(TaskContext) 结束 MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/ CPSID I @执行后续指令前必须先关中断 @恢复任务上下文(TaskContext) 开始 POP_FPU_REGS R1 @弹出FPU值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 @恢复任务上下文(TaskContext) 结束 具体也可看这两篇: v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | harmony > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | harmony > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | harmony > v38.xx (寄存器篇) | arm所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | harmony > v28.xx (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | 51cto | harmony > v27.xx (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | 51cto | harmony > v26.xx (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | 51cto | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | harmony > v22.xx (汇编基础篇) | cpu在哪里打卡上班? < csdn | 51cto | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾cpu? < csdn | 51cto | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | harmony > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | harmony > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | harmony > v06.xx (调度队列篇) | 内核有多少个调度队列? < csdn | 51cto | harmony > v05.xx (任务管理篇) | 任务池是如何管理的? < csdn | 51cto | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | harmony > v03.xx (时钟任务篇) | 调度最大的源动力来自哪里? < csdn | 51cto | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | 51cto | harmony > 进入 >> oschina | csdn | 51cto | 掘金 | harmony 参与贡献 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处.

优秀的个人博客,低调大师

鸿蒙内核源码分析(信号消费篇) | 用户栈到内核栈的两次切换 | 百篇博客分析HarmonyOS源码 | v49.03

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 信号消费 本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下: v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | harmony > v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > 本篇有相当的难度,涉及用户栈和内核栈的两次切换,寄存器改值,将围绕下图来说明. 解读 为本篇理解方便,把图做简化标签说明: user:用户空间 kernel:内核空间 source(...):源函数 sighandle(...):信号处理函数, syscall(...):系统调用,参数为系统调用号,如sigreturn,N(表任意) user.source():表示在用户空间运行的源函数 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈. 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复. 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题. 顺着这个思路可以推到以下几点,实际也是这么做的: kernel.syscall(N) 中必须要再次保存user.source()的上下文sig_switch_context,为何已经保存了一次还要再保存一次? 因为第一次是保存在内核栈中,而内核栈这部分数据会因回到用户态user.sighandle()运行而被恢复现场出栈了.保存现场/恢复现场是成双出队的好基友,注意有些文章说会把整个内核栈清空,这是不对的. 第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的.两次保存的都是user.source()运行时现场信息,再回顾下相关的结构体.关键是sig_switch_context typedef struct { // ... sig_cb sig;//信号控制块,用于异步通信 } LosTaskCB; typedef struct {//信号控制块(描述符) sigset_t sigFlag; //不屏蔽的信号集 sigset_t sigPendFlag; //信号阻塞标签集,记录那些信号来过,任务依然阻塞的集合.即:这些信号不能唤醒任务 sigset_t sigprocmask; /* Signals that are blocked */ //任务屏蔽了哪些信号 sq_queue_t sigactionq; //信号捕捉队列 LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 请查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解 sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待哪些信号的到来 siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息 sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换 } sig_cb; 还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数. 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:__NR_sigreturn,这也是个系统调用.回来后还原sig_switch_context,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行. 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数 OsArmA32SyscallHandle,OsSaveSignalContext,OsRestorSignalContext本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context. sig_switch_context //任务中断上下文 #define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//信号切换上下文 TASK_IRQ_CONTEXT unsigned int R7; //存放系统调用的ID unsigned int count; //记录是否保存了信号上下文 } sig_switch_context; 保存user.source()现场的结构体,USP,ULR代表用户栈指针和返回地址. CPSR寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看 v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > 谈论的用户态(usr普通用户)和内核态(sys超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行. 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存. R7是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数. count记录是否保存了信号上下文 OsArmA32SyscallHandle 系统调用总入口 /* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号 regs:参数就是所有寄存器 注意:本函数在用户态和内核态下都可能被调用到 //MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 ******************************************************************/ LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值 case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序. /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */ return regs;//返回寄存器的值 } 解读 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程. 关于系统调用可查看 v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > 本篇不详细说系统调用过程,只说跟信号相关的部分. OsArmA32SyscallHandle总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext,第二次进入OsArmA32SyscallHandle后再执行OsRestorSignalContext. 看OsSaveSignalContext,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. 在函数的开头,碰到系统调用号__NR_sigreturn,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } OsSaveSignalContext 保存信号上下文 有了上面的铺垫,就不难理解这个函数的作用. /********************************************** 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息 改写PC寄存器的值 **********************************************/ void OsSaveSignalContext(unsigned int *sp) { UINTPTR sigHandler; UINT32 intSave; LosTaskCB *task = NULL; LosProcessCB *process = NULL; sig_cb *sigcb = NULL; unsigned long cpsr; OS_RETURN_IF_VOID(sp == NULL); cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值 OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇 task = OsCurrTaskGet(); process = OsCurrProcessGet(); sigcb = &task->sig;//获取任务的信号控制块 //1.未保存任务上下文任务 //2.任何的信号标签集不为空或者进程有信号要处理 if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) { sigHandler = OsGetSigHandler();//获取信号处理函数 if (sigHandler == 0) {//信号没有注册 sigcb->sigFlag = 0; process->sigShare = 0; SCHEDULER_UNLOCK(intSave); PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID); return; } /* One pthread do the share signal */ sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集 unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet(process, signo);//设置进程退出信号 sigcb->context.CPSR = cpsr; //保存状态寄存器 sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值 sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈 sigcb->context.ULR = sp[REG_LR];//用户栈返回地址 sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值 sigcb->context.R1 = sp[REG_R1]; sigcb->context.R2 = sp[REG_R2]; sigcb->context.R3 = sp[REG_R3]; sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613 sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */ sigcb->sigFlag ^= 1ULL << (signo - 1); sigcb->context.count++; //代表已保存 } SCHEDULER_UNLOCK(intSave); } 解读 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号signo,注意可不是系统调用号,有区别的.信号编号长这样. #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 系统调用号长这样,是不是看到一些很熟悉的函数. #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在_osExceptSwiHdl中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 OsRestorSignalContext 恢复信号上下文 /**************************************************** 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用. 为什么要恢复呢? 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容, 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场 所以函数的功能就变成了还原寄存器的值 ****************************************************/ void OsRestorSignalContext(unsigned int *sp) { LosTaskCB *task = NULL; /* Do not adjust this statement */ LosProcessCB *process = NULL; sig_cb *sigcb = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); task = OsCurrTaskGet(); sigcb = &task->sig;//获取当前任务信号控制块 if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复 SCHEDULER_UNLOCK(intSave); PRINT_ERR("sig error count : %d\n", sigcb->context.count); return; } process = OsCurrProcessGet();//获取当前进程 sp[REG_PC] = sigcb->context.PC;//指令寄存器 OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程序状态寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 sp[REG_R0] = sigcb->context.R0; sp[REG_R1] = sigcb->context.R1; sp[REG_R2] = sigcb->context.R2; sp[REG_R3] = sigcb->context.R3; sp[REG_R7] = sigcb->context.R7; sp[REG_R12] = sigcb->context.R12; sigcb->context.count--; //信号上下文的数量回到减少 process->sigShare = 0; //回到用户态,信号共享清0 OsProcessExitCodeSignalClear(process);//清空进程退出码 SCHEDULER_UNLOCK(intSave); } 解读 在信号处理函数完成之后,内核会触发一个__NR_sigreturn的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle. 恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句. sp[REG_PC] = sigcb->context.PC;//指令寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.USP和ULR指向的是用户栈的位置.一旦PC,USP,ULR从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. 真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着OsArmA32SyscallHandle @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行 @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解 SUB SP, SP, #(4 * 16) @先申请16个栈空间单元用于处理本次软中断 STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放R0 .. R12 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 => TaskContext.regPSR ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LR和SP SUB SP, SP, #4 @ => TaskContext.resved PUSH_FPU_REGS R1 @保存中断模式(用户模式) @保存任务上下文(TaskContext) 结束 MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/ CPSID I @执行后续指令前必须先关中断 @恢复任务上下文(TaskContext) 开始 POP_FPU_REGS R1 @弹出FPU值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 @恢复任务上下文(TaskContext) 结束 具体也可看这两篇: v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > 鸿蒙源码百篇博客 往期回顾 在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P 写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容. v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | harmony > v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | harmony > v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | harmony > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | harmony > v38.xx (寄存器篇) | arm所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony > v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony > v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | harmony > v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | harmony > v27.xx (互斥锁篇) | 比自旋锁丰满许多的互斥锁 < csdn | 51cto | harmony > v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊! < csdn | 51cto | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | harmony > v22.xx (汇编基础篇) | cpu在哪里打卡上班? < csdn | 51cto | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾cpu? < csdn | 51cto | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony > v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | harmony > v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | harmony > v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | harmony > v06.xx (调度队列篇) | 内核有多少个调度队列? < csdn | 51cto | harmony > v05.xx (任务管理篇) | 任务池是如何管理的? < csdn | 51cto | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | harmony > v03.xx (时钟任务篇) | 调度最大的源动力来自哪里? < csdn | 51cto | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | 51cto | harmony > 进入 >> oschina | csdn | 51cto | 掘金 | harmony 参与贡献 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处.

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册