百篇博客系列篇.本篇为:

进程管理相关篇为:

信号消费

本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下:

本篇有相当的难度,涉及用户栈和内核栈的两轮切换,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
  1. typedef struct {
  2. // ...
  3. sig_cb sig;//信号控制块,用于异步通信
  4. } LosTaskCB;
  5. typedef struct {//信号控制块(描述符)
  6. sigset_t sigFlag; //不屏蔽的信号集
  7. sigset_t sigPendFlag; //信号阻塞标签集,记录那些信号来过,任务依然阻塞的集合.即:这些信号不能唤醒任务
  8. sigset_t sigprocmask; /* Signals that are blocked */ //任务屏蔽了哪些信号
  9. sq_queue_t sigactionq; //信号捕捉队列
  10. LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 请查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解
  11. sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待哪些信号的到来
  12. siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息
  13. sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换
  14. } sig_cb;
  • 还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数.
  • 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:__NR_sigreturn,这也是个系统调用.回来后还原sig_switch_context,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行.
  • 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数 OsArmA32SyscallHandleOsSaveSignalContextOsRestorSignalContext本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context.

sig_switch_context

  1. //任务中断上下文
  2. #define TASK_IRQ_CONTEXT \
  3. unsigned int R0; \
  4. unsigned int R1; \
  5. unsigned int R2; \
  6. unsigned int R3; \
  7. unsigned int R12; \
  8. unsigned int USP; \
  9. unsigned int ULR; \
  10. unsigned int CPSR; \
  11. unsigned int PC;
  12. typedef struct {//信号切换上下文
  13. TASK_IRQ_CONTEXT
  14. unsigned int R7; //存放系统调用的ID
  15. unsigned int count; //记录是否保存了信号上下文
  16. } sig_switch_context;
  • 保存user.source()现场的结构体,USPULR代表用户栈指针和返回地址.
  • CPSR寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看

    v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆?

    谈论的用户态(usr普通用户)和内核态(sys超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行.
  • 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存.
  • R7是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数.
  • count记录是否保存了信号上下文

OsArmA32SyscallHandle 系统调用总入口

  1. /* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */
  2. /******************************************************************
  3. 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle
  4. SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号
  5. regs:参数就是所有寄存器
  6. 注意:本函数在用户态和内核态下都可能被调用到
  7. //MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数
  8. ******************************************************************/
  9. LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)
  10. {
  11. UINT32 ret;
  12. UINT8 nArgs;
  13. UINTPTR handle;
  14. UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用
  15. if (cmd >= SYS_CALL_NUM) {//系统调用的总数
  16. PRINT_ERR("Syscall ID: error %d !!!\n" cmd);
  17. return regs;
  18. }
  19. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn
  20. if (cmd == __NR_sigreturn) {
  21. OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行.
  22. return regs;
  23. }
  24. handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead
  25. nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */
  26. nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数
  27. if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个
  28. PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n" cmd nArgs);
  29. regs[REG_R0] = -ENOSYS;
  30. return regs;
  31. }
  32. //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因
  33. switch (nArgs) {//参数的个数
  34. case ARG_NUM_0:
  35. case ARG_NUM_1:
  36. ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname);
  37. break;
  38. case ARG_NUM_2://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值
  39. case ARG_NUM_3:
  40. ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp);
  41. break;
  42. case ARG_NUM_4:
  43. case ARG_NUM_5:
  44. ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
  45. regs[REG_R4]);
  46. break;
  47. default: //7个参数的情况
  48. ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
  49. regs[REG_R4], regs[REG_R5], regs[REG_R6]);
  50. }
  51. regs[REG_R0] = ret;//R0保存系统调用返回值
  52. OsSaveSignalContext(regs);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序.
  53. /* Return the last value of curent_regs. This supports context switches on return from the exception.
  54. * That capability is only used with the SYS_context_switch system call.
  55. */
  56. return regs;//返回寄存器的值
  57. }

解读

  • 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程.
  • 关于系统调用可查看

    v37.xx (系统调用篇) | 系统调用到底经历了什么

    本篇不详细说系统调用过程,只说跟信号相关的部分.
  • OsArmA32SyscallHandle总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext,第二次进入OsArmA32SyscallHandle后再执行OsRestorSignalContext.
  • OsSaveSignalContext,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行.
  • 在函数的开头,碰到系统调用号__NR_sigreturn,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作.
  1. //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn
  2. if (cmd == __NR_sigreturn) {
  3. OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行.
  4. return regs;
  5. }

OsSaveSignalContext 保存信号上下文

有了上面的铺垫,就不难理解这个函数的作用.

  1. /**********************************************
  2. 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息
  3. 改写PC寄存器的值
  4. **********************************************/
  5. void OsSaveSignalContext(unsigned int *sp)
  6. {
  7. UINTPTR sigHandler;
  8. UINT32 intSave;
  9. LosTaskCB *task = NULL;
  10. LosProcessCB *process = NULL;
  11. sig_cb *sigcb = NULL;
  12. unsigned long cpsr;
  13. OS_RETURN_IF_VOID(sp == NULL);
  14. cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值
  15. OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事.
  16. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇
  17. task = OsCurrTaskGet();
  18. process = OsCurrProcessGet();
  19. sigcb = &task->sig;//获取任务的信号控制块
  20. //1.未保存任务上下文任务
  21. //2.任何的信号标签集不为空或者进程有信号要处理
  22. if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) {
  23. sigHandler = OsGetSigHandler();//获取信号处理函数
  24. if (sigHandler == 0) {//信号没有注册
  25. sigcb->sigFlag = 0;
  26. process->sigShare = 0;
  27. SCHEDULER_UNLOCK(intSave);
  28. PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n" task->processID);
  29. return;
  30. }
  31. /* One pthread do the share signal */
  32. sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集
  33. unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1;
  34. OsProcessExitCodeSignalSet(process signo);//设置进程退出信号
  35. sigcb->context.CPSR = cpsr; //保存状态寄存器
  36. sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值
  37. sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈
  38. sigcb->context.ULR = sp[REG_LR];//用户栈返回地址
  39. sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值
  40. sigcb->context.R1 = sp[REG_R1];
  41. sigcb->context.R2 = sp[REG_R2];
  42. sigcb->context.R3 = sp[REG_R3];
  43. sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号.
  44. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613
  45. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数.
  46. sp[REG_R0] = signo; //参数1,信号ID
  47. sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2
  48. /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */
  49. sigcb->sigFlag ^= 1ULL << (signo - 1);
  50. sigcb->context.count++; //代表已保存
  51. }
  52. SCHEDULER_UNLOCK(intSave);
  53. }

解读

  • 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号signo,注意可不是系统调用号,有区别的.信号编号长这样.
  1. #define SIGHUP 1 //终端挂起或者控制进程终止
  2. #define SIGINT 2 //键盘中断(ctrl + c)
  3. #define SIGQUIT 3 //键盘的退出键被按下
  4. #define SIGILL 4 //非法指令
  5. #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行
  6. #define SIGABRT 6 //由abort(3)发出的退出指令
  7. #define SIGIOT SIGABRT //abort发出的信号
  8. #define SIGBUS 7 //总线错误
  9. #define SIGFPE 8 //浮点异常
  10. #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞

系统调用号长这样,是不是看到一些很熟悉的函数.

  1. #define __NR_restart_syscall 0
  2. #define __NR_exit 1
  3. #define __NR_fork 2
  4. #define __NR_read 3
  5. #define __NR_write 4
  6. #define __NR_open 5
  7. #define __NR_close 6
  8. #define __NR_waitpid 7
  9. #define __NR_creat 8
  10. #define __NR_link 9
  11. #define __NR_unlink 10
  12. #define __NR_execve 11
  13. #define __NR_chdir 12
  14. #define __NR_time 13
  15. #define __NR_mknod 14
  16. #define __NR_chmod 15
  17. #define __NR_lchown 16
  18. #define __NR_break 17
  • 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在_osExceptSwiHdl中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数.
  1. sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数.
  2. sp[REG_R0] = signo; //参数1,信号ID
  3. sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2

OsRestorSignalContext 恢复信号上下文

  1. /****************************************************
  2. 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用.
  3. 为什么要恢复呢?
  4. 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容,
  5. 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场
  6. 所以函数的功能就变成了还原寄存器的值
  7. ****************************************************/
  8. void OsRestorSignalContext(unsigned int *sp)
  9. {
  10. LosTaskCB *task = NULL; /* Do not adjust this statement */
  11. LosProcessCB *process = NULL;
  12. sig_cb *sigcb = NULL;
  13. UINT32 intSave;
  14. SCHEDULER_LOCK(intSave);
  15. task = OsCurrTaskGet();
  16. sigcb = &task->sig;//获取当前任务信号控制块
  17. if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复
  18. SCHEDULER_UNLOCK(intSave);
  19. PRINT_ERR("sig error count : %d\n" sigcb->context.count);
  20. return;
  21. }
  22. process = OsCurrProcessGet();//获取当前进程
  23. sp[REG_PC] = sigcb->context.PC;//指令寄存器
  24. OS_SYSCALL_SET_CPSR(sp sigcb->context.CPSR);//重置程序状态寄存器
  25. sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行
  26. sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置
  27. sp[REG_R0] = sigcb->context.R0;
  28. sp[REG_R1] = sigcb->context.R1;
  29. sp[REG_R2] = sigcb->context.R2;
  30. sp[REG_R3] = sigcb->context.R3;
  31. sp[REG_R7] = sigcb->context.R7;
  32. sp[REG_R12] = sigcb->context.R12;
  33. sigcb->context.count--; //信号上下文的数量回到减少
  34. process->sigShare = 0; //回到用户态,信号共享清0
  35. OsProcessExitCodeSignalClear(process);//清空进程退出码
  36. SCHEDULER_UNLOCK(intSave);
  37. }

解读

  • 在信号处理函数完成之后,内核会触发一个__NR_sigreturn的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle.
  • 恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句.
  1. sp[REG_PC] = sigcb->context.PC;//指令寄存器
  2. sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行
  3. sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置

注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.USPULR指向的是用户栈的位置.一旦PCUSPULR从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行.

  • 真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着OsArmA32SyscallHandle
  1. @ Description: Software interrupt exception handler
  2. _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行
  3. @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解
  4. SUB SP SP #(4 * 16) @先申请16个栈空间单元用于处理本次软中断
  5. STMIA SP {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放R0 .. R12
  6. MRS R3 SPSR @读取本模式下的SPSR
  7. MOV R4 LR @保存回跳寄存器LR
  8. AND R1 R3 #CPSR_MASK_MODE @ Interrupted mode 获取中断模式
  9. CMP R1 #CPSR_USER_MODE @ User mode 是否为用户模式
  10. BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转
  11. @ 当为用户模式时,获取SPLR寄出去值
  12. @ we enter from user mode we need get the values of USER mode r13(sp) and r14(lr).
  13. @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list).
  14. MOV R0 SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数
  15. STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR => TaskContext.regPSR
  16. ADD R3 SP #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置
  17. STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC
  18. STMFD R3 {R13 R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LRSP
  19. SUB SP SP #4 @ => TaskContext.resved
  20. PUSH_FPU_REGS R1 @保存中断模式(用户模式)
  21. @保存任务上下文(TaskContext) 结束
  22. MOV FP #0 @ Init frame pointer
  23. CPSIE I @开中断,表明在系统调用期间可响应中断
  24. BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/
  25. CPSID I @执行后续指令前必须先关中断
  26. @恢复任务上下文(TaskContext) 开始
  27. POP_FPU_REGS R1 @弹出FPU值给R1
  28. ADD SP SP,#4 @ 定位到保存旧SPSR值的位置
  29. LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR
  30. MSR SPSR_cxsf R3 @ Set the return mode SPSR 恢复该模式下的SPSR
  31. @ we are leaving to user mode we need to restore the values of USER mode r13(sp) and r14(lr).
  32. @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list)
  33. LDMFD SP!, {R0-R12} @恢复R0-R12寄存器
  34. LDMFD SP {R13 R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器
  35. ADD SP SP #(2 * 4) @定位到保存旧PC值的位置
  36. LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行
  37. @恢复任务上下文(TaskContext) 结束

具体也可看这两篇:

v42.xx (中断切换篇) | 系统因中断活力四射

v41.xx (任务切换篇) | 看汇编如何切换任务

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 百篇博客分析OpenHarmony源码 | v49.04的更多相关文章

  1. 鸿蒙内核源码分析(信号生产篇) | 信号安装和发送过程是怎样的? | 百篇博客分析OpenHarmony源码 | v48.03

    百篇博客系列篇.本篇为: v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管 ...

  2. 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01

    子曰:"我非生而知之者,好古,敏以求之者也." <论语>:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 ...

  3. 鸿蒙内核源码分析(进程回收篇) | 老父亲如何向老祖宗临终托孤 ? | 百篇博客分析OpenHarmony源码 | v47.01

    百篇博客系列篇.本篇为: v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管 ...

  4. 鸿蒙内核源码分析(特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 | 百篇博客分析OpenHarmony源码 | v46.02

    百篇博客系列篇.本篇为: v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁 ...

  5. 鸿蒙内核源码分析(fork篇) | 一次调用,两次返回 | 百篇博客分析OpenHarmony源码 | v45.03

    百篇博客系列篇.本篇为: v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内 ...

  6. 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 百篇博客分析OpenHarmony源码 | v28.03

    百篇博客系列篇.本篇为: v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当 ...

  7. 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 百篇博客分析OpenHarmony源码 | v24.01

    百篇博客系列篇.本篇为: v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内 ...

  8. 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 百篇博客分析OpenHarmonyOS | v2.07

    百篇博客系列篇.本篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核 ...

  9. 鸿蒙源码分析系列(总目录) | 百万汉字注解 百篇博客分析 | 深入挖透OpenHarmony源码 | v8.23

    百篇博客系列篇.本篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o 百篇博客.往期回顾 在给OpenHarmony内核源码加注过程中,整理出以下 ...

随机推荐

  1. SpringBoot - 集成RocketMQ实现延迟消息队列

    目录 前言 环境 具体实现 前言 RocketMQ是阿里巴巴在2012年开源的分布式消息中间件,记录下SpringBoot整合RocketMQ的方式,RocketMQ的安装可以查看:Windows下安 ...

  2. Qt迭代器(Java类型和STL类型)详解

    迭代器为访问容器类里的数据项提供了统一的方法,Qt 有两种迭代器类:Java 类型的迭代器和 STL 类型的迭代器. 两者比较,Java 类型的迭代器更易于使用,且提供一些高级功能,而 STL 类型的 ...

  3. WPF Grid新增框选功能

    有时候会有框选的需求,类似EXCEL一样,画一个框选择里面的子控件. 选择后比如可以将子控件的Border设置为红色边框 说下这个功能的大致原理.背景是一个Grid,比如里面放了很多的Button.  ...

  4. 九:Decorator设计模式

    二.使用Decorator设计模式增强request对象 Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrappe ...

  5. tensorflow实现Word2vec

    # coding: utf-8 ''' Note: Step 3 is missing. That's why I left it. ''' from __future__ import absolu ...

  6. 阿里云服务器上部署java项目(安装mysql)

    安装mysql步骤如下: 1.安装MySQL官方的yum repository: wget -i -c http://dev.mysql.com/get/mysql57-community-relea ...

  7. 三大操作系统对比使用之·Ubuntu16.04

    时间:2018-11-13 整理:byzqy 本篇是一篇个人对 Ubuntu 16.04(桌面版)使用方法.技巧以及应用推荐的文章,以便查询和分享! 打开终端: Ctrl+Alt+T,即可打开&quo ...

  8. MySQL-SQL基础

    mysql> use test; Database changed mysql> create table emp(ename varchar(10),hirdate date,sal d ...

  9. Android中TextView和EditView常用属性设置

    Android中TextView和EditView常用属性设置 点击跳转

  10. Java编程:为什么Class实例可以不是全局唯一

    通过定义两个类加载器加载同一字节码文件来证明Class实例为什么不是全局唯一的 1.将一个名为Demo(没有后缀)的字节码文件放在D盘根目录 2.定义两个类加载器 自定义ClassLoader三要素: ...