首页 文章 精选 留言 我的

精选列表

搜索[harmonyos开发],共10000篇文章
优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v24.01

鸿蒙内核源码注释仓库 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新 < Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 < CSDN | 开源中国 | WeHarmony > 本篇说清楚进程. 读本篇之前建议先读 鸿蒙内核源码分析(总目录) | 精读内核源码 深挖地基工程 < CSDN | 开源中国 | WeHarmony > 调度故事篇,其中有对进程生活场景式的比喻. 官方基本概念 从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。 鸿蒙内核的进程模块可以给用户提供多个进程,实现了进程之间的切换和通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。 鸿蒙内核中的进程采用抢占式调度机制,支持时间片轮转调度方式和FIFO调度机制。 鸿蒙内核的进程一共有32个优先级(0-31),用户进程可配置的优先级有22个(10-31),最高优先级为10,最低优先级为31。 高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。 每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离。 官方概念解读 官方文档最重要的一句话是进程是资源管理单元,注意是管理资源的, 资源是什么? 内存,任务,文件,信号量等等都是资源.故事篇中对进程做了一个形象的比喻(导演),负责节目(任务)的演出,负责协调节目运行时所需的各种资源.让节目能高效顺利的完成. 鸿蒙内核源码分析定位为深挖内核地基,构筑底层网图.就要解剖真身.进程(LosProcessCB)原始真身如下,本篇一一剖析它,看看它的五脏六腑里到底有啥. ProcessCB真身 typedef struct ProcessCB { CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */ //进程名称 UINT32 processID; /**< process ID = leader thread ID */ //进程ID,由进程池分配,范围[0,64] UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞! UINT16 priority; /**< process priority */ //进程优先级 UINT16 policy; /**< process policy */ //进程的调度方式,默认抢占式 UINT16 timeSlice; /**< Remaining time slice *///进程时间片,默认2个tick UINT16 consoleID; /**< The console id of task belongs *///任务的控制台id归属 UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程 UINT32 parentProcessID; /**< Parent process ID */ //父进程ID UINT32 exitCode; /**< process exit status */ //进程退出状态码 LOS_DL_LIST pendList; /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上 LOS_DL_LIST childrenList; /**< my children process list */ //孩子进程都挂到这里,形成双循环链表 LOS_DL_LIST exitChildList; /**< my exit children process list */ //那些要退出孩子进程挂到这里,白发人送黑发人。 LOS_DL_LIST siblingList; /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程. ProcessGroup *group; /**< Process group to which a process belongs */ //所属进程组 LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */ //进程是组长时,有哪些组员进程 UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */ //哪个线程组是进程的主线程ID UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the process */ //进程的各线程调度位图 LOS_DL_LIST threadSiblingList; /**< List of threads under this process *///进程的线程(任务)列表 LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the priority hash table */ //进程的线程组调度优先级哈希表 volatile UINT32 threadNumber; /**< Number of threads alive under this process */ //此进程下的活动线程数 UINT32 threadCount; /**< Total number of threads created under this process */ //在此进程下创建的线程总数 LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid #if (LOSCFG_KERNEL_SMP == YES) UINT32 timerCpu; /**< CPU core number of this task is delayed or pended *///统计各线程被延期或阻塞的时间 #endif UINTPTR sigHandler; /**< signal handler */ //信号处理函数,处理如 SIGSYS 等信号 sigset_t sigShare; /**< signal share bit */ //信号共享位 #if (LOSCFG_KERNEL_LITEIPC == YES) ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */ //用于进程间通讯的虚拟设备文件系统,设备装载点为 /dev/lite_ipc #endif LosVmSpace *vmSpace; /**< VMM space for processes */ //虚拟空间,描述进程虚拟内存的数据结构,linux称为内存描述符 #ifdef LOSCFG_FS_VFS struct files_struct *files; /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器 #endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作 timer_t timerID; /**< iTimer */ #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; /**< Exec bin of the process */ #endif mode_t umask; } LosProcessCB; 结构体还是比较复杂,虽一一都做了注解,但还是不够清晰,没有模块化.这里把它分解成以下六大块逐一分析: 第一大块:和任务(线程)关系 UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */ //哪个线程组是进程的主线程ID UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the process */ //进程的各线程调度位图 LOS_DL_LIST threadSiblingList; /**< List of threads under this process *///进程的线程(任务)列表 LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the priority hash table */ //进程的线程组调度优先级哈希表 volatile UINT32 threadNumber; /**< Number of threads alive under this process */ //此进程下的活动线程数 UINT32 threadCount; /**< Total number of threads created under this process */ //在此进程下创建的线程总数 LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid 进程和线程的关系是1:N的关系,可以有多个任务但一个任务不能同属于多个进程. 任务就是线程,是CPU的调度单元.任务是什么? 鸿蒙内核源码分析(总目录) | 精读内核源码 深挖地基工程 < CSDN | 开源中国 | WeHarmony > 中的线程概念篇中有详细的介绍,可自行翻看. 任务是作为一种资源被进程管理的,进程为任务提供内存支持,提供文件支持,提供设备支持. 进程怎么管理线程的,进程怎么同步线程的状态? 1.进程加载时会找到main函数创建第一个线程,一般为主线程,其第一条指令就是main的第一条指令,一切从哪里开始. 2.执行线程过程中根据代码创建新的线程,其本质和main函数创建的线程没有区别,统一参与调度. 3.线程和线程的关系可以是独立(detached)的,也可以是联结(join)的.联结指的是一个线程可以操作另一个线程(包括回收资源,被对方干掉). 4.进程的主线程或所有线程运行结束后,进程转为僵尸态,一般只能由所有线程结束后,进程才能自然消亡. 5.进程创建后进入就绪态,发生进程切换时,就绪列表中最高优先级的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态,则该进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共存。这里要注意的是进程可以允许多种状态并存! 状态并存很自然的会想到位图管理,系列篇中有对位图详细的介绍. 6.进程内所有的线程均处于阻塞态时,进程在最后一个线程转为阻塞态时,同步进入阻塞态,然后发生进程切换。 7.阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态 8.进程内的最后一个就绪态线程处于阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。 9.进程由运行态转为就绪态的情况有以下两种: 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。 若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。 第二大块:和其他进程的关系 CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */ //进程名称 UINT32 processID; /**< process ID = leader thread ID */ //进程ID,由进程池分配,范围[0,64] UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently running in the process *///这里设计很巧妙.用一个16表示了两层逻辑 数量和状态,点赞! UINT16 priority; /**< process priority */ //进程优先级 UINT16 policy; /**< process policy */ //进程的调度方式,默认抢占式 UINT16 timeSlice; /**< Remaining time slice *///进程时间片,默认2个tick UINT16 consoleID; /**< The console id of task belongs *///任务的控制台id归属 UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程 UINT32 parentProcessID; /**< Parent process ID */ //父进程ID UINT32 exitCode; /**< process exit status */ //进程退出状态码 LOS_DL_LIST pendList; /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上 LOS_DL_LIST childrenList; /**< my children process list */ //孩子进程都挂到这里,形成双循环链表 LOS_DL_LIST exitChildList; /**< my exit children process list */ //那些要退出孩子进程挂到这里,白发人送黑发人。 LOS_DL_LIST siblingList; /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程. #if (LOSCFG_KERNEL_LITEIPC == YES) ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */ //用于进程间通讯的虚拟设备文件系统,设备装载点为 /dev/lite_ipc #endif 进程是家族式管理的,内核进程和用户进程分别有自己的根祖先,祖先进程在内核初始化时就创建好了,分别是1号(用户进程祖先)和2号(内核进程祖先)进程.进程刚生下来就确定了自己的基因,父亲是谁,兄弟姐妹都有谁,当然你也自己发展自己的孩子进程,最终会形成树状结构,每个进程都能找到自己的位置.进程的管理遵循以下几点原则: 1.进程退出时会主动释放持有的进程资源,但持有的进程pid资源需要父进程通过wait/waitpid或父进程退出时回收. 2.一个子进程的消亡要通知父进程,以便父进程在族谱上抹掉它的痕迹,一些异常情况下的坏孩子进程消亡没有告知父进程的,系统也会检测到而回收其资源. 3.进程创建后,只能操作自己进程空间的资源,无法操作其它进程的资源(共享资源除外). 4.进程间有多种通讯方式,liteipc是进程间基于文件的通讯方式. 5.高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。 第三大块:进程的五种状态 初始化(Init):该进程正在被创建。 就绪(Ready):该进程在就绪列表中,等待CPU调度。 运行(Running):该进程正在运行。 阻塞(Pend):该进程被阻塞挂起。本进程内所有的线程均被阻塞时,进程被阻塞挂起。 僵尸态(Zombies):该进程运行结束,等待父进程回收其控制块资源。 第四大块:和内存的关系 LosVmSpace *vmSpace; /**< VMM space for processes */ //虚拟空间,描述进程虚拟内存的数据结构,linux称为内存描述符 进程与内存有关的就只有LosVmSpace一个,是进程空间,每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离,独立进程空间意味着每个进程都要将自己的虚拟内存和物理内存进行映射.并将映射区保存在自己的进程空间.代码区,数据区,堆栈区,映射区都存放在进程空间中,但内核态进程的空间是共用的,只需一次映射.具体的进入系列篇中 鸿蒙内核源码分析(总目录) | 精读内核源码 深挖地基工程 < CSDN | 开源中国 | WeHarmony > 查看内存篇.详细介绍了虚拟内存,物理内存,线性地址,映射关系,共享内存,分配回收,页面置换的概念和实现. 第五大块:和文件的关系 #ifdef LOSCFG_FS_VFS struct files_struct *files; /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器 #endif //每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作 进程与文件系统有关的就只有files_struct,可理解为进程的文件管理器,文件也是很复杂的一大块, 后续有系列篇来讲解文件系统的实现. 理解文件系统的主脉络是: 1.一个真实的物理文件(inode),可以同时被多个进程打开,并有进程独立的文件描述符, 进程文件描述符(ProcessFD)后边映射的是系统文件描述符(SystemFD). 2.系统文件描述符(0-stdin,1-stdout,2-stderr)默认被内核占用,任何进程的文件描述符前三个都是(stdin,stdout,stderr),默认已经打开,可以直接往里面读写数据. 3.文件映射跟内存映射一样,每个进程都需要单独对同一个文件进行映射,page_mapping记录了映射关系,而页高速缓存(page cache)提供了文件实际的存放在内存位置. 4.内存<->文件的置换以页为单位(4K),进程并不能对硬盘文件直接操作,必须通过页高速缓存(page cache)完成.其中会涉及到一些经典的概念比如COW(写时拷贝)技术.后续会详细说明. 第六大块:辅助工具 #if (LOSCFG_KERNEL_SMP == YES) UINT32 timerCpu; /**< CPU core number of this task is delayed or pended *///统计各线程被延期或阻塞的时间 #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; /**< Exec bin of the process */ #endif 其余是一些安全性,统计性的能力. 以上就是进程的五脏六腑,看清楚它鸿蒙内核的影像会清晰很多! 喜欢就请注入源动力吧 各大站点搜 "鸿蒙内核源码分析",快速找到组织.或者更简单的,如图: 鸿蒙内核源码注释仓库 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新 < Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 < CSDN | 开源中国 | WeHarmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v24.02

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

优秀的个人博客,低调大师

鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 中文注解HarmonyOS源码 | v39.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 系列篇ARM部分说明基于ARM720T.pdf文档. 为何要有异常接管? 拿小孩成长打比方,大人总希望孩子能健康成长,但在成长过程中总会遇到各种各样的问题,树欲静而风不止,成长路上有危险,有时是自己的问题有时是外在环境问题.就像抖音最近的流行口水歌一样,社会很单纯,复杂的是人啊,每次听到都想站起来扭几下.哎! 老衲到底做错什么了? 比如:老被小朋友欺负怎么弄? 发现乱花钱怎么搞? 青春期发育怎么应对? 失恋要跳楼又怎么办? 意思超过他的认知范围,就是靠它自己解决不了了,就需要有更高权限,更高智慧的人介入进来,帮着解决,干擦屁股的事. 那么应用程序就是那个小孩,内核就是监护人,有更高的权限,更高的智慧.而且监护人还不止一个,而是六个,每个监护人对应解决一种情况,情况发生了就由它来接管这件事的处理,小朋友你就别管了,先把你关家里,处理好了外面安全了再把应用程序放出来玩去. 这六个人处理问题都自带工具,有标准的解决方案,有自己独立的办公场所,办公场所就是栈空间(独立的),标准解决方案就是私有代码段,放在固定的位置.而自带的工具就是 SPSR_***,SP_***,LR_***寄存器组.详见 系列篇之工作模式篇 ,这里再简单回顾下有哪些工作模式,包括小孩自己(用户模式)一共是七种模式. 七种工作模式 图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,不能直接切换到其他模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):支持高速数据传输及通道处理,FIQ异常响应时进入此模式 外部中断模式(irq):用于通用中断处理,IRQ异常响应时进入此模式 管理模式(svc):操作系统保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发) 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务,与用户模式类似,但具有可以直接切换到其他模式等特权 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除用户模式外,其余6种工作模式都属于特权模式 特权模式中除了系统模式以外的其余5种模式称为异常模式 大多数程序运行于用户模式 进入特权模式是为了处理中断、异常、或者访问被保护的系统资源 硬件权限级别:系统模式 > 异常模式 > 用户模式 快中断(fiq)与慢中断(irq)区别:快中断处理时禁止中断 每种模式都有自己独立的入口和独立的运行栈空间. 系列篇之CPU篇 已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行场地问题. 而且在多核情况下,每个CPU核的每种特权模式都有自己独立的栈空间.注意是特权模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 官方概念 异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。 异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。 鸿蒙的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。 进入和退出异常方式 异常接管切换需要处理好两件事: 一个是代码要切到哪个位置,也就是要重置PC寄存器,每种异常模式下的切换方式如图: 另一个是要恢复每种模式的状态,即 CPSR(1个) 和 SPSR(共5个) 的关系,对M[4:0]的修改,如图: 以下是M[4:0]在每种模式下具体操作方式: 栈帧 每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长,也就是说栈底是高地址,栈顶是底地址. 详见 系列篇之用栈方式篇 以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。 堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。 图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。 解读 LR寄存器(Link Register),链接寄存器,指向函数的返回地址。 R11:可以用作通用寄存器,在开启特定编译选项时可以用作帧指针寄存器FP,用来实现栈回溯功能。 GNU编译器(gcc)默认将R11作为存储变量的通用寄存器,因而默认情况下无法使用FP的栈回溯功能。为支持调用栈解析功能,需要在编译参数中添加-fno-omit-frame-pointer选项,提示编译器将R11作为FP使用。 FP寄存器(Frame Point),帧指针寄存器,指向当前函数的父函数的栈帧起始地址。利用该寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。 当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR、FP寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。 六种异常模式实现代码 /* Define exception type ID */ //ARM处理器一共有7种工作模式,除了用户和系统模式其余都叫异常工作模式 #define OS_EXCEPT_RESET 0x00 //重置功能,例如:开机就进入CPSR_SVC_MODE模式 #define OS_EXCEPT_UNDEF_INSTR 0x01 //未定义的异常,就是others #define OS_EXCEPT_SWI 0x02 //软中断 #define OS_EXCEPT_PREFETCH_ABORT 0x03 //预取异常(取指异常), 指令三步骤: 取指,译码,执行, #define OS_EXCEPT_DATA_ABORT 0x04 //数据异常 #define OS_EXCEPT_FIQ 0x05 //快中断异常 #define OS_EXCEPT_ADDR_ABORT 0x06 //地址异常 #define OS_EXCEPT_IRQ 0x07 //普通中断异常 地址异常处理(Address abort) @ Description: Address abort exception handler _osExceptAddrAbortHdl: @地址异常处理 SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_ADDR_ABORT @ Set exception ID to OS_EXCEPT_ADDR_ABORT. B _osExceptDispatch @跳到异常分发统一处理 快中断处理(fiq) @ Description: Fast interrupt request exception handler _osExceptFiqHdl: @快中断异常处理 SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers. MOV R0, #OS_EXCEPT_FIQ @ Set exception ID to OS_EXCEPT_FIQ. B _osExceptDispatch @ Branch to global exception handler. 解读 快中断处理时需禁用普通中断 取指异常(Prefectch abort) @ Description: Prefectch abort exception handler _osExceptPrefetchAbortHdl: #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsPrefetchAbortExcHandleEntry #endif #else SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_PREFETCH_ABORT @ Set exception ID to OS_EXCEPT_PREFETCH_ABORT. AND R4, R1, #CPSR_MASK_MODE @ Interrupted mode CMP R4, #CPSR_USER_MODE @ User mode BEQ _osExcPageFault @ Branch if user mode _osKernelExceptPrefetchAbortHdl: MOV LR, R5 B _osExceptDispatch @ Branch to global exception handler. #endif 数据访问异常(Data abort) @ Description: Data abort exception handler _osExceptDataAbortHdl: @数据异常处理,缺页就属于数据异常 #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsDataAbortExcHandleEntry #endif #else SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_DATA_ABORT @ Set exception ID to OS_EXCEPT_DATA_ABORT. B _osExcPageFault @跳到缺页异常处理 #endif 软中断处理(swi) @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存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值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给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 切回用户模式运行 普通中断处理(irq) OsIrqHandler: @硬中断处理,此时已切换到硬中断栈 SUB LR, LR, #4 /* push r0-r3 to irq stack */ STMFD SP, {R0-R3} @r0-r3寄存器入 irq 栈 SUB R0, SP, #(4 * 4)@r0 = sp - 16 MRS R1, SPSR @获取程序状态控制寄存器 MOV R2, LR @r2=lr /* disable irq, switch to svc mode */@超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。 CPSID i, #0x13 @切换到SVC模式,此处一切换,后续指令将入SVC的栈 @CPSID i为关中断指令,对应的是CPSIE /* push spsr and pc in svc stack */ STMFD SP!, {R1, R2} @实际是将 SPSR,和LR入栈,入栈顺序为 R1,R2,SP自增 STMFD SP, {LR} @LR再入栈,SP不自增 AND R3, R1, #CPSR_MASK_MODE @获取CPU的运行模式 CMP R3, #CPSR_USER_MODE @中断是否发生在用户模式 BNE OsIrqFromKernel @中断不发生在用户模式下则跳转到OsIrqFromKernel /* push user sp, lr in svc stack */ STMFD SP, {R13, R14}^ @sp和LR入svc栈 解读 普通中断处理时可以响应快中断 未定义异常处理(undef) @ Description: Undefined instruction exception handler _osExceptUndefInstrHdl:@出现未定义的指令处理 #ifdef LOSCFG_GDB GDB_HANDLE OsUndefIncExcHandleEntry #else @ LR offset to return from this exception: 0. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_UNDEF_INSTR @ Set exception ID to OS_EXCEPT_UNDEF_INSTR. B _osExceptDispatch @ Branch to global exception handler. #endif 异常分发统一处理 _osExceptDispatch: @异常模式统一分发处理 MRS R2, SPSR @ Save CPSR before exception. MOV R1, LR @ Save PC before exception. SUB R3, SP, #(8 * 4) @ Save the start address of working registers. MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @ Switch to SVC mode, and disable all interrupts MOV R5, SP EXC_SP_SET __exc_stack_top, OS_EXC_STACK_SIZE, R6, R7 STMFD SP!, {R1} @ Push Exception PC STMFD SP!, {LR} @ Push SVC LR STMFD SP!, {R5} @ Push SVC SP STMFD SP!, {R8-R12} @ Push original R12-R8, LDMFD R3!, {R4-R11} @ Move original R7-R0 from exception stack to original stack. STMFD SP!, {R4-R11} STMFD SP!, {R2} @ Push task`s CPSR (i.e. exception SPSR). CMP R0, #OS_EXCEPT_DATA_ABORT @是数据异常吗? BNE 1f @不是跳到 锚点1处 MRC P15, 0, R8, C6, C0, 0 @R8=C6(内存失效的地址) 0(访问数据失效) MRC P15, 0, R9, C5, C0, 0 @R9=C5(内存失效的状态) 0(无效整个指令cache) B 3f @跳到锚点3处执行 1: CMP R0, #OS_EXCEPT_PREFETCH_ABORT @是预取异常吗? BNE 2f @不是跳到 锚点2处 MRC P15, 0, R8, C6, C0, 2 @R8=C6(内存失效的地址) 2(访问指令失效) MRC P15, 0, R9, C5, C0, 1 @R9=C5(内存失效的状态) 1(虚拟地址) B 3f @跳到锚点3处执行 2: MOV R8, #0 MOV R9, #0 3: AND R2, R2, #CPSR_MASK_MODE CMP R2, #CPSR_USER_MODE @ User mode BNE 4f @不是用户模式 STMFD SP, {R13, R14}^ @ save user mode sp and lr 4: SUB SP, SP, #(4 * 2) @sp=sp-(4*2) 非常重要的ARM37个寄存器 详见 系列篇之寄存器篇 结尾 以上为异常接管对应的代码处理,具体每种异常发生的场景和代码细节处理,因内容太多,太复杂,系列篇后续将分篇一一分析.敬请关注! 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 欢迎转载,请注明出处,公众号转载申请方式: 关注后直接回复您的公众号名称即可. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 中文注解HarmonyOS源码 | v39.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 系列篇ARM部分说明基于ARM720T.pdf文档. 为何要有异常接管? 拿小孩成长打比方,大人总希望孩子能健康成长,但在成长过程中总会遇到各种各样的问题,树欲静而风不止,成长路上有危险,有时是自己的问题有时是外在环境问题.就像抖音最近的流行口水歌一样,社会很单纯,复杂的是人啊,每次听到都想站起来扭几下.哎! 老衲到底做错什么了? 比如:老被小朋友欺负怎么弄? 发现乱花钱怎么搞? 青春期发育怎么应对? 失恋要跳楼又怎么办? 意思超过他的认知范围,就是靠它自己解决不了了,就需要有更高权限,更高智慧的人介入进来,帮着解决,干擦屁股的事. 那么应用程序就是那个小孩,内核就是监护人,有更高的权限,更高的智慧.而且监护人还不止一个,而是六个,每个监护人对应解决一种情况,情况发生了就由它来接管这件事的处理,小朋友你就别管了,先把你关家里,处理好了外面安全了再把应用程序放出来玩去. 这六个人处理问题都自带工具,有标准的解决方案,有自己独立的办公场所,办公场所就是栈空间(独立的),标准解决方案就是私有代码段,放在固定的位置.而自带的工具就是 SPSR_***,SP_***,LR_***寄存器组.详见 系列篇之工作模式篇 ,这里再简单回顾下有哪些工作模式,包括小孩自己(用户模式)一共是七种模式. 七种工作模式 图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 官方概念 异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。 异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。 鸿蒙的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。 进入和退出异常方式 异常接管切换需要处理好两件事: 一个是代码要切到哪个位置,也就是要重置PC寄存器,每种异常模式下的切换方式如图: 另一个是要恢复每种模式的状态,即 CPSR(1个) 和 SPSR(共5个) 的关系,对M[4:0]的修改,如图: 以下是M[4:0]在每种模式下具体操作方式: 栈帧 每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长,也就是说栈底是高地址,栈顶是底地址. 详见 系列篇之用栈方式篇 以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。 堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。 图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。 解读 LR寄存器(Link Register),链接寄存器,指向函数的返回地址。 R11:可以用作通用寄存器,在开启特定编译选项时可以用作帧指针寄存器FP,用来实现栈回溯功能。 GNU编译器(gcc)默认将R11作为存储变量的通用寄存器,因而默认情况下无法使用FP的栈回溯功能。为支持调用栈解析功能,需要在编译参数中添加-fno-omit-frame-pointer选项,提示编译器将R11作为FP使用。 FP寄存器(Frame Point),帧指针寄存器,指向当前函数的父函数的栈帧起始地址。利用该寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。 当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR、FP寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。 六种异常模式实现代码 地址异常处理(Address abort) @ Description: Address abort exception handler _osExceptAddrAbortHdl: @地址异常处理 SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_ADDR_ABORT @ Set exception ID to OS_EXCEPT_ADDR_ABORT. B _osExceptDispatch @跳到异常分发统一处理 快中断处理(fiq) @ Description: Fast interrupt request exception handler _osExceptFiqHdl: @快中断异常处理 SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers. MOV R0, #OS_EXCEPT_FIQ @ Set exception ID to OS_EXCEPT_FIQ. B _osExceptDispatch @ Branch to global exception handler. 取指异常(Prefectch abort) @ Description: Prefectch abort exception handler _osExceptPrefetchAbortHdl: #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsPrefetchAbortExcHandleEntry #endif #else SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_PREFETCH_ABORT @ Set exception ID to OS_EXCEPT_PREFETCH_ABORT. AND R4, R1, #CPSR_MASK_MODE @ Interrupted mode CMP R4, #CPSR_USER_MODE @ User mode BEQ _osExcPageFault @ Branch if user mode _osKernelExceptPrefetchAbortHdl: MOV LR, R5 B _osExceptDispatch @ Branch to global exception handler. #endif 数据访问异常(Data abort) @ Description: Data abort exception handler _osExceptDataAbortHdl: @数据异常处理,缺页就属于数据异常 #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsDataAbortExcHandleEntry #endif #else SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_DATA_ABORT @ Set exception ID to OS_EXCEPT_DATA_ABORT. B _osExcPageFault @跳到缺页异常处理 #endif 软中断处理(swi) @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存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值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给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 切回用户模式运行 硬中断处理(irq) OsIrqHandler: @硬中断处理,此时已切换到硬中断栈 SUB LR, LR, #4 /* push r0-r3 to irq stack */ STMFD SP, {R0-R3} @r0-r3寄存器入 irq 栈 SUB R0, SP, #(4 * 4)@r0 = sp - 16 MRS R1, SPSR @获取程序状态控制寄存器 MOV R2, LR @r2=lr /* disable irq, switch to svc mode */@超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。 CPSID i, #0x13 @切换到SVC模式,此处一切换,后续指令将入SVC的栈 @CPSID i为关中断指令,对应的是CPSIE /* push spsr and pc in svc stack */ STMFD SP!, {R1, R2} @实际是将 SPSR,和LR入栈,入栈顺序为 R1,R2,SP自增 STMFD SP, {LR} @LR再入栈,SP不自增 AND R3, R1, #CPSR_MASK_MODE @获取CPU的运行模式 CMP R3, #CPSR_USER_MODE @中断是否发生在用户模式 BNE OsIrqFromKernel @中断不发生在用户模式下则跳转到OsIrqFromKernel /* push user sp, lr in svc stack */ STMFD SP, {R13, R14}^ @sp和LR入svc栈 未定义异常处理(undef) @ Description: Undefined instruction exception handler _osExceptUndefInstrHdl:@出现未定义的指令处理 #ifdef LOSCFG_GDB GDB_HANDLE OsUndefIncExcHandleEntry #else @ LR offset to return from this exception: 0. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_UNDEF_INSTR @ Set exception ID to OS_EXCEPT_UNDEF_INSTR. B _osExceptDispatch @ Branch to global exception handler. #endif 异常分发统一处理 _osExceptDispatch: @异常模式统一分发处理 MRS R2, SPSR @ Save CPSR before exception. MOV R1, LR @ Save PC before exception. SUB R3, SP, #(8 * 4) @ Save the start address of working registers. MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @ Switch to SVC mode, and disable all interrupts MOV R5, SP EXC_SP_SET __exc_stack_top, OS_EXC_STACK_SIZE, R6, R7 STMFD SP!, {R1} @ Push Exception PC STMFD SP!, {LR} @ Push SVC LR STMFD SP!, {R5} @ Push SVC SP STMFD SP!, {R8-R12} @ Push original R12-R8, LDMFD R3!, {R4-R11} @ Move original R7-R0 from exception stack to original stack. STMFD SP!, {R4-R11} STMFD SP!, {R2} @ Push task`s CPSR (i.e. exception SPSR). CMP R0, #OS_EXCEPT_DATA_ABORT @是数据异常吗? BNE 1f @不是跳到 锚点1处 MRC P15, 0, R8, C6, C0, 0 @R8=C6(内存失效的地址) 0(访问数据失效) MRC P15, 0, R9, C5, C0, 0 @R9=C5(内存失效的状态) 0(无效整个指令cache) B 3f @跳到锚点3处执行 1: CMP R0, #OS_EXCEPT_PREFETCH_ABORT @是预取异常吗? BNE 2f @不是跳到 锚点2处 MRC P15, 0, R8, C6, C0, 2 @R8=C6(内存失效的地址) 2(访问指令失效) MRC P15, 0, R9, C5, C0, 1 @R9=C5(内存失效的状态) 1(虚拟地址) B 3f @跳到锚点3处执行 2: MOV R8, #0 MOV R9, #0 3: AND R2, R2, #CPSR_MASK_MODE CMP R2, #CPSR_USER_MODE @ User mode BNE 4f @不是用户模式 STMFD SP, {R13, R14}^ @ save user mode sp and lr 4: SUB SP, SP, #(4 * 2) @sp=sp-(4*2) 非常重要的ARM37个寄存器 详见 系列篇之寄存器篇 结尾 以上为异常接管对应的代码处理,具体每种异常发生的场景和代码细节处理,因内容太多,太复杂,系列篇后续将分篇一一分析.敬请关注! 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织, 欢迎转载,请注明出处. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v36.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 系列篇硬件部分说明基于ARM720T.pdf文档. 本篇说清楚CPU的工作模式 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构,肯定会有这些设计,只是大部分人不知道,也不需要知道,老百姓就干好老百姓的活就行了,有工作能吃饱饭就知足了,宫的事你管那么多干嘛,你也管不了. 应用程序就只关注应用功能,业务逻辑相关的部分就行了,底层实现对应用层屏蔽的越干净系统设计的就越优良. 但鸿蒙内核源码分析系列篇的定位就是要把整个底层解剖,全部掰开,看看宫里究竟发生了么事.从本篇开始要接触大量的汇编的代码,将鸿蒙内核的每段汇编代码一一说明白.如此才能知道最开始的开始发生了什么,最后的最后又发生了什么. 七种模式 先看一张图,图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间.系列篇之CPU篇已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行问题. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 如何让这七种模式能流畅的跑起来呢? 至少需要以下解决三个基本问题. 栈空间是怎么申请的?申请了多大? 被切换中的模式代码放在哪里?谁来安排它们放在哪里? 模式之间是怎么切换的?状态怎么保存? 本篇代码来源于鸿蒙内核源码之reset_vector_mp.S,点击查看 这个汇编文件大概 500多行,非常重要,本篇受限于篇幅只列出一小部分,说清楚以上三个问题.系列其余篇中将详细说明每段汇编代码的作用和实现,可前往查阅. 1.异常模式栈空间怎么申请? 鸿蒙是如何给异常模式申请栈空间的 #define CORE_NUM LOSCFG_KERNEL_SMP_CORE_NUM //CPU 核数 #ifdef LOSCFG_GDB #define OS_EXC_UNDEF_STACK_SIZE 512 #define OS_EXC_ABT_STACK_SIZE 512 #else #define OS_EXC_UNDEF_STACK_SIZE 40 #define OS_EXC_ABT_STACK_SIZE 40 #endif #define OS_EXC_FIQ_STACK_SIZE 64 #define OS_EXC_IRQ_STACK_SIZE 64 #define OS_EXC_SVC_STACK_SIZE 0x2000 //8K #define OS_EXC_STACK_SIZE 0x1000 //4K @六种特权模式申请对应的栈运行空间 __undef_stack: .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM __undef_stack_top: __abt_stack: .space OS_EXC_ABT_STACK_SIZE * CORE_NUM __abt_stack_top: __irq_stack: .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM __irq_stack_top: __fiq_stack: .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM __fiq_stack_top: __svc_stack: .space OS_EXC_SVC_STACK_SIZE * CORE_NUM __svc_stack_top: __exc_stack: .space OS_EXC_STACK_SIZE * CORE_NUM __exc_stack_top: 代码解读 六种异常模式都有自己独立的栈空间 每种模式的OS_EXC_***_STACK_SIZE栈大小都不一样,最大是管理模式(svc)8K,最小的只有40个字节. svc模式为什么要这么大呢? 因为开机代码和系统调用代码的运行都在管理模式,系统调用的函数实现往往较复杂,最大不能超过8K. 例如:某个系统调用中定义一个8K的局部变量,内核肯定立马闪蹦.因为栈将溢出,处理异常的程序出现了异常,后面就再也没人兜底了,只能是死局. 鸿蒙是支持多核处理的,CORE_NUM表明,每个CPU核的每种异常模式都有自己的独立栈空间.注意理解这个是理解内核代码的基础.否则会一头雾水. 2.异常模式入口地址在哪? 再看一张图,图来源于 ARM720T.pdf 第56页 这就是一切一切的开始,指定所有异常模式的入口地址表,这就是规定,没得商量的.在低地址情况下.开机代码就是放在 0x00000000的位置, 触发开机键后,硬件将PC寄存器置为0x00000000,开始了万里长征的第一步.在系统运行过程中就这么来回跳. b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 以上是各个异常情况下的入口地址,在reset_vector_mp.S中都能找到,经过编译链接后就会变成 b 0x00000000 @开机代码 b 0x00000004 @异常处理之CPU碰到不认识的指令 b 0x00000008 @异常处理之:软中断 b 0x0000000C @异常处理之:取指异常 b 0x00000010 @异常处理之:数据异常 b 0x00000014 @异常处理之:地址异常 b 0x00000018 @异常处理之:硬中断 b 0x0000001C @异常处理之:快中断 不管是主动切换的异常,还是被动切换的异常,都会先跳到对应的入口去处理.每个异常的代码都起始于汇编,处理完了再切回去. 举个例子:某个应用程序调用了系统调用(比如创建定时器),会经过以下大致过程: swi指令将用户模式切换到管理模式(svc) 在管理模式中先保存用户模式的现场信息(R0-R15寄存器值入栈) 获取系统调用号,知道是调用了哪个系统调用 查询系统调用对应的注册函数 执行真正的创建定时器函数 执行完成后,恢复用户模式的现场信息(R0-R15寄存器值出栈) 跳回用户模式继续执行 各异常处理代码很多,不一一列出,本篇只列出开机代码,请尝试读懂鸿蒙内核开机代码,后续详细说明每行代码的用处. 开机代码 reset_vector: //开机代码 /* clear register TPIDRPRW */ mov r0, #0 @r0 = 0 mcr p15, 0, r0, c13, c0, 4 @c0,c13 = 0, C13为进程标识符 含义见 ARM720T.PDF 第64页 /* do some early cpu setup: i/d cache disable, mmu disabled */ @禁用MMU, i/d缓存 mrc p15, 0, r0, c1, c0, 0 @r0 = c1 ,c1寄存器详细解释见第64页 bic r0, #(1<<12) @位清除指令,清除r0的第11位 bic r0, #(1<<2 | 1<<0) @清除第0和2位 ,禁止 MMU和缓存 0位:MMU enable/disable 2位:Cache enable/disable mcr p15, 0, r0, c1, c0, 0 @c1=r0 /* r11: delta of physical address and virtual address */@物理地址和虚拟地址的增量 adr r11, pa_va_offset @将基于PC相对偏移的地址pa_va_offset值读取到寄存器R11中 ldr r0, [r11] @将R11的值给r0 sub r11, r11, r0 @r11 = r11 - r0 mrc p15, 0, r12, c0, c0, 5 /* r12: get cpuid */ @获取CPUID and r12, r12, #MPIDR_CPUID_MASK @r12经过掩码过滤 cmp r12, #0 @当前是否为0号CPU bne secondary_cpu_init @不是0号主CPU则调用secondary_cpu_init /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ @r4获得加载基地址 ldr r5, =SYS_MEM_BASE /* r5: base of physical address */@r5获得物理基地址 subs r12, r4, r5 /* r12: delta of load address and physical address */ @r12=r4-r5 加载地址和物理地址的增量 beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ 异常的优先级 当同时出现多个异常时,该响应哪一个呢?这涉及到了异常的优先级,顺序如下 Reset (highest priority). Data Abort. FIQ. IRQ. Prefetch Abort. Undefined Instruction, SWI (lowest priority). 可以看出swi的优先级最低,swi就是软中断,系统调用就是通过它来实现的. 3.异常模式怎么切换? 写应用程序经常会用到状态,来记录各种分支逻辑,传递参数.这么多异常模式,相互切换,中间肯定会有很多的状态需要保存.比如:如何能知道当前运行在哪种模式下?怎么查?去哪里查呢? 答案是: CPSR 和 SPSR CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。 SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定 的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。 这些寄存器: 保存有关最近执行的ALU操作的信息 控制中断的启用和禁用 设置处理器操作模式 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v36.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 系列篇硬件部分说明基于ARM720T.pdf文档. 本篇说清楚CPU的工作模式 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构,肯定会有这些设计,只是大部分人不知道,也不需要知道,老百姓就干好老百姓的活就行了,有工作能吃饱饭就知足了,宫的事你管那么多干嘛,你也管不了. 应用程序就只关注应用功能,业务逻辑相关的部分就行了,底层实现对应用层屏蔽的越干净系统设计的就越优良. 但鸿蒙内核源码分析系列篇的定位就是要把整个底层解剖,全部掰开,看看宫里究竟发生了么事.从本篇开始要接触大量的汇编的代码,将鸿蒙内核的每段汇编代码一一说明白.如此才能知道最开始的开始发生了什么,最后的最后又发生了什么. 七种模式 先看一张图,图来源于 ARM720T.pdf 第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间.系列篇之CPU篇已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行问题. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 如何让这七种模式能流畅的跑起来呢? 至少需要以下解决三个基本问题. 栈空间是怎么申请的?申请了多大? 被切换中的模式代码放在哪里?谁来安排它们放在哪里? 模式之间是怎么切换的?状态怎么保存? 本篇代码来源于鸿蒙内核源码之reset_vector_mp.S,点击查看 这个汇编文件大概 500多行,非常重要,本篇受限于篇幅只列出一小部分,说清楚以上三个问题.系列其余篇中将详细说明每段汇编代码的作用和实现,可前往查阅. 1.异常模式栈空间怎么申请? 鸿蒙是如何给异常模式申请栈空间的 #define CORE_NUM LOSCFG_KERNEL_SMP_CORE_NUM //CPU 核数 #ifdef LOSCFG_GDB #define OS_EXC_UNDEF_STACK_SIZE 512 #define OS_EXC_ABT_STACK_SIZE 512 #else #define OS_EXC_UNDEF_STACK_SIZE 40 #define OS_EXC_ABT_STACK_SIZE 40 #endif #define OS_EXC_FIQ_STACK_SIZE 64 #define OS_EXC_IRQ_STACK_SIZE 64 #define OS_EXC_SVC_STACK_SIZE 0x2000 //8K #define OS_EXC_STACK_SIZE 0x1000 //4K @六种特权模式申请对应的栈运行空间 __undef_stack: .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM __undef_stack_top: __abt_stack: .space OS_EXC_ABT_STACK_SIZE * CORE_NUM __abt_stack_top: __irq_stack: .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM __irq_stack_top: __fiq_stack: .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM __fiq_stack_top: __svc_stack: .space OS_EXC_SVC_STACK_SIZE * CORE_NUM __svc_stack_top: __exc_stack: .space OS_EXC_STACK_SIZE * CORE_NUM __exc_stack_top: 代码解读 六种异常模式都有自己独立的栈空间 每种模式的OS_EXC_***_STACK_SIZE栈大小都不一样,最大是管理模式(svc)8K,最小的只有40个字节. svc模式为什么要这么大呢? 因为开机代码和系统调用代码的运行都在管理模式,系统调用的函数实现往往较复杂,最大不能超过8K. 例如:某个系统调用中定义一个8K的局部变量,内核肯定立马闪蹦.因为栈将溢出,处理异常的程序出现了异常,后面就再也没人兜底了,只能是死局. 鸿蒙是支持多核处理的,CORE_NUM表明,每个CPU核的每种异常模式都有自己的独立栈空间.注意理解这个是理解内核代码的基础.否则会一头雾水. 2.异常模式入口地址在哪? 再看一张图,图来源于 ARM720T.pdf 第56页 这就是一切一切的开始,指定所有异常模式的入口地址表,这就是规定,没得商量的.在低地址情况下.开机代码就是放在 0x00000000的位置, 触发开机键后,硬件将PC寄存器置为0x00000000,开始了万里长征的第一步.在系统运行过程中就这么来回跳. b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 以上是各个异常情况下的入口地址,在reset_vector_mp.S中都能找到,经过编译链接后就会变成 b 0x00000000 @开机代码 b 0x00000004 @异常处理之CPU碰到不认识的指令 b 0x00000008 @异常处理之:软中断 b 0x0000000C @异常处理之:取指异常 b 0x00000010 @异常处理之:数据异常 b 0x00000014 @异常处理之:地址异常 b 0x00000018 @异常处理之:硬中断 b 0x0000001C @异常处理之:快中断 不管是主动切换的异常,还是被动切换的异常,都会先跳到对应的入口去处理.每个异常的代码都起始于汇编,处理完了再切回去.举个例子: 某个应用程序调用了系统调用(比如创建定时器),会经过以下大致过程: swi指令将用户模式切换到管理模式(svc) 在管理模式中先保存用户模式的现场信息(R0-R15寄存器值入栈) 获取系统调用号,知道是调用了哪个系统调用 查询系统调用对应的注册函数 执行真正的创建定时器函数 执行完成后,恢复用户模式的现场信息(R0-R15寄存器值出栈) 跳回用户模式继续执行 各异常处理代码很多,不一一列出,本篇只列出开机代码,请尝试读懂鸿蒙内核开机代码,后续讲详细说明每行代码的用处. 开机代码 reset_vector: //开机代码 /* clear register TPIDRPRW */ mov r0, #0 @r0 = 0 mcr p15, 0, r0, c13, c0, 4 @c0,c13 = 0, C13为进程标识符 含义见 ARM720T.PDF 第64页 /* do some early cpu setup: i/d cache disable, mmu disabled */ @禁用MMU, i/d缓存 mrc p15, 0, r0, c1, c0, 0 @r0 = c1 ,c1寄存器详细解释见第64页 bic r0, #(1<<12) @位清除指令,清除r0的第11位 bic r0, #(1<<2 | 1<<0) @清除第0和2位 ,禁止 MMU和缓存 0位:MMU enable/disable 2位:Cache enable/disable mcr p15, 0, r0, c1, c0, 0 @c1=r0 /* r11: delta of physical address and virtual address */@物理地址和虚拟地址的增量 adr r11, pa_va_offset @将基于PC相对偏移的地址pa_va_offset值读取到寄存器R11中 ldr r0, [r11] @将R11的值给r0 sub r11, r11, r0 @r11 = r11 - r0 mrc p15, 0, r12, c0, c0, 5 /* r12: get cpuid */ @获取CPUID and r12, r12, #MPIDR_CPUID_MASK @r12经过掩码过滤 cmp r12, #0 @当前是否为0号CPU bne secondary_cpu_init @不是0号主CPU则调用secondary_cpu_init /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ @r4获得加载基地址 ldr r5, =SYS_MEM_BASE /* r5: base of physical address */@r5获得物理基地址 subs r12, r4, r5 /* r12: delta of load address and physical address */ @r12=r4-r5 加载地址和物理地址的增量 beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ 异常的权限 当同时出现多个异常时,该响应哪一个呢?就涉及到了异常的权限,如下 Reset (highest priority). Data Abort. FIQ. IRQ. Prefetch Abort. Undefined Instruction, SWI (lowest priority). 可以看出swi的权限最低,swi就是软件中断,系统调用就是通过它来实现的. 3.异常模式怎么切换? 写应用程序经常会用到状态,来记录各种分支逻辑,传递参数.这么多异常模式,相互切换,中间肯定会有很多的状态需要保存.比如:如何能知道当前运行在哪种模式下?怎么查?去哪里查呢? 答案是: CPSR 和 SPSR CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。 SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定 的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。 这些寄存器: 保存有关最近执行的ALU操作的信息 控制中断的启用和禁用 设置处理器操作模式 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01

鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚互斥锁 读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇. 内核中哪些模块会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和 互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争. 自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克. 互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题: 第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表. 第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式. 第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办? 答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被打回原形.所以一个任务的优先级会看情况时高时低. 第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级. 带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明. 互斥锁长什么样? enum { LOS_MUX_PRIO_NONE = 0, //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队. LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。 //当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级 LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义 }; enum { LOS_MUX_NORMAL = 0, //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测) LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。 LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。 LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁 }; typedef struct { //互斥锁的属性 UINT8 protocol; //协议 UINT8 prioceiling; //优先级上限 UINT8 type; //类型属性 UINT8 reserved; //保留字段 } LosMuxAttr; typedef struct OsMux { //互斥锁结构体 UINT32 magic; /**< magic number */ //魔法数字 LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性 LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上 LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别. VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务 UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次 } LosMux; 这互斥锁长的明显的比自旋锁丰满多啦,还记得自旋锁的样子吗,就一个变量,单薄到令人心疼. 初始化 LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr) { //... SCHEDULER_LOCK(intSave); //拿到调度自旋锁 mutex->muxCount = 0; //锁定互斥量的次数 mutex->owner = NULL; //持有该锁的任务 LOS_ListInit(&mutex->muxList); //初始化等待该锁的任务链表 mutex->magic = OS_MUX_MAGIC; //固定标识,互斥锁的魔法数字 SCHEDULER_UNLOCK(intSave); //释放调度自旋锁 return LOS_OK; } 留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务. 三种申请模式 申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。 无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。 永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。 定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。 如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。 如果没有任务阻塞于该互斥锁,则互斥锁释放成功。 申请互斥锁主函数 OsMuxPendOp //互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一 //最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout) { UINT32 ret; LOS_DL_LIST *node = NULL; LosTaskCB *owner = NULL; if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理 /* This is for mutex macro initialization. */ mutex->muxCount = 0;//锁计数器清0 mutex->owner = NULL;//锁没有归属任务 LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去 } if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回 mutex->muxCount++; //互斥锁计数器加1 mutex->owner = (VOID *)runTask; //当前任务拿到锁 LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表 if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的? LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap OsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级. }//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁. return LOS_OK; } //递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了 if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方 mutex->muxCount++; //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE) return LOS_OK; //成功退出 } //到了这里说明锁在别的任务那里,当前任务只能被阻塞了. if (!timeout) {//参数timeout表示等待多久再来拿锁 return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock } //自己要被阻塞,只能申请调度,让出CPU core 让别的任务上 if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁) return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有 } OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级 owner = (LosTaskCB *)mutex->owner; //记录持有锁的任务 runTask->taskMux = (VOID *)mutex; //记下当前任务在等待这把锁 node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务 ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度 if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文 runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了 ret = LOS_ETIMEDOUT;//返回超时 } if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况 OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图 } return ret; } 释放锁的主体函数 OsMuxPostOp //是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的 //OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题 STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched) { LosTaskCB *resumedTask = NULL; if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空 LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉 mutex->owner = NULL; return LOS_OK; } resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务 if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作? if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级 if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级 LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中 } } else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级 OsMuxPostOpSub(taskCB, mutex);// } } mutex->muxCount = 1;//互斥锁数量为1 mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了 resumedTask->taskMux = NULL;//resumedTask不再等锁了 LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去 LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录 OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态 if (needSched != NULL) {//如果不为空 *needSched = TRUE;//就走起再次调度流程 } return LOS_OK; } 总结 1.互斥锁解决的是任务间竞争共享内存的问题. 2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁. 3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务. 4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数. 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony >

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册