Nota: Basado en el análisis de oscuridad del núcleo de código abierto, la fuente oficial [ kernel_liteos_a ] documentos oficiales [ docs ] Documento de referencia [ Huawei LiteOS ]
autor: entusiastas del núcleo de Hong Meng, continuará estudiando el núcleo de oscuridad, actualizará el blog, así que estad atentos. El contenido solo representa opiniones personales, los errores son bienvenidos, todos pueden corregir y mejorar. Todos los artículos de esta serie ingresan para ver el análisis del código fuente del sistema Hongmeng (catálogo general)
Este artículo analiza en detalle el código fuente del mecanismo de programación de tareas: ../kernel/base/sched/sched_sq/los_process.c
Tabla de contenido
Echemos un vistazo a las instrucciones oficiales.
Inicialización de la gestión de procesos
El proceso de creación de kernel Kprocess
El proceso de creación de un proceso de modo de usuario
Deja dos pequeñas preguntas para que todos piensen
Echemos un vistazo a las instrucciones oficiales.
conceptos básicos
Desde la perspectiva del sistema, un proceso es una unidad de gestión de recursos . Los procesos pueden usar o esperar recursos del sistema como CPU y espacio de memoria, y ejecutarse independientemente de otros procesos.
El módulo de proceso del kernel de OpenHarmony puede proporcionar a los usuarios múltiples procesos, realizar la conmutación y la comunicación entre procesos y ayudar a los usuarios a administrar los flujos de programas comerciales. De esta forma, los usuarios pueden dedicar más energía a la realización de funciones comerciales.
El proceso en el kernel de OpenHarmony adopta un mecanismo de programación preventiva, admite la programación por turnos por intervalos de tiempo y el mecanismo de programación FIFO.
El proceso del kernel de OpenHarmony tiene un total de 32 prioridades (0-31) y hay 22 (10-31) prioridades configurables para los procesos de usuario. La prioridad más alta es 10 y la prioridad más baja es 31.
Los procesos de alta prioridad pueden adelantarse a los de baja prioridad, y los procesos de baja prioridad solo se pueden programar después de que los procesos de alta prioridad se bloqueen o terminen.
Cada proceso de modo de usuario tiene su propio espacio de proceso independiente, que es invisible entre sí, lo que permite el aislamiento entre procesos.
El proceso de raíz en modo de usuario init es creado por el modo kernel, y otros procesos en modo de usuario son bifurcaciones del proceso init.
Descripción del estado del proceso:
-
Inicialización (Init): se está creando el proceso.
-
Listo: el proceso está en la lista de listos, esperando la programación de la CPU.
-
En ejecución: el proceso se está ejecutando.
-
Pend: El proceso se bloquea y se suspende. Cuando todos los subprocesos de este proceso están bloqueados, el proceso se bloquea y se cuelga.
-
Zombies (Zombies): El proceso finaliza, esperando que el proceso principal recupere sus recursos de bloque de control.
Figura 1 Diagrama esquemático de la transición del estado del proceso
Descripción de la transición del estado del proceso:
-
Inicio → Listo :
Cuando se crea un proceso o se bifurca, ingresa al estado de inicio después de obtener el bloque de control del proceso y se encuentra en la etapa de inicialización del proceso. Cuando se completa la inicialización del proceso, el proceso se inserta en la cola de programación y el proceso entra en el estado listo.
-
Listo → En ejecución:
Después de que se crea el proceso, entra en el estado listo. Cuando ocurre un cambio de proceso, el proceso con la prioridad más alta en la lista lista se ejecuta y entra en el estado de ejecución. Si no hay otros subprocesos en el proceso en estado listo en este momento, el proceso se elimina de la lista lista y solo en el estado en ejecución; si hay otros subprocesos en el proceso en estado listo en este momento, el proceso todavía está en la cola lista. El estado listo y el estado de ejecución del proceso coexisten.
-
Ejecutando → Pend :
Cuando todos los subprocesos del proceso están en el estado de bloqueo, cuando el último subproceso del proceso pasa al estado de bloqueo, entra en el estado de bloqueo sincrónicamente y luego se produce el cambio de proceso.
-
Pend → Listo / Pend → En ejecución :
Cuando cualquier subproceso en el proceso bloqueado vuelve al estado listo, el proceso se agrega a la cola lista y cambia de forma síncrona al estado listo. Si se produce un cambio de proceso en este momento, el estado del proceso cambia del estado listo al estado en ejecución.
-
Listo → Pend:
Cuando el último subproceso de estado listo en el proceso está en el estado de bloqueo, el proceso se elimina de la lista de listos y el proceso cambia del estado listo al estado de bloqueo.
-
En ejecución → Listo :
Hay dos situaciones en las que el proceso cambia del estado en ejecución al estado listo:
- Después de que se crea o restaura un proceso con una prioridad más alta, se produce la programación del proceso. En este momento, el proceso con la prioridad más alta en la lista de listos se convierte en el estado en ejecución y el proceso en ejecución original cambia del estado en ejecución al estado listo.
- Si la estrategia de programación del proceso es SCHED_RR, y otro proceso con la misma prioridad está en el estado listo, después de que se agote el intervalo de tiempo del proceso, el proceso pasa del estado en ejecución al estado listo y otro proceso con la misma prioridad cambia del estado listo. El estado cambia al estado de ejecución.
-
Corriendo → Zombis :
Cuando el subproceso principal o todos los subprocesos del proceso finalizan, el proceso pasa del estado de ejecución al estado zombi, esperando que el proceso principal recupere recursos.
escenas a utilizar
Una vez creado el proceso, el usuario solo puede operar los recursos de su propio espacio de proceso y no puede operar los recursos de otros procesos (excepto los recursos compartidos). El modo de usuario permite suspender, reanudar y retrasar los procesos. Al mismo tiempo, la prioridad de programación de procesos y la estrategia de programación del modo de usuario se pueden configurar para obtener la prioridad de programación de procesos y la estrategia de programación. Cuando el proceso finaliza, el proceso liberará activamente los recursos del proceso retenido, pero los recursos del pid del proceso retenido deben ser reciclados por el proceso principal a través de wait / waitpid o cuando el proceso principal finaliza.
Iniciar análisis formal
Tenga en cuenta que el proceso de palabras en rojo marcado arriba es una unidad de gestión de recursos, no una unidad de programación ¿Quién es la unidad de programación? Es Task / Thread, mira la definición del estado correspondiente oficial
#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
El proceso de un proceso desde la creación hasta la muerte debe ser extremadamente complicado en el núcleo, al igual que el nacimiento hasta la desaparición de una empresa. Para facilitar la comprensión del proceso, el autor a menudo hace una analogía, a partir de los ejemplos de la vida, el misterioso núcleo del sistema se exterioriza y se disecciona para que todos lo vean. Una cosa tan complicada debe llevarla una estructura complicada, es decir, LosProcessCB (Process Control Block). El código es muy largo pero hay que sacarlo. La longitud es un poco más larga, ¡paciencia!
LITE_OS_SEC_BSS LosProcessCB *g_runProcess[LOSCFG_KERNEL_CORE_NUM]; //用一个指针数组记录进程运行,LOSCFG_KERNEL_CORE_NUM 为 CPU的核数
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL;//进程数组,最大进程数为 64个
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;//记录空闲进程链表
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecyleList;//记录回收的进程列表
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;
Hay dos modos de proceso, el modo kernel y el modo usuario. Se puede pensar que se creará un modo kernel del proceso de mayor prioridad en la función principal, que es KProcess.
El proceso de llamada es el siguiente
Vea el estado de ejecución de la tarea a través del comando de tarea, puede ver el proceso de KProcess , sabe que es un proceso de kernel por el nombre, se crea cuando se inicia el sistema y puede ver el estado de ejecución del hilo de KProcess en la figura
Por ejemplo: swt_task, oom_task, jffs2_gc_thread, etc.
Inicialización de la gestión de procesos
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;
LITE_OS_SEC_BSS UINT32 g_processMaxNum;//*kyf 进程最大数量
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;//*kyf 进程组
Permítanme explicar todas las variables en .c primero. Los comentarios son agregados por el autor y hay pocos comentarios sobre el kernel de Hongmeng.
Echemos un vistazo al código
/**
* @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;
}
El código es muy claro, se crea un pool de procesos, el valor por defecto es 64 procesos, es decir, el sistema puede tener hasta 64 procesos sin cambiar la macro LOSCFG_BASE_CORE_PROCESS_LIMIT, pero se ocupan dos procesos primero, uno en modo usuario y otro en modo kernel, ellos Es el padre del proceso de creación posterior, por lo que se pueden crear como máximo 62 procesos en el exterior y también se borra la lista de bloqueo de tareas de los dos últimos padres del código.
El proceso de creación de kernel Kprocess
Cree un proceso de estado central, que es el número de proceso [2] en el grupo de subprocesos. En el comando de tarea, Kprocess PID = 2, los parámetros son el estado central y la prioridad más alta 0
El código establece kprocess como el proceso actual y bifurca un KIdle (el proceso inactivo en modo kernel)
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;
}
Lo anterior es el proceso de inicialización del proceso en modo kernel
El proceso de creación de un proceso de modo de usuario
/**
* @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;
}
Se encuentra que el proceso de inicio y creación en modo usuario es similar al modo kernel, y la prioridad del proceso padre en modo usuario es 28, muy baja.
Deja dos pequeñas preguntas para que todos piensen
Hay algún código sobre la memoria en OsUserInitProcess, y el valor no se puede encontrar en el código fuente, como por ejemplo:
CHAR *userInitTextStart = (CHAR *)&__user_init_entry;
CHAR *userInitBssStart = (CHAR *)&__user_init_bss;
CHAR *userInitEnd = (CHAR *)&__user_init_end;
¿Por qué está pasando esto?
Los PID de los otros dos padres son 1 y 2. ¿A dónde fue el proceso número 0 en el grupo de procesos?
Invitamos a todos a responder en el área de comentarios, y analizaremos en detalle más adelante. Se pueden ver más artículos en el análisis de código fuente del sistema Hongmeng (Catálogo general)