百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中
本篇说清楚进程
读本篇之前建议先读鸿蒙内核源码分析(总目录)调度故事篇,其中有对进程生活场景式的比喻.
官方基本概念从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。
鸿蒙内核的进程模块可以给用户提供多个进程,实现了进程之间的切换和通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。
鸿蒙内核中的进程采用抢占式调度机制,支持时间片轮转调度方式和FIFO调度机制。
鸿蒙内核的进程一共有32个优先级(0-31),用户进程可配置的优先级有22个(10-31),最高优先级为10,最低优先级为31。
高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。
每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离。
官方概念解读
官方文档最重要的一句话是进程是资源管理单元,注意是管理资源的, 资源是什么? 内存,任务,文件,信号量等等都是资源.故事篇中对进程做了一个形象的比喻(导演),负责节目(任务)的演出,负责协调节目运行时所需的各种资源.让节目能高效顺利的完成.
鸿蒙内核源码分析定位为深挖内核地基,构筑底层网图.就要解剖真身.进程(LosProcessCB)原始真身如下,本篇一一剖析它,看看它到底长啥样.
ProcessCB真身typedef struct ProcessCB {
CHAR processName[OS_PCB_NAME_LEN]; /**
UINT32 processID; /**
UINT16 processStatus; /**
running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞!
UINT16 priority; /**
UINT16 policy; /**
UINT16 timeSlice; /**
UINT16 consoleID; /**
UINT16 processMode; /**
UINT32 parentProcessID; /**
UINT32 exitCode; /**
LOS_DL_LIST pendList; /**
LOS_DL_LIST childrenList; /**
LOS_DL_LIST exitChildList; /**
LOS_DL_LIST siblingList; /**
ProcessGroup *group; /**
LOS_DL_LIST subordinateGroupList; /**
UINT32 threadGroupID; /**
UINT32 threadScheduleMap; /**
process */ //进程的各线程调度位图
LOS_DL_LIST threadSiblingList; /**
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**
priority hash table */ //进程的线程组调度优先级哈希表
volatile UINT32 threadNumber; /**
UINT32 threadCount; /**
LOS_DL_LIST waitList; /**
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**
#endif
UINTPTR sigHandler; /**
sigset_t sigShare; /**
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**
#endif
LosVmSpace *vmSpace; /**
#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**
#endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
timer_t timerID; /**
#ifdef LOSCFG_SECURITY_CAPABILITY //安全能力
User *user; //进程的拥有者
UINT32 capability; //安全能力范围 对应 CAP_SETGID
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
#endif
#ifdef LOSCFG_DRIVERS_TZDRIVER
struct file *execFile; /**
#endif
mode_t umask;
} LosProcessCB;
结构体还是比较复杂,虽一一都做了注解,但还是不够清晰,没有模块化.这里把它分解成以下六大块逐一分析:
第一大块:和任务(线程)关系UINT32 threadGroupID; /**
UINT32 threadScheduleMap; /**
process */ //进程的各线程调度位图
LOS_DL_LIST threadSiblingList; /**
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**
priority hash table */ //进程的线程组调度优先级哈希表
volatile UINT32 threadNumber; /**
UINT32 threadCount; /**
LOS_DL_LIST waitList; /**
进程和线程的关系是1:N的关系,进程可以有多个任务但一个任务不能同属于多个进程. 任务就是线程,是CPU的调度单元.线程的概念在鸿蒙内核源码分析(总目录)中的线程篇中有详细的介绍,可自行翻看. 任务是作为一种资源被进程管理的,进程为任务提供内存支持,提供文件支持,提供设备支持.
进程怎么管理线程的,进程怎么同步线程的状态?1.进程加载时会找到main函数创建第一个线程,一般为主线程,main函数就是入口函数,一切从哪里开始.
2.执行过程中根据代码(以java举例 如遇到 new thread )创建新的线程,其本质和main函数创建的线程没有区别,只是入口函数变成了run(),统一参与调度.
3.线程和线程的关系可以是独立(detached)的,也可以是联结(join)的.联结指的是一个线程可以操作另一个线程(包括回收资源,被对方干掉).
4.进程的主线程或所有线程运行结束后,进程转为僵尸态,一般只能由所有线程结束后,进程才能自然消亡.
5.进程创建后进入就绪态,发生进程切换时,就绪列表中最高优先级的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态,则该进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共存。这里要注意的是进程可以允许多种状态并存! 状态并存很自然的会想到位图管理,系列篇中有对位图详细的介绍.
6.进程内所有的线程均处于阻塞态时,进程在最后一个线程转为阻塞态时,同步进入阻塞态,然后发生进程切换。
7.阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态
8.进程内的最后一个就绪态线程处于阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。
9.进程由运行态转为就绪态的情况有以下两种:有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
若进程的调度策略为SCHED_RR(抢占式),且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
第二大块:和其他进程的关系CHAR processName[OS_PCB_NAME_LEN]; /**
UINT32 processID; /**
UINT16 processStatus; /**
running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞!
UINT16 priority; /**
UINT16 policy; /**
UINT16 timeSlice; /**
UINT16 consoleID; /**
UINT16 processMode; /**
UINT32 parentProcessID; /**
UINT32 exitCode; /**
LOS_DL_LIST pendList; /**
LOS_DL_LIST childrenList; /**
LOS_DL_LIST exitChildList; /**
LOS_DL_LIST siblingList; /**
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**
#endif
进程是家族式管理的,内核态进程和用户态进程分别有自己的根祖先,祖先进程在内核初始化时就创建好了,分别是1号(用户进程祖先)和2号(内核进程祖先)进程.进程刚生下来就确定了自己的基因,基因决定了你的权限不同, 父亲是谁,兄弟姐妹都有谁都已经安排好了,跟人一样,没法选择出生. 但进程可以有自己的子子孙孙, 从你这一脉繁衍下来的,这很像人类的传承方式.最终会形成树状结构,每个进程都能找到自己的位置.进程的管理遵循以下几点原则:1.进程退出时会主动释放持有的进程资源,但持有的进程pid资源需要父进程通过wait/waitpid或父进程退出时回收.
2.一个子进程的消亡要通知父进程,以便父进程在族谱上抹掉它的痕迹,一些异常情况下的坏孩子进程消亡没有告知父进程的,系统也会有定时任务能检测到而回收其资源.
3.进程创建后,只能操作自己进程空间的资源,无法操作其它进程的资源(共享资源除外).
4.进程间有多种通讯方式,事件,信号,消息队列,管道等等, liteipc是进程间基于文件的一种通讯方式,它的特点是传递的信息量可以很大.
5.高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。
第三大块:进程的五种状态初始化(Init):该进程正在被创建。
就绪(Ready):该进程在就绪列表中,等待CPU调度。
运行(Running):该进程正在运行。
阻塞(Pend):该进程被阻塞挂起。本进程内所有的线程均被阻塞时,进程被阻塞挂起。
僵尸态(Zombies):该进程运行结束,等待父进程回收其控制块资源。
第四大块:和内存的关系LosVmSpace *vmSpace; /**
进程与内存有关的就只有LosVmSpace一个成员变量,叫是进程空间,每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离,独立进程空间意味着每个进程都要将自己的虚拟内存和物理内存进行映射.并将映射区保存在自己的进程空间.另外进程的代码区,数据区,堆栈区,映射区都存放在自己的空间中,但内核态进程的空间是共用的,只需一次映射.
具体的进入鸿蒙内核源码分析(总目录)查看内存篇.详细介绍了虚拟内存,物理内存,线性地址,映射关系,共享内存,分配回收,页面置换的概念和实现.
第五大块:和文件的关系#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**
#endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
进程与文件系统有关的就只有files_struct,可理解为进程的文件管理器,文件也是很复杂的一大块, 后续有系列篇来讲解文件系统的实现. 理解文件系统的主脉络是:1.一个真实的物理文件(inode),可以同时被多个进程打开,并有进程独立的文件描述符, 进程文件描述符(ProcessFD)后边映射的是系统文件描述符(SystemFD).
2.系统文件描述符(0-stdin,1-stdout,2-stderr)默认被内核占用,任何进程的文件描述符前三个都是(stdin,stdout,stderr),默认已经打开,可以直接往里面读写数据.
3.文件映射跟内存映射一样,每个进程都需要单独对同一个文件进行映射,page_mapping记录了映射关系,而页高速缓存(page cache)提供了文件实际内存存放位置.
4.内存
第六大块:辅助工具#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**
#endif
#ifdef LOSCFG_SECURITY_CAPABILITY //安全能力
User *user; //进程的拥有者
UINT32 capability; //安全能力范围 对应 CAP_SETGID
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
#endif
#ifdef LOSCFG_DRIVERS_TZDRIVER
struct file *execFile; /**
#endif
其余是一些安全性,统计性的能力.
以上就是进程的全貌,看清楚它鸿蒙内核的影像会清晰很多!
鸿蒙源码百篇博客 往期回顾
参与贡献
喜欢请大方 点赞+关注+收藏 吧