Concepts fondamentaux du système
Du point de vue du système, un processus est une unité de gestion des ressources. Un processus peut utiliser ou attendre le CPU, utiliser l'espace mémoire et autres ressources système, et s'exécute indépendamment des autres processus.
Le module de processus du noyau OpenHarmony permet aux utilisateurs de gérer plusieurs processus, en assurant leur commutation et leur communication, aidant ainsi à gérer le flux des programmes métier. Cela permet aux utilisateurs de se concentrer davantage sur l'implémentation des fonctionnalités métier.
Le noyau OpenHarmony utilise un mécanisme d'ordonnancement préemptif pour les processus, supportant l'ordonnancement par tranches de temps (round-robin) et le mécanisme d'ordonnancement FIFO.
Le noyau OpenHarmony comporte 32 niveaux de priorité pour les processus (0-31), avec 22 niveaux configurables pour les processus utilisateur (10-31). Le niveau de priorité le plus élevé est 10 et le plus bas est 31.
Les processus de haute priorité peuvent préempter les processus de basse priorité, tandis que les processus de basse priorité ne peuvent être planifiés que lorsque les processus de haute priorité sont bloqués ou terminés.
Chaque processus en mode utilisateur possède son propre espace de processus indépendant, invisible entre eux, assurant ainsi l'isolation inter-processus.
Le processus racine en mode utilisateur (init) est créé par le noyau en mode noyau, tandis que tous les autres processus en mode utilisateur sont dérivés du processus init via l'appel système fork.
États du processus :
- Initialisation (Init) : Le processus est en cours de création.
- Prêt (Ready) : Le processus est dans la liste des processus prêts, en attente d'ordonnancement par le CPU.
- Exécution (Running) : Le processus est en cours d'exécution.
- Bloqué (Pend) : Le processus est bloqué et suspendu. Lorsque tous les threads d'un processus sont bloqués, le processus est bloqué.
- Zombie (Zombies) : Le processus s'est terminé et attend que le processus père récupère ses ressources de contrôle de bloc.
Scénarios d'utilisation
Après sa création, un utilisateur ne peut manipuler que les ressources de son propre espace de processus, pas celles d'autres processus (à l'exception des ressources partagées). Le mode utilisateur permet aux processus d'être suspendus, repris, retardés, et de définir leur priorité et stratégie d'ordonnancement. Lorsqu'un processus se termine, il libère activement les ressources qu'il détenait, mais son PID doit être récupéré par le processus père via wait/waitpid ou lors de la sortie du processus père.
Analyse technique détaillée
Dans la métaphore de l'histoire de M. Zhang, les processus sont ceux qui attendent dans les 32 files d'attente à l'extérieur du site, et ces files représentent les files d'attente des processus prêts.
Il est important de noter qu'un processus est une unité de gestion des ressources, et non l'unité d'ordonnancement finale. L'unité d'ordonnancement est le Task. Voici les définitions d'état correspondantes :
#define OS_PROCESS_STATUS_INIT 0x0010U // État initial du processus
#define OS_PROCESS_STATUS_READY 0x0020U // État prêt du processus
#define OS_PROCESS_STATUS_RUNNING 0x0040U // État d'exécution du processus
#define OS_PROCESS_STATUS_PEND 0x0080U // État bloqué du processus
#define OS_PROCESS_STATUS_ZOMBIES 0x100U // État zombie du processus
Le parcours d'un processus de sa création à sa destruction dans le noyau est extrêmement complexe. Pour faciliter la compréhension, nous utiliserons la métaphore de l'histoire de M. Zhang pour analyser ce mécanisme complexe. Une structure complexe est nécessaire pour porter les informations d'un processus : il s'agit de ProcessControlBlock (bloc de contrôle de processus).
LITE_OS_SEC_BSS ProcessControlCB *g_runningProcess[LOSCFG_KERNEL_CORE_NUM]; // Tableau de pointeurs enregistrant les processus en cours d'exécution
LITE_OS_SEC_BSS ProcessControlCB *g_processPool = NULL; // Pool de processus, nombre maximum de processus : 64
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcessList; // Liste chaînée des processus inoccupés
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_recycledProcessList; // Liste des processus à recycler
typedef struct ProcessControlCB {
CHAR processName[OS_PCB_NAME_LEN]; /**< Nom du processus */
UINT32 processID; /**< ID du processus = ID du thread principal */
UINT16 processStatus; /**< [15:4] Statut du processus; [3:0] Nombre de threads actuellement
en cours d'exécution dans le processus */
UINT16 priority; /**< Priorité du processus */
UINT16 schedulingPolicy; /**< Politique d'ordonnancement du processus */
UINT16 timeSlice; /**< Tranche de temps restante */
UINT16 consoleID; /**< ID de la console à laquelle la tâche appartient */
UINT16 executionMode; /**< Mode Noyau:0; Mode Utilisateur:1; */
UINT32 parentProcessID; /**< ID du processus parent */
UINT32 exitCode; /**< Code de sortie du processus */
LOS_DL_LIST blockedList; /**< Liste de blocage à laquelle le processus appartient */
LOS_DL_LIST childrenList; /**< Liste des processus enfants */
LOS_DL_LIST exitedChildrenList; /**< Liste des processus enfants terminés */
LOS_DL_LIST siblingList; /**< Liaison dans la liste des enfants du parent */
ProcessGroup *group; /**< Groupe de processus auquel appartient le processus */
LOS_DL_LIST groupMemberList; /**< Liaison dans la liste de groupe */
UINT32 threadGroupID; /**< ID du groupe de threads, est l'ID du thread principal du processus */
UINT32 threadScheduleMap; /**< Tableau de bits d'ordonnancement pour le groupe de threads du processus */
LOS_DL_LIST threadList; /**< Liste des threads sous ce processus */
LOS_DL_LIST priorityQueueList[OS_PRIORITY_QUEUE_NUM]; /**< Table de hachage de priorité pour l'ordonnancement du groupe de threads */
volatile UINT32 threadCount; /**< Nombre de threads vivants sous ce processus */
UINT32 totalThreadsCreated; /**< Nombre total de threads créés sous ce processus */
LOS_DL_LIST waitList; /**< Listes d'attente maintenues par le processus pour support wait/waitpid */
UINTPTR signalHandler; /**< Gestionnaire de signaux */
sigset_t signalMask; /**< Masque de partage de signaux */
VirtualMemorySpace *virtualSpace; /**< Espace VMM pour les processus */
FileDescriptor *fileDescriptors; /**< Fichiers détenus par le processus */
timer_t timerID; /**< Identifiant du timer */
} ProcessControlCB;
Il existe deux modes d'exécution pour les processus : mode noyau et mode utilisateur. On peut imaginer que la fonction main crée un processus en mode noyau avec la priorité la plus élevée, qui est KernelProcess.
En utilisant la commande de tâche pour vérifier l'état d'exécution, on peut voir le processus KernelProcess. Son nom indique qu'il s'agit d'un processus noyau, créé au démarrage du système. Le tableau montre que KernelProcess contient plus d'une dizaine de tâches.
Initialisation du module de processus
Dans l'histoire de M. Zhang, KernelProcess est équivalent au personnel du site. Ils doivent également être ordonnancés par M. Zhang pour entrer, mais leur priorité est la plus élevée (niveau 0). Une fois entrés, ils préparent le site avant d'ouvrir au public. Si plusieurs membres du personnel sont nécessaires, ils sont créés via fork. En d'autres termes, il faut d'abord en avoir un, qui est KernelProcess dans HarmonyOS, et les autres membres du personnel sont dérivés de lui.
De même pour les utilisateurs : les personnes qui doivent réellement faire la file sont d'abord créées comme un utilisateur ancestor, et tous les autres utilisateurs en dérivent. Notez que les processus utilisateur et noyau ont des ancêtres distincts : g_userInitProcess (ID 1) et g_kernelInitProcess (ID 2).
/******************************************************************************
Concurrence : Plusieurs threads s'exécutent sur un seul cœur, un seul thread à la fois,
le système commute continuellement entre les threads, donnant l'illusion d'exécution simultanée
Parallélisme : Chaque thread est attribué à un cœur CPU indépendant, les threads s'exécutent simultanément
CPU mono-cœur : Les processus ou threads peuvent réaliser de la concurrence (série microscopique, parallèle macroscopique)
CPU multi-cœurs : Le parallélisme peut être réalisé à la fois macroscopiquement et microscopiquement entre threads
******************************************************************************/
LITE_OS_SEC_BSS ProcessControlCB *g_runningProcess[LOSCFG_KERNEL_CORE_NUM];
LITE_OS_SEC_BSS ProcessControlCB *g_processPool = NULL;
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcessList;
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_recycledProcessList;
LITE_OS_SEC_BSS UINT32 g_userInitProcess = OS_INVALID_VALUE;
LITE_OS_SEC_BSS UINT32 g_kernelInitProcess = OS_INVALID_VALUE;
LITE_OS_SEC_BSS UINT32 g_kernelIdleProcess = OS_INVALID_VALUE;
LITE_OS_SEC_BSS UINT32 g_maxProcessCount;
LITE_OS_SEC_BSS ProcessGroup *g_globalProcessGroup = NULL;
// Initialisation du module de processus
LITE_OS_SEC_TEXT_INIT UINT32 ProcessModuleInit(VOID)
{
UINT32 index;
UINT32 size;
g_maxProcessCount = LOSCFG_BASE_CORE_PROCESS_LIMIT;
size = g_maxProcessCount * sizeof(ProcessControlCB);
g_processPool = (ProcessControlCB *)LOS_MemAlloc(m_aucSysMem1, size);
if (g_processPool == NULL) {
return LOS_NOK;
}
(VOID)memset_s(g_processPool, size, 0, size);
LOS_ListInit(&g_freeProcessList);
LOS_ListInit(&g_recycledProcessList);
for (index = 0; index < g_maxProcessCount; index++) {
g_processPool[index].processID = index;
g_processPool[index].processStatus = OS_PROCESS_FLAG_UNUSED;
LOS_ListTailInsert(&g_freeProcessList, &g_processPool[index].blockedList);
}
g_userInitProcess = 1;
LOS_ListDelete(&g_processPool[g_userInitProcess].blockedList);
g_kernelInitProcess = 2;
LOS_ListDelete(&g_processPool[g_kernelInitProcess].blockedList);
return LOS_OK;
}
Le code est clair : un pool de processus est créé, avec un maximum de 64 processus par défaut. Sans modifier la macro LOSCFG_BASE_CORE_PROCESS_LIMIT, le système supporte jusqu'à 64 processus, mais deux processus sont d'abord utilisés : un pour le mode utilisateur et un pour le mode noyau. Ils sont les racines des processus ultérieurs, ne laissant que 62 processus disponibles pour d'autres créations. À la fin du code, les listes bloquées des deux processus racines sont vidées, car il n'y a pas de tâches bloquées.
Création du processus racine en mode noyau
Création du processus "KernelProcess", qui est le processus numéro 2 du pool de processus g_kernelInitProcess, avec la priorité la plus élevée (0).
// Initialisation du processus numéro 2, processus racine noyau
LITE_OS_SEC_TEXT_INIT UINT32 KernelRootProcessInit(VOID)
{
ProcessControlCB *processControl = NULL;
UINT32 ret;
ret = ProcessModuleInit();
if (ret != LOS_OK) {
return ret;
}
processControl = GetProcessByID(g_kernelInitProcess);
ret = CreateInitialProcess(processControl, OS_KERNEL_MODE, "KernelProcess", 0);
if (ret != LOS_OK) {
return ret;
}
processControl->processStatus &= ~OS_PROCESS_STATUS_INIT;
g_globalProcessGroup = processControl->group;
LOS_ListInit(&g_globalProcessGroup->groupList);
SetCurrentProcess(processControl);
return CreateIdleProcess();
}
// Création d'un processus nommé "Idle" pour utiliser quand le CPU est inactif
STATIC UINT32 CreateIdleProcess(VOID)
{
UINT32 ret;
CHAR *idleName = "Idle";
ProcessControlCB *idleProcess = NULL;
Percpu *cpuData = GetCpuData();
UINT32 *idleTaskID = &cpuData->idleTaskID;
ret = CreateResourceReclaimTask();
if (ret != LOS_OK) {
return ret;
}
ret = LOS_Fork(CLONE_FILES, "Idle", (TASK_ENTRY_FUNC)IdleTaskFunction, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE);
if (ret < 0) {
return LOS_NOK;
}
g_kernelIdleProcess = (UINT32)ret;
idleProcess = GetProcessByID(g_kernelIdleProcess);
*idleTaskID = idleProcess->threadGroupID;
GetTaskByID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;
#if (LOSCFG_KERNEL_SMP == YES)
GetTaskByID(*idleTaskID)->cpuAffinityMask = CPUID_TO_AFFINITY_MASK(ArchCurrentCpuid());
#endif
(VOID)memset_s(GetTaskByID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, 0, OS_TCB_NAME_LEN);
(VOID)memcpy_s(GetTaskByID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName, strlen(idleName));
return LOS_OK;
}
Création du procsesus racine en mode utilisateur
Création du processus Init, qui est le processus numéro 1 du pool de processus g_userInitProcess, avec une priorité de 28.
/**
* @ingroup los_process
* Priorité par défaut du processus racine en mode utilisateur
*/
#define USER_PROCESS_INIT_PRIORITY 28
LITE_OS_SEC_TEXT_INIT UINT32 UserRootProcessInit(VOID)
{
INT32 ret;
UINT32 size;
TASK_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;
ProcessControlCB *processControl = GetProcessByID(g_userInitProcess);
ret = CreateInitialProcess(processControl, OS_USER_MODE, "Init", USER_PROCESS_INIT_PRIORITY);
if (ret != LOS_OK) {
return ret;
}
userText = LOS_PhysicalPagesAllocContiguous(initSize >> PAGE_SHIFT);
if (userText == NULL) {
ret = LOS_NOK;
goto ERROR;
}
(VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);
ret = LOS_VirtualToPhysicalAddressMap(processControl->virtualSpace, (VADDR_T)(UINTPTR)userInitTextStart,
LOS_PhysicalAddressQuery(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 = UserInitStackAllocation(g_userInitProcess, &size);
if (stack == NULL) {
PRINT_ERR("user init process malloc user stack failed!\n");
ret = LOS_NOK;
goto ERROR;
}
param.pfnTaskEntry = (TASK_ENTRY_FUNC)userInitTextStart;
param.userParam.userSP = (UINTPTR)stack + size;
param.userParam.userMapBase = (UINTPTR)stack;
param.userParam.userMapSize = size;
param.uwReserved = OS_TASK_FLAG_PTHREAD_JOIN;
ret = StartUserInitProcess(g_userInitProcess, ¶m);
if (ret != LOS_OK) {
(VOID)LOS_Unmap(processControl->virtualSpace, param.userParam.userMapBase, param.userParam.userMapSize);
goto ERROR;
}
return LOS_OK;
ERROR:
(VOID)LOS_PhysicalPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);
ProcessControlDeinit(processControl);
return ret;
}