提示:本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】官方文档【docs】参考文档【Huawei LiteOS】
本文作者:鸿蒙内核发烧友,首创用生活场景讲故事的方式去解构内核,一窥究竟,让神秘的内核栩栩如生,浮现眼前。博文持续更新,敬请关注。内容仅代表个人观点,错误之处,欢迎大家指正完善。本系列全部文章进入鸿蒙系统源码分析(总目录)查看
强烈建议先阅读同系列姊妹篇 鸿蒙内核源码分析(必读篇)|用故事说内核|张大爷的故事
本文分析任务调度机制源码 详见:../kernel/base/sched/sched_sq/los_process.c
目录
先看官方文档一定要读
基本概念
从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。
OpenHarmony内核的进程模块可以给用户提供多个进程,实现了进程之间的切换和通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。
OpenHarmony内核中的进程采用抢占式调度机制,支持时间片轮转调度方式和FIFO调度机制。
OpenHarmony内核的进程一共有32个优先级(0-31),用户进程可配置的优先级有22个(10-31),最高优先级为10,最低优先级为31。
高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。
每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离。
用户态根进程init由内核态创建,其它用户态进程均由init进程fork而来。
进程状态说明:
-
初始化(Init):该进程正在被创建。
-
就绪(Ready):该进程在就绪列表中,等待CPU调度。
-
运行(Running):该进程正在运行。
-
阻塞(Pend):该进程被阻塞挂起。本进程内所有的线程均被阻塞时,进程被阻塞挂起。
-
僵尸态(Zombies):该进程运行结束,等待父进程回收其控制块资源。
进程状态迁移说明:
-
Init→Ready:
进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
-
Ready→Running:
进程创建后进入就绪态,发生进程切换时,就绪列表中最高优先级的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态,则该进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共存。
-
Running→Pend:
进程内所有的线程均处于阻塞态时,进程在最后一个线程转为阻塞态时,同步进入阻塞态,然后发生进程切换。
-
Pend→Ready / Pend→Running:
阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。
-
Ready→Pend:
进程内的最后一个就绪态线程处于阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。
-
Running→Ready:
进程由运行态转为就绪态的情况有以下两种:
- 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
- 若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
-
Running→Zombies:
当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
使用场景
进程创建后,用户只能操作自己进程空间的资源,无法操作其它进程的资源(共享资源除外)。 用户态允许进程挂起,恢复,延时等操作,同时也可以设置用户态进程调度优先级和调度策略,获取进程调度优先级和调度策略。进程结束的时候,进程会主动释放持有的进程资源,但持有的进程pid资源需要父进程通过wait/waitpid或父进程退出时回收。
开始正式分析
对应张大爷的故事,进程就是那些在场馆外32个队列里排队的人,那些队列就是进程的就绪队列。
请注意上面标注的红色的字 进程是资源管理单元 ,而非调度单元,调度单元是谁?是 Task/线程 ,看下官方对应状态的define
-
#define OS_PROCESS_STATUS_INIT 0x0010U
-
#define OS_PROCESS_STATUS_READY 0x0020U
-
#define OS_PROCESS_STATUS_RUNNING 0x0040U
-
#define OS_PROCESS_STATUS_PEND 0x0080U
-
#define OS_PROCESS_STATUS_ZOMBIES 0x100U
一个进程从创建到消亡过程,在内核肯定是极其复杂的。为了方便理解进程,整个系列文章笔者会用张大爷的故事打比方,从生活中的例子来将神秘的系统内核外化解剖出来给大家看。一件这么复杂的事情肯定会有个复杂的结构体来承载,就是LosProcessCB(进程控制块),代码很长但必须全部拿出来,长是长了点,忍忍吧!
-
-
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];
//*kyf 用一个指针数组记录进程运行,LOSCFG_KERNEL_CORE_NUM 为 CPU的核数
-
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray =
NULL;
//*kyf 进程池,最大进程数为 64个
-
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;
//*kyf 记录空闲的进程链表
-
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;
//*kyf 记录回收的进程列表
-
-
-
typedef
struct ProcessCB {
-
CHAR processName[OS_PCB_NAME_LEN];
/**< Process name */
-
UINT32 processID;
/**< process ID = leader thread ID */
-
UINT16 processStatus;
/**< [15:4] process Status; [3:0] The number of threads currently
-
running in the process */
-
UINT16 priority;
/**< process priority */
-
UINT16 policy;
/**< process policy */
-
UINT16 timeSlice;
/**< Remaining time slice */
-
UINT16 consoleID;
/**< The console id of task belongs */
-
UINT16 processMode;
/**< Kernel Mode:0; User Mode:1; */
-
UINT32 parentProcessID;
/**< Parent process 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 */
-
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 */
-
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 */
-
#if (LOSCFG_KERNEL_SMP == YES)
-
UINT32 timerCpu;
/**< CPU core number of this task is delayed or pended */
-
#endif
-
UINTPTR sigHandler;
/**< signal handler */
-
sigset_t sigShare;
/**< signal share bit */
-
#if (LOSCFG_KERNEL_LITEIPC == YES)
-
ProcIpcInfo ipcInfo;
/**< memory pool for lite ipc */
-
#endif
-
LosVmSpace *vmSpace;
/**< VMM space for processes */
-
#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;
-
#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;
进程的模式有两种,内核态和用户态,能想到main函数中肯定会创建一个内核态的最高优先级进程,他就是 KProcess
调用过程如下
通过task命令查看任务运行状态,可以看到 KProcess 进程 ,看名字就知道是一个内核进程,在系统启动时创建,图中可以看到 KProcess 的task运行情况,从表里可以看到KProcess内有 10几个task
进程初始化
-
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM];
//*kyf CPU内核个数
-
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray =
NULL;
//*kyf 进程池
-
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;
//*kyf 空闲状态下可供分配的进程,此时进程白纸一张
-
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;
//*kyf 需要回收的进程列表
-
LITE_OS_SEC_BSS UINT32 g_userInitProcess = OS_INVALID_VALUE;
//*kyf 用户态的初始进程,用户态下其他进程由它 fork
-
LITE_OS_SEC_BSS UINT32 g_kernelInitProcess = OS_INVALID_VALUE;
//*kyf 内核态初始进程,内核态下其他进程由它 fork
-
LITE_OS_SEC_BSS UINT32 g_kernelIdleProcess = OS_INVALID_VALUE;
//*kyf 内核空闲进程
-
LITE_OS_SEC_BSS UINT32 g_processMaxNum;
//*kyf 进程最大数量
-
LITE_OS_SEC_BSS ProcessGroup *g_processGroup =
NULL;
//*kyf 进程组
以上是.c 中的所有全部变量。注释是笔者添加的,鸿蒙内核的注释很少。
KProcess 在张大爷的故事里相当于场馆的工作人员,他们也要接受张大爷的调度排队进场,但他们的优先级是最高的0级,他们进场后需完成场馆的准备工作,再开门做生意。如果需要多个工作人员怎么办,就是通过fork,简单说就是复制一个,复制的前提是需要有一个,鸿蒙里就是KProcess,其他工作人员都是通过它fork的。那用户怎么来的呢?就是真正要排队的人也是一样,先创建一个用户爸爸,其他用户就用爸爸fork来的,注意是fork 不是那个什么K,哈哈,你懂得。
还是直接看代码吧
-
/**
-
* @ingroup los_config
-
* Maximum supported number of process rather than the number of usable processes.
-
*/
-
#ifndef LOSCFG_BASE_CORE_PROCESS_LIMIT
-
#define LOSCFG_BASE_CORE_PROCESS_LIMIT 64
-
#endif
-
-
LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
-
{
-
UINT32 index;
-
UINT32 size;
-
-
g_processMaxNum = LOSCFG_BASE_CORE_PROCESS_LIMIT;
//*kyf 默认是64个
-
size = g_processMaxNum *
sizeof(LosProcessCB);
-
-
g_processCBArray = (LosProcessCB *)LOS_MemAlloc(m_aucSysMem1, size);
//*kyf 进程池
-
if (g_processCBArray ==
NULL) {
-
return LOS_NOK;
-
}
-
(VOID)memset_s(g_processCBArray, size,
0, size);
-
-
LOS_ListInit(&g_freeProcess);
-
LOS_ListInit(&g_processRecyleList);
-
-
for (index =
0; index < g_processMaxNum; index++) {
-
g_processCBArray[index].processID = index;
-
g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;
//*kyf 默认都是白纸一张,臣妾干净着呢
-
LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);
//*kyf 可分配链表
-
}
-
-
g_userInitProcess =
1;
/* 1: The root process ID of the user-mode process is fixed at 1 */
-
LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);
//*kyf 清空pend链表
-
-
g_kernelInitProcess =
2;
/* 2: The root process ID of the kernel-mode process is fixed at 2 */
-
LOS_ListDelete(&g_processCBArray[g_kernelInitProcess].pendList);
//*kyf 清空pend链表
-
-
return LOS_OK;
-
}
代码已经很清楚,创建了一个进程池,默认64个进程,也就是不改宏LOSCFG_BASE_CORE_PROCESS_LIMIT的情况下 系统最多是64个进程,但有两个进程先被占用,用户态和内核态各一个,他们是后续创建进程的爹,所以最多留给外面的只有 62个进程可创建,代码的最后两个爸爸的task阻塞链表也被清空了。
创建内核态Kprocess的过程
创建核心态进程,也就是线程池中的 [2] 号进程,task 命令中 Kprocess PID = 2, 参数是 内核态和 最高优先级 0
代码的把kprocess 设为当前进程,并且fork了一个 KIdle(内核态的空闲进程)
-
STATIC UINT32 OsCreateIdleProcess(VOID)
-
{
-
UINT32 ret;
-
CHAR *idleName =
"Idle";
-
LosProcessCB *idleProcess =
NULL;
-
Percpu *perCpu = OsPercpuGet();
-
UINT32 *idleTaskID = &perCpu->idleTaskID;
-
-
ret = OsCreateResourceFreeTask();
//*kyf 创建一个资源回收任务
-
if (ret != LOS_OK) {
-
return ret;
-
}
-
-
ret = LOS_Fork(CLONE_FILES,
"KIdle", (TSK_ENTRY_FUNC)OsIdleTask, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE);
-
if (ret <
0) {
-
return LOS_NOK;
-
}
-
g_kernelIdleProcess = (UINT32)ret;
-
-
idleProcess = OS_PCB_FROM_PID(g_kernelIdleProcess);
-
*idleTaskID = idleProcess->threadGroupID;
-
OS_TCB_FROM_TID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;
-
#if (LOSCFG_KERNEL_SMP == YES)
-
OS_TCB_FROM_TID(*idleTaskID)->cpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
-
#endif
-
(VOID)memset_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN,
0, OS_TCB_NAME_LEN);
-
(VOID)memcpy_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName,
strlen(idleName));
-
return LOS_OK;
-
}
以上是内核态进程的初始化过程
创建用户态进程的过程是怎样的?看代码
-
/**
-
* @ingroup los_process
-
* User state root process default priority
-
*/
-
#define OS_PROCESS_USERINIT_PRIORITY 28
-
-
LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
-
{
-
INT32 ret;
-
UINT32 size;
-
TSK_INIT_PARAM_S param = {
0 };
-
VOID *
stack =
NULL;
-
VOID *userText =
NULL;
-
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;
-
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
-
CHAR *userInitEnd = (CHAR *)&__user_init_end;
-
UINT32 initBssSize = userInitEnd - userInitBssStart;
-
UINT32 initSize = userInitEnd - userInitTextStart;
-
-
LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);
-
ret = OsProcessCreateInit(processCB, OS_USER_MODE,
"Init", OS_PROCESS_USERINIT_PRIORITY);
-
if (ret != LOS_OK) {
-
return ret;
-
}
-
-
userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);
-
if (userText ==
NULL) {
-
ret = LOS_NOK;
-
goto ERROR;
-
}
-
-
(VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);
-
ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),
-
initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
-
VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);
-
if (ret <
0) {
-
goto ERROR;
-
}
-
-
(VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize,
0, initBssSize);
-
-
stack = OsUserInitStackAlloc(g_userInitProcess, &size);
-
if (
stack ==
NULL) {
-
PRINTK(
"user init process malloc user stack failed!\n");
-
ret = LOS_NOK;
-
goto ERROR;
-
}
-
-
param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;
-
param.userParam.userSP = (UINTPTR)
stack + size;
-
param.userParam.userMapBase = (UINTPTR)
stack;
-
param.userParam.userMapSize = size;
-
param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;
-
ret = OsUserInitProcessStart(g_userInitProcess, ¶m);
-
if (ret != LOS_OK) {
-
(VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);
-
goto ERROR;
-
}
-
-
return LOS_OK;
-
-
ERROR:
-
(VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);
-
OsDeInitPCB(processCB);
-
return ret;
-
}
发现用户态init 和 创建的过程类似 内核态,并且用户态爸爸进程的优先级是 28,好低啊。
对应张大爷的故事:工作人员和用户的工作环境是不一样的,工作人员可以全场活动,但用户不能去管理处溜达,以上代码相当于给用户划界限,你只能在表演的场地活动,这个场地就是用户空间,内存就是整个场地,具体要划分后续有文章讲怎么管理内存。
留下两个小问题请大家思考
OsUserInitProcess里有些关于内存的代码,源码中找不到值,比如:
-
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;
-
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
-
CHAR *userInitEnd = (CHAR *)&__user_init_end;
为什么会这样?
另外两个爸爸对应的PID是 1和2,那进程池里的0号进程去哪里了?
欢迎大家在评论区里回复,后续会详细剖析,更多文章在 鸿蒙系统源码分析(总目录) 中查看
转载:https://blog.csdn.net/kuangyufei/article/details/108595941