| 百篇博客分析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