将 HarmonyOS | 鸿蒙 研究到底 < 国内 | 国外 >
百篇博客系列篇.本篇为:
本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下:
本篇有相当的难度,涉及用户栈和内核栈的两轮切换,CPU四次换栈,寄存器改值,将围绕下图来说明.
解读
sig_switch_context
,为何已经保存了一次还要再保存一次?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;
__NR_sigreturn
,这也是个系统调用.回来后还原sig_switch_context
,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行.OsArmA32SyscallHandle
,OsSaveSignalContext
,OsRestorSignalContext
本篇一一解读,彻底挖透.先看信号上下文结构体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
代表用户栈指针和返回地址.CPSR
寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看usr
普通用户)和内核态(sys
超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行.R7
是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数.count
记录是否保存了信号上下文/* 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;//返回寄存器的值
}
解读
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;
}
有了上面的铺垫,就不难理解这个函数的作用.
/**********************************************
产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息
改写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
_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
/****************************************************
恢复信号上下文,由系统调用之__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[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 (中断切换篇) | 系统因中断活力四射
v41.xx (任务切换篇) | 看汇编如何切换任务
在加注过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆.说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.
与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,但会反复修正,持续更新,.xx
代表修改的次数,精雕细琢,言简意赅,力求打造精品内容.
v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51 .c .h .o
v53.xx 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 51 .c .h .o
v52.xx 鸿蒙内核源码分析(静态站点篇) | 五一哪也没去就干了这事 | 51 .c .h .o
v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o
v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙看这篇或许真的够了 | 51 .c .h .o
v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o
v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o
v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o
v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51 .c .h .o
v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o
v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51 .c .h .o
v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51 .c .h .o
v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51 .c .h .o
v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51 .c .h .o
v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51 .c .h .o
v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51 .c .h .o
v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51 .c .h .o
v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51 .c .h .o
v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51 .c .h .o
v35.xx 鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位 | 51 .c .h .o
v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51 .c .h .o
v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51 .c .h .o
v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51 .c .h .o
v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51 .c .h .o
v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51 .c .h .o
v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51 .c .h .o
v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51 .c .h .o
v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51 .c .h .o
v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞节牌坊 | 51 .c .h .o
v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51 .c .h .o
v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o
v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o
v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o
v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51 .c .h .o
v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地由谁提供 | 51 .c .h .o
v19.xx 鸿蒙内核源码分析(位图管理篇) | 谁能一分钱分两半花 | 51 .c .h .o
v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51 .c .h .o
v17.xx 鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存 | 51 .c .h .o
v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51 .c .h .o
v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51 .c .h .o
v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51 .c .h .o
v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51 .c .h .o
v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51 .c .h .o
v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51 .c .h .o
v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51 .c .h .o
v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事说内核调度过程 | 51 .c .h .o
v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o
v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51 .c .h .o
v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51 .c .h .o
v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51 .c .h .o
v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51 .c .h .o
v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51 .c .h .o
v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o
v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o
看系列篇文章会常看到 51 .c .h .o
,希望这对大家阅读不会造成影响.
分别对应以下四个站点的首个字符,感谢这些站点一直以来对系列篇的支持和推荐,尤其是 oschina gitee ,很喜欢它的界面风格,简洁大方,让人感觉到开源的伟大!
而巧合的是.c .h .o
是C语言的头/源/目标文件,这就很有意思了,冥冥之中似有天数,将这四个宝贝以这种方式融合在一起. 51 .c .h .o
, 我要CHO ,嗯嗯,hin 顺口 : )
百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc >
热爱是所有的理由和答案 - turing
原创不易,欢迎转载,但麻烦请注明出处.