1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
| 后开始执行的,按理它不会在新任务栈中出现这些变量,而实际上后面又能顺利的使用这些变量,说明父进程当前任务的用户态的数据也复制了一份给子进程的新任务栈中. 被fork成功的子进程跑的首条代码指令是 `pid = 0` ,这里的0是返回值,存放在 `R0` 寄存器中。说明父进程的任务上下文也进行了一次拷贝,父进程从内核态回到用户态时恢复的上下文和子进程的任务上下文是一样的,即 PC寄存器指向是一样的,如此才能确保在代码段相同的位置执行. 执行 `./a.out` 后 第一条打印的是 `This is the child` 说明 `fork()` 中发生了一次调度,CPU切到了子进程的任务执行, `sleep(1)` 的本质在系列篇中多次说过是任务主动放弃CPU的使用权,将自己挂入任务等待链表,由此发生一次任务调度,CPU切到父进程执行,才有了打印第二条的 `This is the parent` ,父进程的 `sleep(1)` 又切到子进程如此往返,直到 n = 0, 结束父子进程. 但这个例子和笔者的解读只解释了fork是什么的使用说明书,并猜测其中做了些什么,并没有说明为什么要这样做和代码是怎么实现的. 正式结合鸿蒙的源码说清楚为什么和怎么做这两个问题? ##### 为什么是fork fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。从上图可以看出,一开始是一个控制流程,调用fork之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。 系列篇已经写了40+多篇,已经很容易理解一个程序运行起来就需要各种资源(内存,文件,ipc,监控信息等等),资源就需要管理,进程就是管理资源的容器。这些资源相当于干活需要各种工具一样,干活的工具都差不多,实在没必再走流程一一申请,而且申请下来会发现和别人手里已有的工具都一样, 别人有直接拿过来使用它不香吗? 所以最简单的办法就是认个干爹,让干爹拷贝一份干活工具给你。这样只需要专心的干好活(任务)就行了. fork的本质就是copy,具体看代码. ##### fork怎么实现的? //系统调用之fork ,建议去 https://gitee.com/weharmony/kernel_liteos_a_note fork 一下? :P int SysFork(void) { return OsClone(CLONE_SIGHAND, 0, 0);//本质就是克隆 } 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"); }
return OsCopyProcess(cloneFlag & flags, NULL, sp, size); } STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size) { UINT32 intSave, ret, processID; LosProcessCB *run = OsCurrProcessGet();//获取当前进程
LosProcessCB *child = OsGetFreePCB();//从进程池中申请一个进程控制块,鸿蒙进程池默认64 if (child == NULL) { return -LOS_EAGAIN; } processID = child->processID;
ret = OsForkInitPCB(flags, child, name, sp, size);//初始化进程控制块 if (ret != LOS_OK) { goto ERROR_INIT; }
ret = OsCopyProcessResources(flags, child, run);//拷贝进程的资源,包括虚拟空间,文件,安全,IPC == goto ERROR_TASK; }
ret = OsChildSetProcessGroupAndSched(child, run);//设置进程组和加入进程调度就绪队列 }
LOS_MpSchedule(OS_MP_CPU_ALL);//给各CPU发送准备接受调度信号 if (OS_SCHEDULER_ACTIVE) {//当前CPU core处于活动状态 LOS_Schedule();// 申请调度 }
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函数实现 #### 2.1 OsForkInitPCB函数 STATIC UINT32 (UINT32 flags, LosProcessCB *child, const CHAR *name, UINTPTR sp, UINT32 size) { UINT32 ret;
ret = OsInitPCB(child, run->processMode, OS_PROCESS_PRIORITY_LOWEST, LOS_SCHED_RR, name);//初始化PCB信息,进程模式,优先级,调度方式,名称 == 信息 return ret; }
ret = OsCopyParent(flags, child, run);//拷贝父亲大人的基因信息 }
return OsCopyTask(flags, child, name, sp, size);//拷贝任务,设置任务入口函数,栈大小 } //初始化PCB块 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; //用户态进程还是内核态进程 processCB->processStatus = OS_PROCESS_STATUS_INIT; //进程初始状态 processCB->parentProcessID = OS_INVALID_VALUE; //爸爸进程,外面指定 processCB->threadGroupID = OS_INVALID_VALUE; //所属线程组 processCB->priority = priority; //进程优先级 processCB->policy = policy; //调度算法 LOS_SCHED_RR processCB->umask = OS_PROCESS_DEFAULT_UMASK; //掩码 processCB->timerID = (timer_t)(UINTPTR)MAX_INVALID_TIMER_VID;
LOS_ListInit(&processCB->threadSiblingList);//初始化孩子任务/线程链表,上面挂的都是由此fork的孩子线程 见于 OsTaskCBInit LOS_ListTailInsert(&(processCB->threadSiblingList), &(taskCB->threadList)); LOS_ListInit(&processCB->childrenList); //初始化孩子进程链表,上面挂的都是由此fork的孩子进程 见于 OsCopyParent LOS_ListTailInsert(&parentProcessCB->childrenList, &childProcessCB->siblingList); LOS_ListInit(&processCB->exitChildList); //初始化记录退出孩子进程链表,上面挂的是哪些exit 见于 OsProcessNaturalExit LOS_ListTailInsert(&parentCB->exitChildList, &processCB->siblingList); LOS_ListInit(&(processCB->waitList)); //初始化等待任务链表 上面挂的是处于等待的 见于 OsWaitInsertWaitLIstInOrder LOS_ListHeadInsert(&processCB->waitList, &runTask->pendList);
for (count = 0; count < OS_PRIORITY_QUEUE_NUM; ++count) { //根据 priority数 创建对应个数的队列 LOS_ListInit(&processCB->threadPriQueueList[count]); //初始化一个个线程队列,队列中存放就绪状态的线程/task }//在鸿蒙内核中 task就是thread,在鸿蒙源码分析系列篇中有详细阐释 见于 https://my.oschina.net/u/3751245
if (OsProcessIsUserMode(processCB)) {// 是否为用户模式进程 space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));//分配一个虚拟空间 if (space == NULL) { PRINT_ERR("%s %d, alloc space failed\n", __FUNCTION__, __LINE__); return LOS_ENOMEM; } VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);//分配一个物理页用于存储L1页表 4G虚拟内存分成 (4096*1M) if (ttb == NULL) {//这里直接获取物理页ttb PRINT_ERR("%s %d, alloc ttb or space failed\n", __FUNCTION__, __LINE__); (VOID)LOS_MemFree(m_aucSysMem0, space); } (VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);//内存清0 retVal = OsUserVmSpaceInit(space, ttb);//初始化虚拟空间和进程mmu vmPage = OsVmVaddrToPage(ttb);//通过虚拟地址拿到page if ((retVal == FALSE) || (vmPage == NULL)) {//异常处理 PRINT_ERR("create space failed! ret: %d, vmPage: %#x\n", retVal, vmPage); processCB->processStatus = OS_PROCESS_FLAG_UNUSED;//进程未使用,干净 (VOID)LOS_MemFree(m_aucSysMem0, space);//释放虚拟空间 LOS_PhysPagesFreeContiguous(ttb, 1);//释放物理页,4K return LOS_EAGAIN; } processCB->vmSpace = space;//设为进程虚拟空间 LOS_ListAdd(&processCB->vmSpace->archMmu.ptList, &(vmPage->node));//将空间映射页表挂在 空间的mmu L1页表, L1为表头 processCB->vmSpace = LOS_GetKVmSpace();//内核共用一个虚拟空间,内核进程 常驻内存 }
#ifdef LOSCFG_SECURITY_VID status = VidMapListInit(processCB); if (status != LOS_OK) { PRINT_ERR("VidMapListInit failed!\n"); } #endif #ifdef LOSCFG_SECURITY_CAPABILITY OsInitCapability(processCB);
if (OsSetProcessName(processCB, name) != LOS_OK) { }
return LOS_OK; }
|