1 百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格。..
1 2 百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony >
进程关系链 1 进程是家族式管理的,父子关系,兄弟关系,朋友关系,子女关系,甚至陌生人关系(等待你消亡)在一个进程的生命周期中都会记录下来。用什么来记录呢?当然是内核最重要的胶水结构体 `LOS_DL_LIST` ,进程控制块(以下简称 `PCB` )用了8个双向链表来记录进程家族的基因关系和运行时关系.如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 typedef struct ProcessCB { LOS_DL_LIST pendList; LOS_DL_LIST childrenList; LOS_DL_LIST exitChildList; LOS_DL_LIST siblingList; LOS_DL_LIST subordinateGroupList; LOS_DL_LIST threadSiblingList; LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; LOS_DL_LIST waitList; } LosProcessCB; `解读```text pendList ``` 个人认为它是鸿蒙内核功能最多的一个链表,它远不止字面意思阻塞链表这么简单,只有深入解读源码后才能体会它真的是太会来事了,一般把它理解为阻塞链表就行.上面挂的是处于阻塞状态的进程. `childrenList` 孩子链表,所有由它fork出来的进程都挂到这个链表上.上面的孩子进程在死亡前会将自己从上面摘出去,转而挂到 ```text exitChildList `链表上.```text ``` 退出孩子链表,进入死亡程序的进程要挂到这个链表上,一个进程的死亡是件挺麻烦的事,进程池的数量有限,需要及时回收进程资源,但家族管理关系复杂,要去很多地方消除痕迹.尤其还有其他进程在看你笑话,等你死亡( ```text wait `/```text waitpid `)了通知它们一声.```text siblingList `兄弟链表,和你同一个父亲的进程都挂到了这个链表上.```text subordinateGroupList ``` 朋友圈链表,里面是因为兴趣爱好(进程组)而挂在一起的进程,它们可以不是一个父亲,不是一个祖父,但一定是同一个老祖宗(用户态和内核态根进程). `threadSiblingList` 线程链表,上面挂的是进程ID都是这个进程的线程(任务),进程和线程的关系是1 :N的关系,一个线程只能属于一个进程.这里要注意任务在其生命周期中是不能改所属进程的. `threadPriQueueList` 线程的调度队列数组,一共32 个,任务和进程一样有32 个优先级,调度算法的过程是先找到优先级最高的进程,在从该进程的任务队列里去最高的优先级任务运行. ```text waitList `是等待子进程消亡的任务链表,注意上面挂的是任务.任务是通过系统调用```java pid_t wait (int *status) ; pid_t waitpid (pid_t pid, int *status, int options) ; `将任务挂到```text `上.鸿蒙waitpid系统调用为```text SysWait ``` ,稍后会讲. ##### 进程正常死亡过程 一个进程的自然消亡过程如下
//一个进程的自然消亡过程,参数是当前运行的任务 STATIC VOID OsProcessNaturalExit(LosTaskCB *runTask, UINT32 status)
LosProcessCB *processCB = OS_PCB_FROM_PID(runTask->processID);//通过task找到所属PCB
LosProcessCB *parentCB = NULL;
LOS_ASSERT(!(processCB->threadScheduleMap != 0));//断言没有任务需要调度了,当前task是最后一个了
LOS_ASSERT(processCB->processStatus & OS_PROCESS_STATUS_RUNNING);//断言必须为正在运行的进程
OsChildProcessResourcesFree(processCB);//释放孩子进程的资源
1 2 #ifdef LOSCFG_KERNEL_CPUP
OsCpupClean(processCB->processID);
/* is a child process */
if (processCB->parentProcessID != OS_INVALID_VALUE) {//判断是否有父进程
parentCB = OS_PCB_FROM_PID(processCB->parentProcessID);//获取父进程实体
LOS_ListDelete(&processCB->siblingList);//将自己从兄弟链表中摘除,家人们,永别了!
if (!OsProcessExitCodeSignalIsSet(processCB)) {//是否设置了退出码?
OsProcessExitCodeSet(processCB, status);//将进程状态设为退出码
LOS_ListTailInsert(&parentCB->exitChildList, &processCB->siblingList);//挂到父进程的孩子消亡链表,家人中,永别的可不止我一个.
LOS_ListDelete(&processCB->subordinateGroupList);//和志同道合的朋友们永别了,注意家里可不一定是朋友的,所有各有链表.
LOS_ListTailInsert(&processCB->group->exitProcessList, &processCB->subordinateGroupList);//挂到进程组消亡链表,朋友中,永别的可不止我一个.
OsWaitCheckAndWakeParentProcess(parentCB, processCB);//检查父进程的等待任务并唤醒任务,此处将会切换到其他任务运行.
OsDealAliveChildProcess(processCB);//老父亲临终向各自的祖宗托孤
processCB->processStatus |= OS_PROCESS_STATUS_ZOMBIES;//贴上僵死进程的标签
(VOID)OsKill(processCB->parentProcessID, SIGCHLD, OS_KERNEL_KILL_PERMISSION);//以内核权限发送SIGCHLD(子进程退出)信号.
LOS_ListHeadInsert(&g_processRecyleList, &processCB->pendList);//将进程通过其阻塞节点挂入全局进程回收链表
OsRunTaskToDelete(runTask);//删除正在运行的任务
return;
LOS_Panic("pid : %u is the root process exit!\n", processCB->processID);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 } ``` 解读 退群,向兄弟姐妹 `siblingList` 告别,向朋友圈(进程组)告别 `subordinateGroupList` . 留下你的死亡记录,老父亲记录到 `exitChildList` ,朋友圈记录到 `exitProcessList` 中. 告诉后人死亡原因 `OsProcessExitCodeSet` ,因为 `waitList` 上挂的任务在等待你的死亡信息. 向老祖宗托孤,用户态和内核态进程都有自己的祖宗进程(1和2号进程),老祖宗身子硬朗,最后死。所有的短命鬼进程都可以把自己的孩子委托给老祖宗照顾,老祖宗会一视同仁. 将自己变成了 `OS_PROCESS_STATUS_ZOMBIES` 僵尸进程. 老父亲跑到村口广播这个孩子已经死亡的信号 `OsKill` . 将自己挂入进程回收链表,等待回收任务 `ResourcesTask` 回收资源. 最后删除这个正在运行的任务,很明显其中一定会发生一次调度 `OsSchedResched` . ```java //删除一个正在运行的任务 LITE_OS_SEC_TEXT VOID OsRunTaskToDelete(LosTaskCB *taskCB) { LosProcessCB *processCB = OS_PCB_FROM_PID(taskCB->processID);//拿到task所属进程 OsTaskReleaseHoldLock(processCB, taskCB);//task还锁 OsTaskStatusUnusedSet(taskCB);//task重置为未使用状态,等待回收 LOS_ListDelete(&taskCB->threadList);//从进程的线程链表中将自己摘除 processCB->threadNumber--;//进程的活动task --,注意进程还有一个记录总task的变量 processCB->threadCount LOS_ListTailInsert(&g_taskRecyleList, &taskCB->pendList);//将task插入回收链表,等待回收资源再利用 OsEventWriteUnsafe(&g_resourceEvent, OS_RESOURCE_EVENT_FREE, FALSE, NULL);//发送释放资源的事件,事件由 OsResourceRecoveryTask 消费 OsSchedResched();//申请调度 } ``` 但这是一个自然死亡的进程,还有很多非正常死亡在其他篇幅中已有说明.请自行翻看.非正常死亡的会产生僵尸进程.这种进程需要别的进程通过 `waitpid` 来回收. ##### 孤儿进程
一般情况下往往是白发人送黑发人,子进程的生命周期是要短于父进程。但因为fork之后,进程之间相互独立,调度算法一视同仁,父子之间是弱的关系力,就什么情况都可能发生了。内核是允许老父亲先走的,如果父进程退出而它的一个或多个子进程还在运行,那么这些子进程就被称为孤儿进程,孤儿进程最终将被两位老祖宗(用户态和内核态)所收养,并由老祖宗完成对它们的状态收集工作。 //当一个进程自然退出的时候,它的孩子进程由两位老祖宗收养 STATIC VOID OsDealAliveChildProcess(LosProcessCB *processCB)
UINT32 parentID;
LosProcessCB *childCB = NULL;
LOS_DL_LIST *nextList = NULL;
LOS_DL_LIST *childHead = NULL;
if (!LOS_ListEmpty(&processCB->childrenList)) {//如果存在孩子进程
childHead = processCB->childrenList.pstNext;//获取孩子链表
LOS_ListDelete(&(processCB->childrenList));//清空自己的孩子链表
if (OsProcessIsUserMode(processCB)) {//是用户态进程
parentID = g_userInitProcess;//用户态进程老祖宗
} else {
parentID = g_kernelInitProcess;//内核态进程老祖宗
for (nextList = childHead; ;) {//遍历孩子链表
childCB = OS_PCB_FROM_SIBLIST(nextList);//找到孩子的真身
childCB->parentProcessID = parentID;//孩磕认老祖宗为爸爸
nextList = nextList->pstNext;//找下一个孩子进程
if (nextList == childHead) {//一圈下来,孩子们都磕完头了
break;
parentCB = OS_PCB_FROM_PID(parentID);//找个老祖宗的真身
LOS_ListTailInsertList(&parentCB->childrenList, childHead);//挂到老祖宗的孩子链表上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 } } ``` 解读 函数很简单,都一一注释了,老父亲临终托付后事,请各自的老祖宗照顾孩子. 从这里也可以看出进程的家族管理模式,两个家族从进程的出生到死亡负责到底. ##### 僵尸进程 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 `PCB` 还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。 如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程,即 Z 进程.任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了. 不正常情况下就需要手动 `waitpid` 清理了. ##### waitpid 在鸿蒙系统中,一个进程结束了,但是它的父进程没有等待(调用 `wait` `waitpid` )它,那么它将变成一个僵尸进程。通过系统调用 `waitpid` 可以彻底的清理掉子进程.归还 `pcb` .最终调用到 `SysWait` #include <sys/wait.h> #include "syscall.h" pid_t waitpid(pid_t pid, int *status, int options) { return syscall_cp(SYS_wait4, pid, status, options, 0); }
1 2 //等待子进程结束 int SysWait(int pid, USER int *status, int options, void *rusage)
{
1 return LOS_Wait(pid, status, (unsigned int)options, NULL);
}
1 2 //返回已经终止的子进程的进程ID号,并清除僵死进程。 LITE_OS_SEC_TEXT INT32 LOS_Wait(INT32 pid, USER INT32 *status, UINT32 options, VOID *rusage)
{
1 2 3 4 5 (VOID)rusage; UINT32 ret; UINT32 intSave; LosProcessCB *processCB = NULL; LosTaskCB *runTask = NULL;
1 ret = OsWaitOptionsCheck(options);//参数检查,只支持LOS_WAIT_WNOHANG
1 2 if (ret != LOS_OK) { return -ret;
}
1 2 3 SCHEDULER_LOCK(intSave); processCB = OsCurrProcessGet(); //获取当前进程 runTask = OsCurrTaskGet(); //获取当前任务
1 2 3 ret = OsWaitChildProcessCheck(processCB, pid, &childCB);//先检查下看能不能找到参数要求的退出子进程 pid = -ret; goto ERROR;
}
1 2 if (childCB != NULL) {//找到了进程 return OsWaitRecycleChildPorcess(childCB, intSave, status);//回收进程
}
1 2 3 //没有找到,看是否要返回还是去做个登记 if ((options & LOS_WAIT_WNOHANG) != 0) {//有LOS_WAIT_WNOHANG标签 runTask->waitFlag = 0;//等待标识置0
1 pid = 0;//这里置0,是为了 return 0
}
1 2 3 4 //等待孩子进程退出 OsWaitInsertWaitListInOrder(runTask, processCB);//将当前任务挂入进程waitList链表 //发起调度的目的是为了让出CPU,让其他进程/任务运行 OsSchedResched();//发起调度
1 2 3 runTask->waitFlag = 0; if (runTask->waitID == OS_INVALID_VALUE) { pid = -LOS_ECHILD;//没有此子进程
}
1 childCB = OS_PCB_FROM_PID(runTask->waitID);//获取当前任务的等待子进程ID
1 2 if (!(childCB->processStatus & OS_PROCESS_STATUS_ZOMBIES)) {//子进程非僵死进程 pid = -LOS_ESRCH;//没有此进程
}
1 2 //回收僵死进 return OsWaitRecycleChildPorcess(childCB, intSave, status);
ERROR:
1 2 SCHEDULER_UNLOCK(intSave); return pid;
}
pid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 参数值 说明 pid<-1 等待进程组号为pid绝对值的任何子进程。 pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。 pid=0 等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。 pid>0 等待进程号为pid的子进程。 `pid` 不同值代表的真正含义可以看这个函数 `OsWaitSetFlag` . ```java //设置等待子进程退出方式方法 STATIC UINT32 OsWaitSetFlag(const LosProcessCB *processCB, INT32 pid, LosProcessCB **child) { ProcessGroup *group = NULL; LosTaskCB *runTask = OsCurrTaskGet(); if (pid > 0) {//等待进程号为pid的子进程结束 /* Wait for the child process whose process number is pid. */ childCB = OsFindExitChildProcess(processCB, pid);//看能否从退出的孩子链表中找到PID if (childCB != NULL) {//找到了,确实有一个已经退出的PID,注意一个进程退出时会挂到父进程的exitChildList上 goto WAIT_BACK;//直接成功返回 } ret = OsFindChildProcess(processCB, pid);//看能否从现有的孩子链表中找到PID return LOS_ECHILD;//参数进程并没有这个PID孩子,返回孩子进程失败. } runTask->waitFlag = OS_PROCESS_WAIT_PRO;//设置当前任务的等待类型 runTask->waitID = pid; //当前任务要等待进程ID结束 } else if (pid == 0) {//等待同一进程组中的任何子进程 /* Wait for any child process in the same process group */ childCB = OsFindGroupExitProcess(processCB->group, OS_INVALID_VALUE);//看能否从退出的孩子链表中找到PID if (childCB != NULL) {//找到了,确实有一个已经退出的PID } runTask->waitID = processCB->group->groupID;//等待进程组的任意一个子进程结束 runTask->waitFlag = OS_PROCESS_WAIT_GID;//设置当前任务的等待类型 } else if (pid == -1) {//等待任意子进程 /* Wait for any child process */ childCB = OsFindExitChildProcess(processCB, OS_INVALID_VALUE);//看能否从退出的孩子链表中找到PID goto WAIT_BACK; } runTask->waitID = pid;//等待PID,这个PID可以和当前进程没有任何关系 runTask->waitFlag = OS_PROCESS_WAIT_ANY;//设置当前任务的等待类型 } else { /* pid < -1 */ //等待指定进程组内为|pid|的所有子进程 /* Wait for any child process whose group number is the pid absolute value. */ group = OsFindProcessGroup(-pid);//先通过PID找到进程组 if (group == NULL) { return LOS_ECHILD; } childCB = OsFindGroupExitProcess(group, OS_INVALID_VALUE);//在进程组里任意一个已经退出的子进程 if (childCB != NULL) { } runTask->waitID = -pid;//此处用负数是为了和(pid == 0)以示区别,因为二者的waitFlag都一样. } WAIT_BACK: *child = childCB; return LOS_OK; }
status 带走进程退出码,
exitCode 分成了三个部分格式如下
text 0 - 7 `为信号位,信号处理有专门的篇幅,此处不做详细介绍,请自行翻看,这里仅列出部分信号含义.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 `options` 是行为参数,提供了一些另外的选项来控制waitpid()函数的行为。 参数值 鸿蒙支持 说明 LOS_WAIT_WNOHANG 支持 如果没有孩子进程退出,则立即返回,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。 LOS_WAIT_WUNTRACED 不支持 报告终止或停止的子进程的状态 LOS_WAIT_WCONTINUED 不支持 鸿蒙目前只支持了LOS_WAIT_WNOHANG模式,内核源码中虽有 `LOS_WAIT_WUNTRACED` 和 `LOS_WAIT_WCONTINUED` ``` 的实现痕迹,但是整体阅读下来比较乱,应该是没有写好. ##### 参与贡献  - 进程在临终前如何向老祖宗托孤 - 百篇博客分析HarmonyOS源码 - v47.02)
Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request
1 2 3 4 新建 Issue 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处.
进入 >> oschina | csdn | 51cto | 简书 | 掘金 | harmony
本文标题: 鸿蒙内核源码分析
发布时间: 2025年01月20日 00:00
最后更新: 2025年12月30日 08:54
原始链接: https://haoxiang.eu.org/84078d5b/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!