Hongmeng kernel source code analysis (special process chapter) | dragon born dragon, phoenix born chicken, mouse born child can make holes | 100 blog analysis HarmonyOS source code | v46 01

Posted by dragin33 on Wed, 09 Mar 2022 11:09:34 +0100

Million Chinese character annotation > > intensive reading kernel source code, Chinese annotation analysis, deep foundation engineering, permanent brain memory, and four code warehouses are updated synchronously every day< gitee | github | csdn | coding >

One hundred blog Analysis > > story telling kernel, question and answer guide, life style metaphor, tabular description, graphical display, and regular updates of mainstream websites< oschina | csdn | harmony >

Three processes

Hongmeng has three special processes, which are created in the following order:

  • Process 2, KProcess, is a kernel root process Created during startup
  • Process 0, KIdle is the second process in kernel mode, which comes from KProcess fork It's a little hard to understand
  • Process 1, init, is the user state root process Created by task SystemInit
  • It is found that process 0 is not visible in the figure. After reading this article, please think about why?

Family management

  • Process is a family management, which is divided into two families: user state family and kernel state family
  • The process of user status is the civilian class, working in all walks of life, with limited rights, large number and limited scope of activities Zhongnanhai is definitely not allowed to enter or leave at will This class has a common ancestor G_ Userinitprocess (process 1)
    g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///Root process in user mode
    //Get the root process of user state process. All user processes are g_processCBArray[g_userInitProcess] fork
    LITE_OS_SEC_TEXT UINT32 OsGetUserInitProcessID(VOID)
    {
        return g_userInitProcess;
    }
    
  • The process of kernel state is aristocratic class, managing civilian class and maintaining civilian life order. It has super authority and a small number of people The ancestor of this class is g_ Kernelinitprocess (process 2)
    g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///Kernel root process
    //Get the root process of the kernel state process. All kernel processes are g_processCBArray[g_kernelInitProcess] fork, including g_processCBArray[g_kernelIdleProcess] process
    LITE_OS_SEC_TEXT UINT32 OsGetKernelInitProcessID(VOID)
    {
        return g_kernelInitProcess;
    }
    
  • Can these two classes flow with each other? Is there an opportunity to change their fate through the college entrance examination? The answer is: absolutely impossible!!! A dragon begets a dragon, a phoenix begets a Phoenix, and a mouse begets a son. He can make a hole It has been engraved in the gene since the founding of our ancestors Because all the processes were cloned by these two old comrades and inherited this gene Los processcb has a special label to distinguish the two levels of processMode The whole Hongmeng kernel source code does not provide a set function to change the chance of fate
      #define OS_KERNEL_MODE 0x0U 	// Kernel state
      #define OS_USER_MODE   0x1U 	// User state
      STATIC INLINE BOOL OsProcessIsUserMode(const LosProcessCB *processCB)//User mode process
      {
          return (processCB->processMode == OS_USER_MODE);
      }
      typedef struct ProcessCB {
          // ...
          UINT16               processMode;                  /**< Kernel Mode:0; User Mode:1; */	//Specifies whether the mode is kernel or user process
      } LosProcessCB;    
    

Process 2 KProcess

Process 2 is the ancestor of kernel state and the first process created by kernel. The source code process is as follows, and irrelevant code is omitted

bl     main  @belt LR Subroutine jump, LR = pc - 4 ,implement C layer main function
/******************************************************************************
Kernel entry function, called by assembly, can be found in reset_vector_up.S and reset_vector_mp.S
up Refers to single core CPU, mp refers to multi-core CPU, BL main
******************************************************************************/
LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//It is executed by the main CPU. By default, CPU 0 is the main CPU 
{
    // ...  ellipsis
    uwRet = OsMain();// Initialization of kernel modules
}
LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID)
{
    // ... 
    ret = OsKernelInitProcess();// Create kernel root process
    // ...
    ret = OsSystemInit(); //The user status root process is created in the middle
}
//Initialize process 2, the ancestor of kernel state process
LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID)
{
    LosProcessCB *processCB = NULL;
    UINT32 ret;

    ret = OsProcessInit();// Initialize all variables of the process module and create a circular two-way linked list
    if (ret != LOS_OK) {
        return ret;
    }

    processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// Get a process in PID mode
    ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// The highest priority of initialization process is 0. Hongmeng process has a total of 32 priorities (0-31), of which 0-9 are kernel processes, and the configurable priorities of user processes are 22 (10-31)
    if (ret != LOS_OK) {
        return ret;
    }

    processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// Process initialization location 1
    g_processGroup = processCB->group;//The global process group points to the process group where KProcess is located
    LOS_ListInit(&g_processGroup->groupList);// Process group linked list initialization
    OsCurrProcessSet(processCB);// Set as current process
    return OsCreateIdleProcess();// Create an idle process
}

unscramble

  • The main function will be discussed separately in the series. Please check it by yourself. It is created in SVC mode at the beginning of startup
  • The ancestor of kernel state is called KProcess, with the highest priority of level 0 The "KProcess" process is long-term and active, and many important tasks will run under it For example:
    • Swt_Task
    • oom_task
    • system_wq
    • tcpip_thread
    • SendToSer
    • SendToTelnet
    • eth_irq_task
    • TouchEventHandler
    • USB_GIANT_Task
      These tasks are not discussed in detail here. They are introduced in other sections, but you can guess eight or nine by just looking at the name. Please look through them yourself
  • Then KProcess with CLONE_FILES fork s a child process named "KIdle"
  • All processes in the kernel state come from process 2. This old comrade, his children and grandchildren, are passed down from generation to generation to form a family tree. Different from human inheritance, they are often white haired people to black haired people, and the processes of their children and grandchildren are often short-lived ghosts. The old ancestors can live best, their children and grandchildren are dead, and they are still there. Some corpse collection work needs to be done by them

Process 0 KIdle

Process 0 is the first process created by the kernel. After process 2 is set as the current process at the end of OsKernelInitProcess, process 0 is forked immediately Why do you have to set the current process first, because fork needs a parent process. At this time, the system is in the startup stage and there is no current process Yes, you are right Process is a concept derived from the operating system to facilitate the management of resources. The system does not need processes and tasks to run There is nothing in the boot phase. It runs in svc mode by default, and the entry address reset is specified by default_ Vectors are specified after the hardware is powered on Process and thread are given the meaning slowly after running. OsCurrProcessSet gives the concept of current process from the software level This is the first current process set by the kernel

//Create a 0 process named "KIdle" to be used when the CPU is idle
STATIC UINT32 OsCreateIdleProcess(VOID)
{
    UINT32 ret;
    CHAR *idleName = "Idle";
    LosProcessCB *idleProcess = NULL;
    Percpu *perCpu = OsPercpuGet();
    UINT32 *idleTaskID = &perCpu->idleTaskID;//Get the idle task of CPU

    ret = OsCreateResourceFreeTask();// Create a resource recycling task with priority of 5 to recycle various resources when the process exits
    if (ret != LOS_OK) {
        return ret;
    }
	//When the CPU creates an idle process called "idle task", it waits for a process called "idle task"
    ret = LOS_Fork(CLONE_FILES, "KIdle", (TSK_ENTRY_FUNC)OsIdleTask, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE);
    if (ret < 0) {//The fork of the kernel process will not be called once and returned twice. The starting position of the execution of this sub process is the parameter OsIdleTask
        return LOS_NOK;
    }
    g_kernelIdleProcess = (UINT32)ret;//Return to process 0

    idleProcess = OS_PCB_FROM_PID(g_kernelIdleProcess);//Get the process entity through ID
    *idleTaskID = idleProcess->threadGroupID;//Bind the IdleTask of the CPU, or change the existing idle task of the CPU
    OS_TCB_FROM_TID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//Set Idle task as a system task
#if (LOSCFG_KERNEL_SMP == YES)
    OS_TCB_FROM_TID(*idleTaskID)->cpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//Task assignment of multi-core CPU to prevent disorderly string. Pay attention to multi-core before parallel processing
#endif
    (VOID)memset_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, 0, OS_TCB_NAME_LEN);//Clear 0 first for task name
    (VOID)memcpy_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName, strlen(idleName));//The name of the task is idle
    return LOS_OK;
}

unscramble

  • After reading the fork article, you may find a parameter. The way KIdle is created is different from that through system call. One uses CLONE_FILES, one is clone_ The specific creation method of sighand is as follows:
      #define CLONE_VM       0x00000100 	// The child process runs in the same memory space as the parent process
      #define CLONE_FS       0x00000200 	// The child process shares the same file system as the parent process, including root, current directory and umask
      #define CLONE_FILES    0x00000400 	// The child process and the parent process share the same file descriptor table
      #define CLONE_SIGHAND  0x00000800 	// The child process and the parent process share the same signal handler table
      #define CLONE_PTRACE   0x00002000 	// If the parent process is tracked, the child process is also tracked
      #define CLONE_VFORK    0x00004000 	// The parent process is suspended until the child process releases virtual memory resources
      #define CLONE_PARENT   0x00008000 	// The parent process of the created child process is the parent process of the caller. The new process and the process that created it become "brothers" rather than "parents and children"
      #define CLONE_THREAD   0x00010000 	// Added in Linux 2.4 to support POSIX thread standard. Child processes and parent processes share the same thread group
    
  • KIdle creates a task named Idle. The entry function of the task is OsIdleTask. This is an Idle task that does nothing It is specially used to give the cpu rest. When the cpu is free, it will stay in this task and wait for work
      LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
      {
          while (1) {//There is only one dead cycle
      #ifdef LOSCFG_KERNEL_TICKLESS / / low power mode switch. Turn off tick in idle task
              if (OsTickIrqFlagGet()) {
                  OsTickIrqFlagSet(0);
                  OsTicklessStart();
              }
      #endif
              Wfi();//WFI instruction: arm core immediately enters low power standby state, waits for interruption and enters sleep mode.
          }
      }
    
  • There is one difference between fork kernel state process and fork user state process, that is, the value of SP register The fork user state process calls and returns twice at a time (once for the parent and child processes), and the return location is the same (because it copies the context when the parent process falls into the kernel) Therefore, the return value can only be used to judge whether the parent or child returns This is described in detail in the fork article Please look through it yourself However, although fork kernel processes return twice, the return location is different. The return location of child processes is specified by the kernel For example, OsIdleTask is the entry function See code for details:
    //Copy task information during task initialization
      STATIC VOID OsInitCopyTaskParam(LosProcessCB *childProcessCB, const CHAR *name, UINTPTR entry, UINT32 size,
                                      TSK_INIT_PARAM_S *childPara)
      {
          LosTaskCB *mainThread = NULL;
          UINT32 intSave;
    
          SCHEDULER_LOCK(intSave);
          mainThread = OsCurrTaskGet();//Get the current task and pay attention to the variable name. It can also be seen from here that thread and task are a concept, but the kernel often says task and the upper application says thread, which is the mapping of the concept
    
          if (OsProcessIsUserMode(childProcessCB)) {//User state process
              childPara->pfnTaskEntry = mainThread->taskEntry;//Copy the current task entry address
              childPara->uwStackSize = mainThread->stackSize;	//Stack space size
              childPara->userParam.userArea = mainThread->userArea;		//Top position of user status stack area
              childPara->userParam.userMapBase = mainThread->userMapBase;	//User status stack bottom
              childPara->userParam.userMapSize = mainThread->userMapSize;	//User state stack size
          } else {//Note that the entry of the kernel process creation task is specified by the outside world. For example, OsCreateIdleProcess specifies OsIdleTask
              childPara->pfnTaskEntry = (TSK_ENTRY_FUNC)entry;//Parameter (sp) is the kernel state entry address
              childPara->uwStackSize = size;//The parameter (size) is the kernel stack size
          }
          childPara->pcName = (CHAR *)name;					//Copy process name
          childPara->policy = mainThread->policy;				//Copy scheduling mode
          childPara->usTaskPrio = mainThread->priority;		//Copy priority 
          childPara->processID = childProcessCB->processID;	//Copy process ID
          if (mainThread->taskStatus & OS_TASK_FLAG_PTHREAD_JOIN) {
              childPara->uwResved = OS_TASK_FLAG_PTHREAD_JOIN;
          } else if (mainThread->taskStatus & OS_TASK_FLAG_DETACHED) {
              childPara->uwResved = OS_TASK_FLAG_DETACHED;
          }
    
          SCHEDULER_UNLOCK(intSave);
      }
    
  • The conclusion is that the OsCreateIdleProcess in process 0 calls LOS_ There will only be one return after fork And the return value is 0 because G_ Process 0 in freeprocess has not been assigned See the code for details, and pay attention to the final comments:
    //The process module is initialized and compiled in the code segment In init
      LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
      {
          UINT32 index;
          UINT32 size;
    
          g_processMaxNum = LOSCFG_BASE_CORE_PROCESS_LIMIT;//64 processes are supported by default
          size = g_processMaxNum * sizeof(LosProcessCB);//Calculate the total size
    
          g_processCBArray = (LosProcessCB *)LOS_MemAlloc(m_aucSysMem1, size);// Process pool, occupied kernel heap, memory pool allocation 
          if (g_processCBArray == NULL) {
              return LOS_NOK;
          }
          (VOID)memset_s(g_processCBArray, size, 0, size);//Safe mode reset clear 0
    
          LOS_ListInit(&g_freeProcess);//Process idle linked list initialization, when creating a process from G_ Apply for a process descriptor in freeprocess
          LOS_ListInit(&g_processRecyleList);//The process recycling linked list is initialized. After the recycling is completed, enter g_freeProcess is waiting to be applied for again
    
          for (index = 0; index < g_processMaxNum; index++) {//Process pool loop creation
              g_processCBArray[index].processID = index;//Assignment of process ID[0-g_processMaxNum-1]
              g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// The default is a piece of white paper with unused labels
              LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//Attention g_freeProcess is attached to the pendList node, so it needs to be used through the OS_PCB_FROM_PENDLIST found the process entity
          }
    
          g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///Root process in user mode
          LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// Remove process 1 from the free linked list
    
          g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///Kernel root process
          LOS_ListDelete(&g_processCBArray[g_kernelInitProcess].pendList);// Remove process 2 from the free linked list
    
          //Note: after this bosao operation, G_ There are also 0, 3, 4 g_ Processmaxnum - process 1 The creation process is from G_ Apply on freeprocess
          //That is, the next application will be process 0, and OsCreateIdleProcess will occupy process 0
    
          return LOS_OK;
      }
    

Process 1 init

Process 1 is the ancestral source code of user status. The process is as follows, and irrelevant code is omitted

LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID)
{
    // ... 
    ret = OsKernelInitProcess();// Create kernel root process
    // ...
    ret = OsSystemInit(); //The user status root process is created in the middle
}
UINT32 OsSystemInit(VOID)
{
    //..
    ret = OsSystemInitTaskCreate();//Created a system task,
}

STATIC UINT32 OsSystemInitTaskCreate(VOID)
{
    UINT32 taskID;
    TSK_INIT_PARAM_S sysTask;

    (VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit;//The entry function of the task. This function implementation is provided externally
    sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K
    sysTask.pcName = "SystemInit";//Name of the task
    sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;// The kernel default priority is 10 
    sysTask.uwResved = LOS_TASK_STATUS_DETACHED;//Task separation mode
#if (LOSCFG_KERNEL_SMP == YES)
    sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//CPU affinity setting, record the CPU that has executed the task, and try to ensure that the same CPU completes the task cycle
#endif
    return LOS_TaskCreate(&taskID, &sysTask);//Create a task and join the ready queue, and immediately participate in the scheduling
}
//The implementation of SystemInit is provided externally, such as \vendor\hi3516dv300\module_init\src\system_init.c
void SystemInit(void)
{
    // ...
    if (OsUserInitProcess()) {//The ancestor of creating user state processes
        PRINT_ERR("Create user init process faialed!\n");
        return;
    }
}
//The creation process of user status root process
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;//Start position of code area, corresponding to LITE_USER_SEC_ENTRY
    CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// The data area (BSS) is not initialized. Change the value corresponding to Lite during operation_ USER_ SEC_ BSS
    CHAR *userInitEnd = (CHAR *)&__user_init_end;// End address
    UINT32 initBssSize = userInitEnd - userInitBssStart;
    UINT32 initSize = userInitEnd - userInitTextStart;

    LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);//"Init process priority is 28"
    ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// Initialize the user process, which will be the parent process of all applications
    if (ret != LOS_OK) {
        return ret;
    }

    userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// Allocate contiguous physical pages
    if (userText == NULL) {
        ret = LOS_NOK;
        goto ERROR;
    }

    (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// The result of security copy through loader load__ user_ init_ load_ addr -> userText
    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);// Mapping between virtual address and physical address
    if (ret < 0) {
        goto ERROR;
    }

    (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// Except for the code segment, the rest are cleared 0

    stack = OsUserInitStackAlloc(g_userInitProcess, &size);//The running stack of the assigned task in the user state is 1M in size
    if (stack == NULL) {
        PRINTK("user init process malloc user stack failed!\n");
        ret = LOS_NOK;
        goto ERROR;
    }

    param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// Execution starts from the code area, which is the location of the main function of the application
    param.userParam.userSP = (UINTPTR)stack + size;// User status stack bottom
    param.userParam.userMapBase = (UINTPTR)stack;// User status stack top
    param.userParam.userMapSize = size;// User state stack size
    param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// joinable can be reclaimed and killed by other threads
    ret = OsUserInitProcessStart(g_userInitProcess, &param);// Create a task to run the main function
    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);//Freeing physical memory blocks
    OsDeInitPCB(processCB);//Delete PCB block
    return ret;
}

unscramble

  • From the code, we can see that the creation process of the ancestor of user mode is a little interesting. First, its source is the same as the ancestor of kernel mode in OsMain
  • This is accomplished by creating a separate mode, priority 10 system task SystemInit The implementation of the task entry function SystemInit() is specified by the platform integrator This paper adopts the implementation of hi3516dv300 That is to say, the ancestor of user status was created in systask uwStackSize = LOSCFG_ BASE_ CORE_ TSK_ DEFAULT_ STACK_ SIZE;// Completed in 16K stack This task belongs to the kernel process KProcess
  • The ancestor of user status is called Init, and the priority is level 28
  • Each process in user mode has an independent virtual process space vmSpace and has independent memory mapping tables (L1 and L2 tables). The applied memory needs to be remapped. The mapping process is described in detail in the memory series
  • Init creates a task whose entry address is__ user_init_entry, specified by the compiler
  • User state process refers to the process that should be run by the program, which is started by dynamically loading ELF file The specific loading process is explained in the series, not in detail User mode processes run in user space, but can fall into kernel space through system calls See this picture for details:

Hongmeng source code 100 blog review

Participation and contribution

If you like, please "like + follow + collect"

Topics: Multithreading Operating System kernel harmonyos