Confucius in the township party, pye Xun such as also, like can not speak. Its in the ancestral temple court, poop speech, only respectfully er. The Analects of Confucius: Township Party chapter

A hundred blog series. This is:

V45. Xx HongMeng kernel source code analysis (Fork) | one call, return twice

Process management:

  • V02. Xx HongMeng kernel source code analysis (process management) | who in the management of core resources
  • V24. Xx HongMeng kernel source code analysis concept (process) | process in which resources management
  • V45. Xx HongMeng kernel source code analysis (Fork) | one call, return twice
  • V46. Xx HongMeng kernel source code analysis (special) | mouse son can make hole
  • V47. Xx HongMeng kernel source code analysis (process recycling) | how to entrust an orphan to ancestor dying
  • V48. Xx HongMeng kernel source code analysis (signal) | the year lead half hundred, still energetic
  • V49. Xx HongMeng kernel source code analysis (signal) | who let CPU run for four times in the stack
  • V71. Xx HongMeng kernel source code analysis (Shell editor) | two tasks, three stages
  • V72. Xx HongMeng kernel source code analysis (Shell) | watching the kernel of the application window

The first time I saw fork, it was one call and two returns, and I was confused. Because this is enough to change the way we think about functions, function calls can also be played in such a way that the parent process calls once and the parent process returns once. And you can only tell which process is returning by the return value. So there are a couple of questions that are always on my mind.

  • What is fork? How to use it correctly externally.
  • Why fork? What is the nature and benefit of fork?
  • How did you do that? Call fork() to make both parent and child processes return twice. What happened?
  • whypid = 0Does it represent the return of the child process? Why doesn’t the parent process need to return 0?

I didn’t understand until I looked at the Linux kernel source code, but the positioning of this series is to dig through the kernel source code, so this article will delve into the fork function, using the kernel source code to explain these problems. It is advisable to read the rest of the series before reading this one. Such as (task switch, register, work mode, system call, etc.), with these foundations, it is easy to understand the implementation process of fork.

What is the fork

Let’s start with a snippet of code that’s often mentioned on the web as fork.

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	pid_t pid;
	char *message;
	int n;
	pid = fork();
	if (pid < 0) {
		perror("fork failed");
		exit(1);
	}
	if (pid == 0) {
		message = "This is the child\n";
		n = 6;
	} else {
		message = "This is the parent\n";
		n = 3;
	}
	for(; n > 0; n--) {
		printf(message);
		sleep(1);
	}
	return 0;
}
Copy the code
  • pid < 0The fork failure
  • pid == 0Fork succeeds, which is the return of the child process
  • pid > 0Fork succeeds, which is the return of the parent process
  • forkIt makes sense to specify the return value of.forkReturns 0 in the child, which can still be calledgetpidThe function gets its own process ID and can also be calledgetppidThe function gets the ID of the parent process. Used in the parent processgetpidYou can get your own process ID, but to get the child process ID, you can only get the child process IDforkThere is no other way.
  • The child process did not actually executefork()Instead, the kernel took the return value in a very clever way and forcibly overwrote it to 0forkThe best part of the implementation.

The results

$ ./a.out This is the child This is the parent This is the child This is the parent This is the child This is the parent  This is the child $ This is the child This is the childCopy the code

The running process of this program is shown below.

Interpretation of the

  • Fork () is a system call, so it switches to SVC mode. In SVC stack, the parent process copies a child process, and the PCB information of the parent process and child process is the same, as well as the user mode code and data.

  • As can be seen from the execution of the case, the parent process will execute after the fork, i.e. the code pointing (to the PC register) is the same. Fork is actually called only once by the parent process. The child process does not execute fork, but gets a return value pid == 0, which is very important. That is the focus of this illustration.

  • The parent prints This is the parent three times because n = 3. The child prints This is the child six times because n = 6. The subroutine does not execute the following code:

        pid_t pid;
        char *message;
        int n;
    Copy the code

    The child process starts from pid = fork(). Normally, it will not appear in the new task stack. In fact, it can use these variables successfully later, indicating that the parent process’s current user state data is also copied to the child process’s new task stack.

  • The first instruction to be run by a fork child is PID = 0, where 0 is the return value stored in register R0. Note The task context of the parent process is also copied. When the parent process returns from kernel mode to user mode, the context recovered by the parent process is the same as the task context of the child process, that is, the PC register points to the same, so that the code can be executed at the same location.

  • Sleep (1) is a task that gives up the right to use the CPU and suspends itself in the task waiting list. The parent’s sleep(1) is cut to the child and back again until n = 0 terminates the parent process.

  • But this example and my interpretation only explain what fork is and guess what it does, not why it does it or how the code is implemented. Formal combination of hongmeng source code to say why and how to do these two issues?

Why fork

The fork function is called once and returned twice, once in the parent process and once in the child process. As you can see in the figure above, we started with one control flow, but after the call to fork, we have two control flows, hence the name “fork”. With more than 40 articles written, it’s easy to understand that a program needs resources (memory, files, IPC, monitoring information, etc.) to run, resources need to be managed, and processes are containers for managing resources. These resources are equivalent to the work needs a variety of tools, tools are almost the same, there is no need to go through the process of one application, and the application will be found and others have the same tools, others have directly to use it does not smell? So the easiest way is to identify a father, and ask the father to make a copy of the work tools for you. All you need to do is focus on the task.

How is fork implemented?

/ / the fork system call, suggested to https://gitee.com/weharmony/kernel_liteos_a_note fork? : P
int SysFork(void)
{
    returnOsClone (CLONE_SIGHAND,0.0);// The essence is cloning
}
LITE_OS_SEC_TEXT INT32 OsClone(UINT32 flags, UINTPTR sp, UINT32 size)
{
    UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_VM;

    if (flags & (~cloneFlag)) {
        PRINT_WARN("Clone dont support some flags! \n");
    }

    returnOsCopyProcess (cloneFlag & flags,NULL, sp, size); }STATIC INT32 OsCopyProcess(UINT32 flags,constUINTPTR SP, UINT32 Size)
{UINT32 intSave, RET, processID; LosProcessCB *run = OsCurrProcessGet();// Get the current process

    LosProcessCB *child = OsGetFreePCB();// Apply for a process control block from the process pool. The default value is 64
    if (child == NULL) {
        return -LOS_EAGAIN;
    }
    processID = child->processID;

    ret = OsForkInitPCB(flags, child, name, sp, size);// Initialize the process control block
    if(ret ! = LOS_OK) {goto ERROR_INIT;
    }

    ret = OsCopyProcessResources(flags, child, run);// Copy process resources, including virtual space, files, security, IPC ==
    if(ret ! = LOS_OK) {gotoERROR_TASK; } ret = OsChildSetProcessGroupAndSched (child, run);// Set the process group and join the process scheduling ready queue
    if(ret ! = LOS_OK) {goto ERROR_TASK;
    }

    LOS_MpSchedule(OS_MP_CPU_ALL);// Send a signal to each CPU to be ready to accept scheduling
    if (OS_SCHEDULER_ACTIVE) {// The current CPU core is active
        LOS_Schedule();// request scheduling
    }

    return processID;

ERROR_TASK:
    SCHEDULER_LOCK(intSave);
    (VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave);
ERROR_INIT:
    OsDeInitPCB(child);
    return-ret; } # # #OsForkInitPCB
STATIC UINT32 (UINT32 FLAGS, LosProcessCB *child,constUINTPTR SP, UINT32 Size)
{
    UINT32 ret;
    LosProcessCB *run = OsCurrProcessGet();// Get the current processRet = OsInitPCB(child, run->processMode, OS_PROCESS_PRIORITY_LOWEST, LOS_SCHED_RR, name);// Initialize PCB information, process mode, priority, scheduling mode, name == information
    if(ret ! = LOS_OK) {return ret;
    }

    ret = OsCopyParent(flags, child, run);// Copy your father's genetic information
    if(ret ! = LOS_OK) {return ret;
    }

    return OsCopyTask(flags, child, name, sp, size);// Copy the task, set the task entry function, stack size
}
// Initialize the PCB block
STATIC UINT32 OsInitPCB(LosProcessCB *processCB, UINT32 Mode, UINT16 priority, UINT16 policy,const CHAR *name)
{
    UINT32 count;
    LosVmSpace *space = NULL;
    LosVmPage *vmPage = NULL;
    status_t status;
    BOOL retVal = FALSE;

    processCB->processMode = mode;						// User process or kernel process
    processCB->processStatus = OS_PROCESS_STATUS_INIT;	// The initial state of the process
    processCB->parentProcessID = OS_INVALID_VALUE;		// Dad process, externally specified
    processCB->threadGroupID = OS_INVALID_VALUE;		// Belongs to a thread group
    processCB->priority = priority;						// Process priority
    processCB->policy = policy;							// Scheduling algorithm LOS_SCHED_RR
    processCB->umask = OS_PROCESS_DEFAULT_UMASK;		/ / mask
    processCB->timerID = (timer_t)(UINTPTR)MAX_INVALID_TIMER_VID;

    LOS_ListInit(&processCB->threadSiblingList);// Initialize the child task/thread list with the child threads suspended from this fork see OsTaskCBInit LOS_ListTailInsert(&(processCB->threadSiblingList),  &(taskCB->threadList));
    LOS_ListInit(&processCB->childrenList);		OsCopyParent LOS_ListTailInsert(&parentProcessCB->childrenList,  &childProcessCB->siblingList);
    LOS_ListInit(&processCB->exitChildList);	See OsProcessNaturalExit LOS_ListTailInsert(&parentCB->exitChildList,  &processCB->siblingList);
    LOS_ListInit(&(processCB->waitList));		/ / initialize waiting for the task list Hanging above are in a waiting in OsWaitInsertWaitLIstInOrder LOS_ListHeadInsert (& processCB - > waitList, & runTask - > pendList);

    for (count = 0; count < OS_PRIORITY_QUEUE_NUM; ++count) { // Create queues based on the number of priorities
        LOS_ListInit(&processCB->threadPriQueueList[count]); // Initialize a queue of ready threads/tasks
    }/ / in HongMeng kernel task is the thread, in HongMeng series article has detailed source code analysis See https://my.oschina.net/u/3751245

    if (OsProcessIsUserMode(processCB)) {// Whether it is a user mode processSpace = LOS_MemAlloc (m_aucSysMem0,sizeof(LosVmSpace));// Allocate a virtual space
        if (space == NULL) {
            PRINT_ERR("%s %d, alloc space failed\n", __FUNCTION__ __LINE__);return LOS_ENOMEM;
        }
        VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);// Allocate a physical page to store the L1 table.
        if (ttb == NULL) {// Get the physical page TTB directly
            PRINT_ERR("%s %d, alloc ttb or space failed\n", __FUNCTION__ __LINE__); (VOID) LOS_MemFree (m_aucSysMem0, space);returnLOS_ENOMEM; } (VOID)memset_s(TTB, PAGE_SIZE,0The PAGE_SIZE);// The memory is clearRetVal = OsUserVmSpaceInit(space, TTB);// Initialize the virtual space and process MMU
        vmPage = OsVmVaddrToPage(ttb);// Get the page from the virtual address
        if ((retVal == FALSE) || (vmPage == NULL)) {// Exception handling
            PRINT_ERR("create space failed! ret: %d, vmPage: %#x\n", retVal vmPage); processCB->processStatus = OS_PROCESS_FLAG_UNUSED;// The process is unused and clean(VOID) LOS_MemFree (m_aucSysMem0, space);// Free virtual spaceLOS_PhysPagesFreeContiguous (TTB,1);// Free physical page, 4K
            return LOS_EAGAIN;
        }
        processCB->vmSpace = space;// Set it to process virtual spaceLOS_ListAdd (& processCB - > vmSpace - > archMmu. PtList, & (vmPage - > node));// Attach the space mapping page table to the mmU L1 page table of the space. L1 is the header of the table
    } else {
        processCB->vmSpace = LOS_GetKVmSpace();// The kernel shares a virtual space, and kernel processes reside in memory
    }

#ifdef LOSCFG_SECURITY_VID
    status = VidMapListInit(processCB);
    if(status ! = LOS_OK) { PRINT_ERR("VidMapListInit failed! \n");
        return LOS_ENOMEM;
    }
#endif
#ifdef LOSCFG_SECURITY_CAPABILITY
    OsInitCapability(processCB);
#endif

    if(OsSetProcessName (processCB, name)! = LOS_OK) {return LOS_ENOMEM;
    }

    return LOS_OK;
}
Copy the code
// Copy a Task procedure
STATIC UINT32 OsCopyTask(UINT32 FLAGS, LosProcessCB *childProcessCB,const CHAR *name, UINTPTR entry, UINT32 size)
{
    LosTaskCB *childTaskCB = NULL;
    TSK_INIT_PARAM_S childPara = { 0 };
    UINT32 ret;
    UINT32 intSave;
    UINT32 taskID;

    OsInitCopyTaskParam(childProcessCB, name, entry, size, &childPara);// Initialize Task parametersRet = LOS_TaskCreateOnly(&taskID, &childPara);// Create tasks without scheduling them
    if(ret ! = LOS_OK) {if (ret == LOS_ERRNO_TSK_TCB_UNAVAILABLE) {
            return LOS_EAGAIN;
        }
        return LOS_ENOMEM;
    }

    childTaskCB = OS_TCB_FROM_TID(taskID);// Get the task entity through taskId
    childTaskCB->taskStatus = OsCurrTaskGet()->taskStatus;// The task status is synchronized first. Note that this is the assignment operation.... 01101001
    if (childTaskCB->taskStatus & OS_TASK_STATUS_RUNNING) {// There can only be one running task, so if it is the same, change the 4 bit
        childTaskCB->taskStatus &= ~OS_TASK_STATUS_RUNNING;// Change bit 4 to... 01100001
    } else {// What happens in the non-running state?
        if (OS_SCHEDULER_ACTIVE) {// The clone thread failed to run due to an error
            LOS_Panic("Clone thread status not running error status: 0x%x\n"ChildTaskCB - > taskStatus); } childTaskCB->taskStatus &= ~OS_TASK_STATUS_UNUSED;// Clean Task
        childProcessCB->priority = OS_PROCESS_PRIORITY_LOWEST;// Set the process to the lowest priority
    }

    if (OsProcessIsUserMode(childProcessCB)) {// Is it a user processSCHEDULER_LOCK(intSave); OsUserCloneParentStack (childTaskCB, OsCurrTaskGet ());// Copy the current task context to the new task
        SCHEDULER_UNLOCK(intSave);
    }
    OS_TASK_PRI_QUEUE_ENQUEUE(childProcessCB, childTaskCB);// Add task to the ready queue of the child process
    childTaskCB->taskStatus |= OS_TASK_STATUS_READY;// The task status is labeled ready
    return LOS_OK;
}
// Clone parent task context to child task
LITE_OS_SEC_TEXT VOID OsUserCloneParentStack(LosTaskCB *childTaskCB, LosTaskCB *parentTaskCB)
{
    TaskContext *context = (TaskContext *)childTaskCB->stackPointer;
    VOID *cloneStack = (VOID *)(((UINTPTR)parentTaskCB->topOfStack + parentTaskCB->stackSize) - sizeof(TaskContext));
	/ / point cloneStack TaskContext
    LOS_ASSERT(parentTaskCB->taskStatus & OS_TASK_STATUS_RUNNING);// The current task must be a running task(VOID) memcpy_s (childTaskCB - > stackPointer,sizeof(TaskContext), cloneStack,sizeof(TaskContext));// Copy the task context directly
    context->R[0] = 0;Pid = fork() pid == 0
}
Copy the code

Interpretation of the

  • The system call is passedCLONE_SIGHANDTo create a child process. The specific creation methods are 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, including root, current directory, and umask
      #define CLONE_FILES    0x00000400	// The child process shares the same file descriptor table as the parent process
      #define CLONE_SIGHAND  0x00000800	// The child process shares the same signal handler table as the parent process
      #define CLONE_PTRACE   0x00002000	If the parent process is traced, the child process is also traced
      #define CLONE_VFORK    0x00004000	// The parent process is suspended until the child process releases virtual memory resources
      #define CLONE_PARENT   0x00008000	// The parent of the created child process is the parent of the caller. The new process becomes "brother" rather than "parent" to the process that created it.
      #define CLONE_THREAD   0x00010000	// Added in Linux 2.4 to support THE POSIX thread standard. Child processes share the same thread group as parent processes
    Copy the code

    Processes send signals to each other for asynchronous communication. There is a special section on signals in this series.

  • You can see that the body function of fork isOsCopyProcess, first apply for a clean PCB, which is equivalent to applying for a container to hold resources.
  • Initialize the containerOsForkInitPCB.OsInitPCBFirst clean the container, virtual space, address mapping table (L1 table), all linked list initialization, ready for the following content copy.
  • OsCopyParentPassing on family genes/relationships to child processes, who your ancestor is, who your sister-in-law is, will be on the list you have initialized.
  • OsCopyTaskIt is important to copy the parent’s current task data to the child’s new task. As mentioned in this series, it is the task (thread) that really does the CPU’s work, so the child needs to create a new taskLOS_TaskCreateOnlyTo accept the data of the current task, which includes stack data, running code snippet pointing to,OsUserCloneParentStackPut user – mode context dataTaskContextCopy to the bottom of the stack of the new task in the child process, that is, the new task is running stack only context data. And it has the single most important line of codecontext->R[0] = 0;It is mandatory to recover the context in the futureR0The data in the register is changed to 0, which means that after the scheduling algorithm cuts to the child process’s task, the first thing the task does is restore the contextR0The value of the register becomes 0, andR0=0What does it mean? At the same timeLR/SPThe register values are the same as those of the parent process. What does that mean?
  • The return value is stored in register R0.A()->B(), A recognizes only the return value of BR0The R0 register value is equal to 0, which is equivalent to the return value of 0, and the LR register points to the instructionPid = return valuesThe SP register records the position in the stack where the calculation starts, thus fully restoring the parent process callfork()The only difference before the run scene is changedR0The value of the register, that’s why we have it
    pid = 0;// return value of fork(), note that the child process does not perform fork(), it just retrieves a return value by restoring the context.
    if (pid == 0) {
      	message = "This is the child\n";
      	n = 6;
      }
    Copy the code

    This ensures that this is the return of the child process. This is afork()The best part. Be sure to understand.OsCopyTask``OsUserCloneParentStackIt’ll blow your mind, and you’ll never forget it.

  • The return of the parent isprocessID = child->processID;Is the ID of the child process. The ID of any child process cannot be equal to 0, but must be greater than 0 if it succeeds. Failure is a negative numberreturn -ret;
  • OsCopyProcessResourcesUsed to assign various resources, including copy virtual space memory, copy open file list, IPC, and so on.
  • OsChildSetProcessGroupAndSchedSet subprocess groups and scheduling preparations, join the scheduling queue, and prepare for scheduling.
  • LOS_MpScheduleIt is an intercore interrupt that sends a scheduling signal to all cpus, causing all cpus to schedule once. The parent cedes CPU usage because the child’s scheduling priority is the same as the parent’s, while the child’s task is already at the head of the ready queueOS_PROCESS_PRI_QUEUE_ENQUEUEThe parent process takes precedence over the parent process, so the next scheduled task is the child process’s task until no other process or task of higher priority appears. That’s what we saw at the beginning of this article
    $ ./a.out This is the child This is the parent This is the child This is the parent This is the child This is the parent  This is the child $ This is the child This is the childCopy the code
  • The above is the whole implementation process of fork in the Hongmeng kernel, be sure to combine with the other parts of the series of understanding, once a thorough understanding, never forget.

Intensive reading of the kernel source code

Four code stores synchronous annotation kernel source code, >> view the Gitee repository

Analysis of 100 blogs. Dig deep into the core

Add comments to hongmeng kernel source code process, sort out the following article. Content based on the source code, often in life scene analogy as much as possible into the kernel knowledge of a scene, with a pictorial sense, easy to understand memory. It’s important to speak in a way that others can understand! The 100 blogs are by no means a bunch of ridiculously difficult concepts being put forward by Baidu. That’s not interesting. More hope to make the kernel become lifelike, feel more intimate. It’s hard, it’s hard, but there’s no turning back. 😛 and code bugs need to be constantly debug, there will be many mistakes and omissions in the article and annotation content, please forgive, but will be repeatedly amended, continuous update. Xx represents the number of modifications, refined, concise and comprehensive, and strive to create high-quality content.

Compile build The fundamental tools Loading operation Process management
Compile environment

The build process

Environment script

Build tools

Designed.the gn application

Ninja ninja

Two-way linked list

Bitmap management

In the stack way

The timer

Atomic operation

Time management

The ELF format

The ELF parsing

Static link

relocation

Process image

Process management

Process concept

Fork

Special process

Process recycling

Signal production

Signal consumption

Shell editor

Shell parsing

Process of communication Memory management Ins and outs Task management
spinlocks

The mutex

Process of communication

A semaphore

Incident control

The message queue

Memory allocation

Memory management

Memory assembly

The memory mapping

Rules of memory

Physical memory

Total directory

Scheduling the story

Main memory slave

The source code comments

Source structure

Static site

The clock task

Task scheduling

Task management

The scheduling queue

Scheduling mechanism

Thread concept

Concurrent parallel

The system calls

Task switching

The file system Hardware architecture
File concept

The file system

The index node

Mount the directory

Root file system

Character device

VFS

File handle

Pipeline file

Compilation basis

Assembly and the cords

Working mode

register

Anomaly over

Assembly summary

Interrupt switch

Interrupt concept

Interrupt management

HongMeng station | into a little bit every day, the original is not easy, welcome to reprint, please indicate the source.