fork功能
上层通过使用fork()函数创建新进程。
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;
}
运行输出结果:
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 child
- pid < 0 fork 失败
- pid == 0 fork成功,是子进程的返回
- pid > 0 fork成功,是父进程的返回
- fork的返回值这样规定是有道理的。fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程id,也可以调用getppid函数得到父进程的id。在父进程中用getpid可以得到自己的进程id,然而要想得到子进程的id,只有将fork的返回值记录下来,别无它法。
- 子进程并没有真正执行fork(),而是内核用了一个很巧妙的方法获得了返回值,并且将返回值硬生生的改写成了0,这是笔者认为fork的实现最精彩的部分
- fork() 是一个系统调用,因此会切换到SVC模式运行.在SVC栈中父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同.
fork 之后的代码父子进程都会执行,即代码段指向(PC寄存器)是一样的.实际上fork只被父进程调用了一次,子进程并没有执行fork函数,但是却获得了一个返回值,pid == 0;
子进程是从pid = fork() 后开始执行的,按理它不会在新任务栈中出现这些变量,而实际上后面又能顺利的使用这些变量,说明父进程当前任务的用户态的数据也复制了一份给子进程的新任务栈中.
-
被fork成功的子进程跑的首条代码指令是 pid = 0,这里的0是返回值,存放在R0寄存器中.说明父进程的任务上下文也进行了一次拷贝,父进程从内核态回到用户态时恢复的上下文和子进程的任务上下文是一样的,即 PC寄存器指向是一样的,如此才能确保在代码段相同的位置执行.
-
执行程序后 第一条打印的是This is the child说明 fork()中发生了一次调度,CPU切到了子进程的任务执行,sleep(1)的本质在系列篇中多次说过是任务主动放弃CPU的使用权,将自己挂入任务等待链表,由此发生一次任务调度,CPU切到父进程执行,才有了打印第二条的This is the parent,父进程的sleep(1)又切到子进程如此往返,直到 n = 0, 结束父子进程.
-
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次
fork的调用流程
pid_t ret = fork();
musl-libc是面向上层提供的C-API
third_party\musl\src\process\fork.c
pid_t fork(void)
{
sigset_t set;
__fork_handler(-1);
__block_app_sigs(&set);
int need_locks = libc.need_locks > 0;
if (need_locks) {
__ldso_atfork(-1);
__inhibit_ptc();
for (int i=0; i<sizeof atfork_locks/sizeof *atfork_locks; i++)
if (*atfork_locks[i]) LOCK(*atfork_locks[i]);
__malloc_atfork(-1);
__tl_lock();
}
pthread_t self=__pthread_self(), next=self->next;
pid_t ret = _Fork();//执行_Fork()
int errno_save = errno;
if (need_locks) {
if (!ret) {
for (pthread_t td=next; td!=self; td=td->next)
td->tid = -1;
if (__vmlock_lockptr) {
__vmlock_lockptr[0] = 0;
__vmlock_lockptr[1] = 0;
}
}
__tl_unlock();
__malloc_atfork(!ret);
for (int i=0; i<sizeof atfork_locks/sizeof *atfork_locks; i++)
if (*atfork_locks[i])
if (ret) UNLOCK(*atfork_locks[i]);
else **atfork_locks[i] = 0;
__release_ptc();
__ldso_atfork(!ret);
}
__restore_sigs(&set);
__fork_handler(!ret);
if (ret<0) errno = errno_save;
return ret;
}
third_party\musl\src\process_Fork.c
pid_t _Fork(void)
{
pid_t ret;
sigset_t set;
__block_all_sigs(&set);
__aio_atfork(-1);
LOCK(__abort_lock);
#ifdef SYS_fork
//LiteOS-a内核系统调用SYS_fork
ret = __syscall(SYS_fork);
#else
ret = __syscall(SYS_clone, SIGCHLD, 0);
#endif
if (!ret) {
pthread_t self = __pthread_self();
self->tid = __syscall(SYS_gettid);
#ifdef __LITEOS_A__
self->pid = __syscall(SYS_getpid);
#else
self->pid = self->tid;
#endif
self->proc_tid = -1;
self->robust_list.off = 0;
self->robust_list.pending = 0;
self->next = self->prev = self;
__thread_list_lock = 0;
libc.threads_minus_1 = 0;
#ifndef __LITEOS__
__clear_proc_pid();
#endif
if (libc.need_locks) libc.need_locks = -1;
#ifdef __LITEOS_A__
libc.exit = 0;
signal(SIGSYS, arm_do_signal);
#endif
}
UNLOCK(__abort_lock);
__aio_atfork(!ret);
__restore_sigs(&set);
return __syscall_ret(ret);
}
kernel\liteos_a\syscall\process_syscall.c
//系统调用SYS_fork
int SysFork(void)
{
return OsClone(0, 0, 0);//本质就是克隆
}
kernel\liteos_a\kernel\base\core\los_process.c
LITE_OS_SEC_BSS LosProcessCB *g_processCBArray = NULL; ///< 进程池数组
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_freeProcess;///< 空闲状态下的进程链表
LITE_OS_SEC_DATA_INIT STATIC LOS_DL_LIST g_processRecycleList;///< 需要回收的进程列表
LITE_OS_SEC_BSS UINT32 g_processMaxNum;///< 进程最大数量,默认64个
#ifndef LOSCFG_PID_CONTAINER
LITE_OS_SEC_BSS ProcessGroup *g_processGroup = NULL;///< 全局进程组,负责管理所有进程组
#define OS_ROOT_PGRP(processCB) (g_processGroup)
#endif
......
/*!
* @brief OsClone 进程克隆
*
* @param flags
* @param size 进程主任务内核栈大小
* @param sp 进程主任务的入口函数
* @return
*
* @see
*/
LITE_OS_SEC_TEXT INT32 OsClone(UINT32 flags, UINTPTR sp, UINT32 size)
{
UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | SIGCHLD;
#ifdef LOSCFG_KERNEL_CONTAINER
#ifdef LOSCFG_PID_CONTAINER
cloneFlag |= CLONE_NEWPID;
LosProcessCB *curr = OsCurrProcessGet();//获取当前进程
if (((flags & CLONE_NEWPID) != 0) && ((flags & (CLONE_PARENT | CLONE_THREAD)) != 0)) {
return -LOS_EINVAL;
}
//判断当前进程的pid容器和其子进程是否一致,一致则为false
if (OS_PROCESS_PID_FOR_CONTAINER_CHECK(curr) && ((flags & CLONE_NEWPID) != 0)) {
return -LOS_EINVAL;
}
if (OS_PROCESS_PID_FOR_CONTAINER_CHECK(curr) && ((flags & (CLONE_PARENT | CLONE_THREAD)) != 0)) {
return -LOS_EINVAL;
}
#endif
#ifdef LOSCFG_UTS_CONTAINER
cloneFlag |= CLONE_NEWUTS;
#endif
#ifdef LOSCFG_MNT_CONTAINER
cloneFlag |= CLONE_NEWNS;
#endif
#ifdef LOSCFG_IPC_CONTAINER
cloneFlag |= CLONE_NEWIPC;
if (((flags & CLONE_NEWIPC) != 0) && ((flags & CLONE_FILES) != 0)) {
return -LOS_EINVAL;
}
#endif
#ifdef LOSCFG_TIME_CONTAINER
cloneFlag |= CLONE_NEWTIME;
#endif
#ifdef LOSCFG_USER_CONTAINER
cloneFlag |= CLONE_NEWUSER;
#endif
#ifdef LOSCFG_NET_CONTAINER
cloneFlag |= CLONE_NEWNET;
#endif
#endif
if (flags & (~cloneFlag)) {
return -LOS_EOPNOTSUPP;
}
//拷贝进程
return OsCopyProcess(cloneFlag & flags, NULL, sp, size);
}
......
STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size)
{
UINT32 ret, processID;
LosProcessCB *run = OsCurrProcessGet();//获取当前进程
LosProcessCB *child = OsGetFreePCB();//从进程池中申请一个进程控制块,鸿蒙进程池默认64
if (child == NULL) {
return -LOS_EAGAIN;
}
processID = child->processID;
ret = OsInitPCB(child, run->processMode, name);//初始化PCB(进程控制块)
if (ret != LOS_OK) {
goto ERROR_INIT;
}
#ifdef LOSCFG_KERNEL_CONTAINER
//创建子进程的container,并copy父进程的PID_CONTAINER,MNT_CONTAINER,IPC_CONTAINER,USER_CONTAINER,TIME_CONTAINER,NET_CONTAINER,UTS_CONTAINER给子进程
ret = OsCopyContainers(flags, child, run, &processID);
if (ret != LOS_OK) {
goto ERROR_INIT;
}
#ifdef LOSCFG_KERNEL_PLIMITS
//给子进程配置cgroups:
//pLimits是内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对cpu,内存等资源实现精细化控制。plimits的接口通过plimitsfs的伪文件系统提供。通过操作文件对进程及进程资源进行分组管理,通过配置plimits组内限制器Plimiter限制进程组的memory、sched等资源的使用
//在proc目录下支持plimits目录,支持ipc, pid, memory, devices, sched控制器
ret = OsPLimitsAddProcess(run->plimits, child);
if (ret != LOS_OK) {
goto ERROR_INIT;
}
#endif
#endif
ret = OsForkInitPCB(flags, child, name, sp, size);//初始化进程控制块
if (ret != LOS_OK) {
goto ERROR_INIT;
}
ret = OsCopyProcessResources(flags, child, run);//拷贝进程的资源,包括虚拟空间,文件,安全,IPC ==
if (ret != LOS_OK) {
goto ERROR_TASK;
}
ret = OsChildSetProcessGroupAndSched(child, run);//设置进程组和加入进程调度就绪队列
if (ret != LOS_OK) {
goto ERROR_TASK;
}
LOS_MpSchedule(OS_MP_CPU_ALL);//给各CPU发送准备接受调度信号
if (OS_SCHEDULER_ACTIVE) {//当前CPU core处于活动状态
LOS_Schedule();// 申请调度
}
return processID;
ERROR_TASK:
(VOID)LOS_TaskDelete(child->threadGroup->taskID);
ERROR_INIT:
OsDeInitPCB(child);
return -ret;
}
......
STATIC LosProcessCB *OsGetFreePCB(VOID)
{
LosProcessCB *processCB = NULL;
UINT32 intSave;
//申请调度自旋锁,禁止调度
SCHEDULER_LOCK(intSave);
//判断空闲状态下的进程链表是否为空
if (LOS_ListEmpty(&g_freeProcess)) {
SCHEDULER_UNLOCK(intSave);
PRINT_ERR("No idle PCB in the system!\n");
return NULL;
}
//LOS_DL_LIST_FIRST(&g_freeProcess) 从链表中取出最头部的节点
//OS_PCB_FROM_PENDLIST内部调用LOS_DL_LIST_ENTRY,根据结构体成员地址, 类型,成员名,退出结构体的首地址并强制转换
//具体可看openharmony内核中特殊双向链表的遍历读取操作
processCB = OS_PCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_freeProcess));
//pendList->进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上。
//从等锁链表中将自己删除
LOS_ListDelete(&processCB->pendList);
//释放调度自旋锁,允许调度
SCHEDULER_UNLOCK(intSave);
return processCB;
}
......
/*! 初始化PCB(进程控制块)*/
STATIC UINT32 OsInitPCB(LosProcessCB *processCB, UINT32 mode, const CHAR *name)
{
processCB->processMode = mode; //用户态进程还是内核态进程
processCB->processStatus = OS_PROCESS_STATUS_INIT; //进程初始状态
processCB->parentProcess = NULL; //父进程
processCB->threadGroup = NULL; //任务组
processCB->umask = OS_PROCESS_DEFAULT_UMASK; //掩码
processCB->timerID = (timer_t)(UINTPTR)MAX_INVALID_TIMER_VID;
//LOS_ListInit 初始化双向链表
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);
/*!
* 将任务挂入进程的waitList链表,表示这个任务在等待某个进程的退出
* 当被等待进程退出时候会将自己挂到父进程的退出子进程链表和进程组的退出进程链表.
*/
LOS_ListInit(&(processCB->waitList)); //初始化等待任务链表 上面挂的是处于等待的 见于 OsWaitInsertWaitLIstInOrder LOS_ListHeadInsert(&processCB->waitList, &runTask->pendList);
#ifdef LOSCFG_KERNEL_VM
//processCB->processMode == OS_USER_MODE
if (OsProcessIsUserMode(processCB)) {//如果是用户态进程
processCB->vmSpace = OsCreateUserVmSpace();//创建用户空间
if (processCB->vmSpace == NULL) {
processCB->processStatus = OS_PROCESS_FLAG_UNUSED;
return LOS_ENOMEM;
}
} else {
processCB->vmSpace = LOS_GetKVmSpace();//从这里也可以看出,所有内核态进程是共享一个进程空间的
}//在鸿蒙内核态进程只有kprocess 和 kidle 两个
#endif
#ifdef LOSCFG_KERNEL_CPUP
//进程的cpu使用
processCB->processCpup = (OsCpupBase *)LOS_MemAlloc(m_aucSysMem1, sizeof(OsCpupBase));
if (processCB->processCpup == NULL) {
return LOS_ENOMEM;
}
(VOID)memset_s(processCB->processCpup, sizeof(OsCpupBase), 0, sizeof(OsCpupBase));
#endif
#ifdef LOSCFG_SECURITY_VID
//进程vid相关的初始化
status_t status = VidMapListInit(processCB);
if (status != LOS_OK) {
return LOS_ENOMEM;
}
#endif
#ifdef LOSCFG_SECURITY_CAPABILITY
OsInitCapability(processCB);//初始化进程安全相关功能
#endif
//配置进程名字
if (OsSetProcessName(processCB, name) != LOS_OK) {
return LOS_ENOMEM;
}
return LOS_OK;
}
......
UINT32 OsSetProcessName(LosProcessCB *processCB, const CHAR *name)
{
errno_t errRet;
if (processCB == NULL) {
return LOS_EINVAL;
}
if (name != NULL) {
errRet = strncpy_s(processCB->processName, OS_PCB_NAME_LEN, name, OS_PCB_NAME_LEN - 1);
if (errRet == EOK) {
return LOS_OK;
}
}
switch (processCB->processMode) {
case OS_KERNEL_MODE:
errRet = snprintf_s(processCB->processName, OS_PCB_NAME_LEN, OS_PCB_NAME_LEN - 1,
"KerProcess%u", processCB->processID);
break;
default:
errRet = snprintf_s(processCB->processName, OS_PCB_NAME_LEN, OS_PCB_NAME_LEN - 1,
"UserProcess%u", processCB->processID);
break;
}
if (errRet < 0) {
return LOS_NOK;
}
return LOS_OK;
}
.....
STATIC UINT32 OsForkInitPCB(UINT32 flags, LosProcessCB *child, const CHAR *name, UINTPTR sp, UINT32 size)
{
UINT32 ret;
LosProcessCB *run = OsCurrProcessGet();//获取当前进程
ret = OsCopyParent(flags, child, run);//拷贝当前运行的父进程的信息给子进程
if (ret != LOS_OK) {
return ret;
}
//拷贝任务,设置任务入口函数,栈大小
return OsCopyTask(flags, child, name, sp, size);
}
......
STATIC UINT32 OsCopyParent(UINT32 flags, LosProcessCB *childProcessCB, LosProcessCB *runProcessCB)
{
UINT32 intSave;
LosProcessCB *parentProcessCB = NULL;
SCHEDULER_LOCK(intSave);
if (childProcessCB->parentProcess == NULL) {
if (flags & CLONE_PARENT) {
//如果需要copy父进程的父进程信息(childProcessCB 和 runProcessCB就是兄弟关系啦)
parentProcessCB = runProcessCB->parentProcess;
} else {
parentProcessCB = runProcessCB;
}
childProcessCB->parentProcess = parentProcessCB;
//通过兄弟姐妹节点,挂到父进程的子进程链表尾部(siblingList链表中的所有进程来自同一个父进程)
LOS_ListTailInsert(&parentProcessCB->childrenList, &childProcessCB->siblingList);
}
if (childProcessCB->pgroup == NULL) {
//copy父进程的进程组
childProcessCB->pgroup = parentProcessCB->pgroup;
//将自己的进程组成员插入到父进程进程组链表中
LOS_ListTailInsert(&parentProcessCB->pgroup->processList, &childProcessCB->subordinateGroupList);
}
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
}
......
/**
*不管是内核态的任务还是用户态的任务,于切换而言是统一处理,一视同仁的,因为切换是需要换栈运行,寄存器有限,需要频繁的复用,这就需要将当前寄存器值先保存到任务自己的栈中,以便别人用完了轮到自己再用时恢复寄存器当时的值,确保老任务还能继续跑下去. 而保存寄存器顺序的结构体叫:任务上下文(TaskContext)
*/
/**
* OsCopyTask这个很重要,拷贝父进程当前执行的任务数据给子进程的新任务,真正让CPU干活的是任务(线程),所以子进程需要创建一个新任务 LOS_TaskCreateOnly来接受当前任务的数据,这个数据包括栈的数据,运行代码段指向,OsUserCloneParentStack将用户态的上下文数据TaskContext拷贝到子进程新任务的栈底位置, 也就是说新任务运行栈中此时只有上下文的数据.而且有最最最重要的一句代码 context->R[0] = 0; 强制性的将未来恢复上下文R0寄存器的数据改成了0, 这意味着调度算法切到子进程的任务后, 任务干的第一件事是恢复上下文,届时R0寄存器的值变成0,而R0=0意味着什么? 同时LR/SP寄存器的值也和父进程的一样.这又意味着什么?
其实返回值就是存在R0寄存器中,A()->B(),A拿B的返回值只认R0的数据,读到什么就是什么返回值,而R0寄存器值等于0,等同于获得返回值为0, 而LR寄存器所指向的指令是pid=返回值, sp寄存器记录了栈中的开始计算的位置,如此完全还原了父进程调用fork()前的运行场景,唯一的区别是改变了R0寄存器的值,所以才有了
pid = 0;//fork()的返回值,注意子进程并没有执行fork(),它只是通过恢复上下文获得了一个返回值.
if (pid == 0) {
message = "This is the child\n";
n = 6;
}
由此确保了这是子进程的返回
*/
STATIC UINT32 OsCopyTask(UINT32 flags, LosProcessCB *childProcessCB, const CHAR *name, UINTPTR entry, UINT32 size)
{
LosTaskCB *runTask = OsCurrTaskGet();
TSK_INIT_PARAM_S taskParam = { 0 };
UINT32 ret, taskID, intSave;
SchedParam param = { 0 };
taskParam.pcName = (CHAR *)name;
GetCopyTaskParam(childProcessCB, entry, size, &taskParam, ¶m);
//子进程创建任务
ret = LOS_TaskCreateOnly(&taskID, &taskParam);
if (ret != LOS_OK) {
if (ret == LOS_ERRNO_TSK_TCB_UNAVAILABLE) {
return LOS_EAGAIN;
}
return LOS_ENOMEM;
}
LosTaskCB *childTaskCB = childProcessCB->threadGroup;
childTaskCB->taskStatus = runTask->taskStatus;//任务状态先同步,注意这里是赋值操作. ...01101001
childTaskCB->ops->schedParamModify(childTaskCB, ¶m);
if (childTaskCB->taskStatus & OS_TASK_STATUS_RUNNING) {//因只能有一个运行的task,所以如果一样要改4号位
childTaskCB->taskStatus &= ~OS_TASK_STATUS_RUNNING;//将四号位清0 ,变成 ...01100001
} else {//非运行状态下会发生什么?
if (OS_SCHEDULER_ACTIVE) {//克隆线程发生错误未运行
LOS_Panic("Clone thread status not running error status: 0x%x\n", childTaskCB->taskStatus);
}
childTaskCB->taskStatus &= ~OS_TASK_STATUS_UNUSED;//干净的Task
}
if (OsProcessIsUserMode(childProcessCB)) {//是否是用户进程
SCHEDULER_LOCK(intSave);
//copy父进程的栈数据
OsUserCloneParentStack(childTaskCB->stackPointer, entry, runTask->topOfStack, runTask->stackSize);
SCHEDULER_UNLOCK(intSave);
}
return LOS_OK;
}
......
/// 拷贝进程资源
STATIC UINT32 OsCopyProcessResources(UINT32 flags, LosProcessCB *child, LosProcessCB *run)
{
UINT32 ret;
ret = OsCopyUser(child, run);//拷贝用户信息
if (ret != LOS_OK) {
return ret;
}
ret = OsCopyMM(flags, child, run);//拷贝虚拟空间
if (ret != LOS_OK) {
return ret;
}
ret = OsCopyFile(flags, child, run);//拷贝文件信息
if (ret != LOS_OK) {
return ret;
}
#ifdef LOSCFG_KERNEL_LITEIPC
if (run->ipcInfo != NULL) { //重新初始化IPC池
child->ipcInfo = LiteIpcPoolReInit((const ProcIpcInfo *)(run->ipcInfo));//@note_good 将沿用用户态空间地址(即线性区地址)
if (child->ipcInfo == NULL) {//因为整个进程虚拟空间都是拷贝的,ipc的用户态虚拟地址当然可以拷贝,但因进程不同了,所以需要重新申请ipc池和重新映射池中两个地址uvaddr和kvaddr
return LOS_ENOMEM;
}
}
#endif
#ifdef LOSCFG_SECURITY_CAPABILITY
OsCopyCapability(run, child);//拷贝安全能力
#endif
return LOS_OK;
}
......
/// 拷贝用户信息 直接用memcpy_s
STATIC UINT32 OsCopyUser(LosProcessCB *childCB, LosProcessCB *parentCB)
{
#ifdef LOSCFG_SECURITY_CAPABILITY
UINT32 size = sizeof(User) + sizeof(UINT32) * (parentCB->user->groupNumber - 1);
childCB->user = LOS_MemAlloc(m_aucSysMem1, size);
if (childCB->user == NULL) {
return LOS_ENOMEM;
}
(VOID)memcpy_s(childCB->user, size, parentCB->user, size);
#endif
return LOS_OK;
}
//拷贝虚拟空间
STATIC UINT32 OsCopyMM(UINT32 flags, LosProcessCB *childProcessCB, LosProcessCB *runProcessCB)
{
status_t status;
UINT32 intSave;
if (!OsProcessIsUserMode(childProcessCB)) {//不是用户模式,直接返回,内核虚拟空间只有一个,无需COPY !!!
return LOS_OK;
}
if (flags & CLONE_VM) {//贴有虚拟内存的标签
SCHEDULER_LOCK(intSave);
childProcessCB->vmSpace->archMmu.virtTtb = runProcessCB->vmSpace->archMmu.virtTtb;//TTB虚拟地址基地址,即L1表存放位置,virtTtb是个指针,进程的虚拟空间是指定的范围的
childProcessCB->vmSpace->archMmu.physTtb = runProcessCB->vmSpace->archMmu.physTtb;//TTB物理地址基地址,physTtb是个值,取决于运行时映射到物理内存的具体哪个位置.
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
}
//虚拟内存空间拷贝,以及物理内存的映射
status = LOS_VmSpaceClone(flags, runProcessCB->vmSpace, childProcessCB->vmSpace);//虚拟空间clone
if (status != LOS_OK) {
return LOS_ENOMEM;
}
return LOS_OK;
}
/// 拷贝进程文件描述符(proc_fd)信息
STATIC UINT32 OsCopyFile(UINT32 flags, LosProcessCB *childProcessCB, LosProcessCB *runProcessCB)
{
#ifdef LOSCFG_FS_VFS //如果开启了虚拟文件宏(每个进程都有属于自己的文件管理器,记录对文件的操作<一个文件可以被多个进程操作 >)
if (flags & CLONE_FILES) {
//如果flag是CLONE_FILES,则copy父进程所持有的所有文件
childProcessCB->files = runProcessCB->files;
} else {
#ifdef LOSCFG_IPC_CONTAINER
if (flags & CLONE_NEWIPC) {
//记录当前进程任务(LosTaskCB)的cloneIpc为true
OsCurrTaskGet()->cloneIpc = TRUE;
}
#endif
//如果flag不是CLONE_FILES,则重新分配申请分配一个文件,copy runProcessCB->files一些信息给到新申请的文件
childProcessCB->files = dup_fd(runProcessCB->files);
#ifdef LOSCFG_IPC_CONTAINER
OsCurrTaskGet()->cloneIpc = FALSE;
#endif
}
if (childProcessCB->files == NULL) {
return LOS_ENOMEM;
}
#ifdef LOSCFG_PROC_PROCESS_DIR
//根据进程的pid创建进程目录(/proc/procProcessName)
INT32 ret = ProcCreateProcessDir(OsGetRootPid(childProcessCB), (UINTPTR)childProcessCB);
if (ret < 0) {
PRINT_ERR("ProcCreateProcessDir failed, pid = %u\n", childProcessCB->processID);
return LOS_EBADF;
}
#endif
#endif
childProcessCB->consoleID = runProcessCB->consoleID;//控制台也是文件
childProcessCB->umask = runProcessCB->umask;//进程掩码
return LOS_OK;
}
......
//设置进程组和加入进程调度就绪队列
STATIC UINT32 OsChildSetProcessGroupAndSched(LosProcessCB *child, LosProcessCB *run)
{
UINT32 intSave;
UINT32 ret;
ProcessGroup *pgroup = NULL;
SCHEDULER_LOCK(intSave);
//(pgroup)->pgroupLeader == OS_USER_PRIVILEGE_PROCESS_GROUP
//OS_USER_PRIVILEGE_PROCESS_GROUP为OsGetUserInitProcess()即init进程
//如果进程组属于init进程
if ((UINTPTR)OS_GET_PGROUP_LEADER(run->pgroup) == OS_USER_PRIVILEGE_PROCESS_GROUP) {
ret = OsSetProcessGroupIDUnsafe(child->processID, child->processID, &pgroup);
if (ret != LOS_OK) {
SCHEDULER_UNLOCK(intSave);
return LOS_ENOMEM;
}
}
//进程状态标记为初始化
child->processStatus &= ~OS_PROCESS_STATUS_INIT;
LosTaskCB *taskCB = child->threadGroup;//任务组
//将当前的任务入队(OsSchedRunqueue根据cpu id获取的对应的调度队列)
taskCB->ops->enqueue(OsSchedRunqueue(), taskCB);
SCHEDULER_UNLOCK(intSave);
(VOID)LOS_MemFree(m_aucSysMem1, pgroup);
return LOS_OK;
}
......
STATIC UINT32 OsSetProcessGroupIDUnsafe(UINT32 pid, UINT32 gid, ProcessGroup **pgroup)
{
LosProcessCB *processCB = OS_PCB_FROM_PID(pid);//根据进程id获取对应的进程控制块
ProcessGroup *rootPGroup = OS_ROOT_PGRP(OsCurrProcessGet());//获取当前进程的根进程组
LosProcessCB *pgroupCB = OS_PCB_FROM_PID(gid);//根据进程组id获取对应的进程控制块
UINT32 ret = OsSetProcessGroupCheck(processCB, pgroupCB);//进行安全检查,确保进程被移动到进程组
if (ret != LOS_OK) {
return ret;
}
//如果目标进程已经是目标进程组的领导,则返回成功
if (OS_GET_PGROUP_LEADER(processCB->pgroup) == pgroupCB) {
return LOS_OK;
}
ProcessGroup *oldPGroup = processCB->pgroup;
//使目标进程退出当前进程组
ExitProcessGroup(processCB, pgroup);
//根据进程组id查找新的进程组
ProcessGroup *newPGroup = OsFindProcessGroup(gid);
if (newPGroup != NULL) {
//将进程的进程组成员插入到新的进程组进程链表里面
LOS_ListTailInsert(&newPGroup->processList, &processCB->subordinateGroupList);
processCB->pgroup = newPGroup;
return LOS_OK;
}
//如果没找到进程组,则创建一个新的进程组
newPGroup = OsCreateProcessGroup(pgroupCB);
if (newPGroup == NULL) {
//如果创建失败,则将目标进程重新添加到原始的进程组中
LOS_ListTailInsert(&oldPGroup->processList, &processCB->subordinateGroupList);
processCB->pgroup = oldPGroup;
if (*pgroup != NULL) {
LOS_ListTailInsert(&rootPGroup->groupList, &oldPGroup->groupList);
processCB = OS_GET_PGROUP_LEADER(oldPGroup);
processCB->processStatus |= OS_PROCESS_FLAG_GROUP_LEADER;
*pgroup = NULL;
}
return LOS_EPERM;
}
return LOS_OK;
}
kernel\liteos_a\kernel\base\vm\los_vm_map.c
LosVmSpace *OsCreateUserVmSpace(VOID)
{
BOOL retVal = FALSE;
//从指定动态内存池中申请size长度的内存
//m_aucSysMem0 内存池基地址
//当平台不支持异常交互功能时候,m_aucSysMem0和m_aucSysMem1的起始地址相同,用于减少内存碎片,简化内存管理逻辑
LosVmSpace *space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));
if (space == NULL) {
return NULL;
}
//分配一个物理页用于存储L1页表 4G虚拟内存分成 (4096*1M)
//申请的是一页地址连续的物理内存
VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);
if (ttb == NULL) {
//释放内存
(VOID)LOS_MemFree(m_aucSysMem0, space);
return NULL;
}
//初始化申请ttb内存 值为0
//PAGE_SZIE 为4kb(0x1000U)
(VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);
//初始化vmSpace 的size mapSize headSize等
//将节点添加到链表中 LOS_ListAdd(&g_vmSpaceList, &vmSpace->node);
//以及初始化MMU(内存管理单元,用于在CPU和内存之间实现虚拟内存管理,将虚拟地址转换为物理地址)硬件模块
retVal = OsUserVmSpaceInit(space, ttb);
//通过虚拟地址拿到page
LosVmPage *vmPage = OsVmVaddrToPage(ttb);
if ((retVal == FALSE) || (vmPage == NULL)) {
//释放内存
(VOID)LOS_MemFree(m_aucSysMem0, space);
//释放地址连续的物理内存
LOS_PhysPagesFreeContiguous(ttb, 1);
return NULL;
}
//将空间映射页表挂在 空间的mmu L1页表, L1为表头
LOS_ListAdd(&space->archMmu.ptList, &(vmPage->node));
return space;
}
......
STATUS_T LOS_VmSpaceClone(UINT32 cloneFlags, LosVmSpace *oldVmSpace, LosVmSpace *newVmSpace)
{
LosRbNode *pstRbNode = NULL; //地址区间红黑树节点
LosRbNode *pstRbNodeNext = NULL; //地址区间下一个红黑树节点
STATUS_T ret = LOS_OK;
PADDR_T paddr;
VADDR_T vaddr;
LosVmPage *page = NULL;//物理内存页
UINT32 flags, i, intSave, numPages;
//判断新旧两个虚拟内存空间是否为NULL
if ((OsVmSpaceParamCheck(oldVmSpace) == FALSE) || (OsVmSpaceParamCheck(newVmSpace) == FALSE)) {
return LOS_ERRNO_VM_INVALID_ARGS;
}
//判断地址区间的红黑树根节点的ulNodes是否为0,以及要拷贝的虚拟内存空间为内核虚拟空间
if ((OsIsVmRegionEmpty(oldVmSpace) == TRUE) || (oldVmSpace == &g_kVmSpace)) {
return LOS_ERRNO_VM_INVALID_ARGS;
}
/* search the region list */
newVmSpace->mapBase = oldVmSpace->mapBase;//地址空间的映射区开始地址
newVmSpace->heapBase = oldVmSpace->heapBase;//地址空间的堆开始地址
newVmSpace->heapNow = oldVmSpace->heapNow;//地址空间的堆现在的地址
(VOID)LOS_MuxAcquire(&oldVmSpace->regionMux);//申请地址区间的红黑树的互斥锁
//如下使用:红黑树的宏对RB_SCAN_SAFE和RB_SCAN_SAFE_END从第一个树节点循环遍历
RB_SCAN_SAFE(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)
LosVmMapRegion *oldRegion = (LosVmMapRegion *)pstRbNode;
#if defined(LOSCFG_KERNEL_SHM) && defined(LOSCFG_IPC_CONTAINER)
//如果当前地址区间的地址区间标记为共享内存的地址区间标记以及需要clone ipc信息,则继续遍历下一个红黑树节点
if ((oldRegion->regionFlags & VM_MAP_REGION_FLAG_SHM) && (cloneFlags & CLONE_NEWIPC)) {
continue;
}
#endif
//从地址空间newVmSpace中申请空闲的虚拟地址区间
//函数主要实现(LOS_RegionAlloc为这个函数)
//⑴处如果指定的虚拟地址为空,则调用函数OsAllocRange()申请内存。
//⑵如果指定的虚拟地址不为空,则调用函数OsAllocSpecificRange申请虚拟内存,下文会详细分析这2个申请函数。
//⑶处创建虚拟内存地址区间,然后指定地址区间的地址空间为当前空间vmSpace。
//⑷处把创建的地址区间插入地址空间的红黑树中。
LosVmMapRegion *newRegion = OsVmRegionDup(newVmSpace, oldRegion, oldRegion->range.base(虚拟内存地址区间开始地址), oldRegion->range.size(虚拟内存地址区间大小);
if (newRegion == NULL) {
VM_ERR("dup new region failed");
ret = LOS_ERRNO_VM_NO_MEMORY;
break;
}
#ifdef LOSCFG_KERNEL_SHM
//如果当前地址区间的地址区间标记为共享内存的地址区间标记
if (oldRegion->regionFlags & VM_MAP_REGION_FLAG_SHM) {
//fork共享内存,编译下个红黑树节点
OsShmFork(newVmSpace, oldRegion, newRegion);
continue;
}
#endif
//如果copy的地址内存空间的地址区间和当前节点的地址区间一致
if (oldRegion == oldVmSpace->heap) {
newVmSpace->heap = newRegion;
}
//PAGE_SHIFT 为12(即页大小为4KB)
//根据对应的虚拟内存地址区间大小获取对应的页数(如虚拟内存地址区间大小为8KB,转换的页数为2)
numPages = newRegion->range.size >> PAGE_SHIFT;
for (i = 0; i < numPages; i++) {
//计算每页的起始虚拟地址vaddr
vaddr = newRegion->range.base + (i << PAGE_SHIFT);
//获取对应物理地址paddr以及flag
if (LOS_ArchMmuQuery(&oldVmSpace->archMmu, vaddr, &paddr, &flags) != LOS_OK) {
continue;
}
//获取对应的物理页
page = LOS_VmPageGet(paddr);
if (page != NULL) {
//刷新内存页的引用计数
LOS_AtomicInc(&page->refCounts);
}
if (flags & VM_MAP_REGION_FLAG_PERM_WRITE) {
LOS_ArchMmuUnmap(&oldVmSpace->archMmu, vaddr, 1);
LOS_ArchMmuMap(&oldVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE);
}
//用于映射进程空间虚拟地址区间与物理地址区间
LOS_ArchMmuMap(&newVmSpace->archMmu, vaddr, paddr, 1, flags & ~VM_MAP_REGION_FLAG_PERM_WRITE);
#ifdef LOSCFG_FS_VFS
//如果开启了虚拟文件系统宏,并且地址区间是有效的文件类型
if (LOS_IsRegionFileValid(oldRegion)) {
LosFilePage *fpage = NULL;
//申请映射自旋锁
LOS_SpinLockSave(&oldRegion->unTypeData.rf.vnode->mapping.list_lock, &intSave);
fpage = OsFindGetEntry(&oldRegion->unTypeData.rf.vnode->mapping, newRegion->pgOff + i);
if ((fpage != NULL) && (fpage->vmPage == page)) { /* cow page no need map */
OsAddMapInfo(fpage, &newVmSpace->archMmu, vaddr);
}
//释放映射自旋锁
LOS_SpinUnlockRestore(&oldRegion->unTypeData.rf.vnode->mapping.list_lock, intSave);
}
#endif
}
RB_SCAN_SAFE_END(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)
(VOID)LOS_MuxRelease(&oldVmSpace->regionMux);
return ret;
}
kernel\liteos_a\arch\arm\arm\src\los_hw.c
VOID OsUserCloneParentStack(VOID *childStack, UINTPTR sp, UINTPTR parentTopOfStack, UINT32 parentStackSize)
{
LosTaskCB *task = OsCurrTaskGet();
sig_cb *sigcb = &task->sig;
VOID *cloneStack = NULL;
if (sigcb->sigContext != NULL) {
cloneStack = (VOID *)((UINTPTR)sigcb->sigContext - sizeof(TaskContext));
} else {
cloneStack = (VOID *)(((UINTPTR)parentTopOfStack + parentStackSize) - sizeof(TaskContext));
}
//将用户态的上下文数据TaskContext拷贝到子进程新任务的栈底位置, 也就是说新任务运行栈中此时只有上下文的数据
(VOID)memcpy_s(childStack, sizeof(TaskContext), cloneStack, sizeof(TaskContext));
((TaskContext *)childStack)->R0 = 0;//R0寄存器的值变为0
if (sp != 0) {
((TaskContext *)childStack)->USP = TRUNCATE(sp, LOSCFG_STACK_POINT_ALIGN_SIZE);//确保指针对齐,设置新的栈指针
((TaskContext *)childStack)->ULR = 0;
}
}
kernel\liteos_a\security\cap\capability.c
VOID OsCopyCapability(LosProcessCB *from, LosProcessCB *to)
{
UINT32 intSave;
SCHEDULER_LOCK(intSave);
to->capability = from->capability;
SCHEDULER_UNLOCK(intSave);
}
kernel\liteos_a\kernel\extended\liteipc\hm_liteipc.c
LITE_OS_SEC_TEXT_INIT STATIC UINT32 LiteIpcPoolInit(ProcIpcInfo *ipcInfo)
{
ipcInfo->pool.uvaddr = NULL;
ipcInfo->pool.kvaddr = NULL;
ipcInfo->pool.poolSize = 0;
ipcInfo->ipcTaskID = INVAILD_ID;
//初始化链表
LOS_ListInit(&ipcInfo->ipcUsedNodelist);
return LOS_OK;
}
LITE_OS_SEC_TEXT_INIT STATIC ProcIpcInfo *LiteIpcPoolCreate(VOID)
{
//从内存池中申请ProcIpcInfo内存
ProcIpcInfo *ipcInfo = LOS_MemAlloc(m_aucSysMem1, sizeof(ProcIpcInfo));
if (ipcInfo == NULL) {
return NULL;
}
(VOID)memset_s(ipcInfo, sizeof(ProcIpcInfo), 0, sizeof(ProcIpcInfo));
(VOID)LiteIpcPoolInit(ipcInfo);
return ipcInfo;
}
LITE_OS_SEC_TEXT ProcIpcInfo *LiteIpcPoolReInit(const ProcIpcInfo *parent)
{
ProcIpcInfo *ipcInfo = LiteIpcPoolCreate();
if (ipcInfo == NULL) {
return NULL;
}
ipcInfo->pool.uvaddr = parent->pool.uvaddr;
ipcInfo->pool.kvaddr = NULL;
ipcInfo->pool.poolSize = 0;
ipcInfo->ipcTaskID = INVAILD_ID;
return ipcInfo;
}
kernel\liteos_a\kernel\base\mp\los_mp.c
VOID LOS_MpSchedule(UINT32 target)
{
//获取当前的cpu id
UINT32 cpuid = ArchCurrCpuid();
target &= ~(1U << cpuid);
HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);
}
kernel\liteos_a\arch\arm\gic\gic_v3.c
STATIC VOID GicSgi(UINT32 irq, UINT32 cpuMask)
{
UINT16 tList;
UINT32 cpu = 0;
UINT64 val, cluster;
//循环遍历,检测哪些cpu核心需要触发中断
while (cpuMask && (cpu < LOSCFG_KERNEL_CORE_NUM)) {
if (cpuMask & (1U << cpu)) {
cluster = CPU_MAP_GET(cpu) & ~0xffUL;
tList = GicTargetList(&cpu, cpuMask, cluster);
/* Generates a Group 1 interrupt for the current security state */
val = ((MPIDR_AFF_LEVEL(cluster, 3) << 48) | /* 3: Serial number, 48: Register bit offset */
(MPIDR_AFF_LEVEL(cluster, 2) << 32) | /* 2: Serial number, 32: Register bit offset */
(MPIDR_AFF_LEVEL(cluster, 1) << 16) | /* 1: Serial number, 16: Register bit offset */
(irq << 24) | tList); /* 24: Register bit offset */
//触发中断
GiccSetSgi1r(val);
}
cpu++;
}
}
VOID HalIrqSendIpi(UINT32 target, UINT32 ipi)
{
GicSgi(ipi, target);
}
kernel\liteos_a\arch\arm\include\gic_v3.h
STATIC INLINE VOID GiccSetSgi1r(UINT64 val)
{
//通过内嵌汇编语言在arm处理器上触发一个软件生成的中断(SGI);通过向ICC_SGI1R_EL1寄存器上写入特定的值实现
__asm__ volatile("msr " ICC_SGI1R_EL1 ", %0" ::"r"(val));
ISB;//指令同步屏障
DSB;//数据同步屏障
}
kernel\liteos_a\kernel\base\sched\los_sched.c
VOID LOS_Schedule(VOID)
{
UINT32 intSave;
LosTaskCB *runTask = OsCurrTaskGet();
//OsSchedRunqueue根据cpu id获取的对应的调度队列
SchedRunqueue *rq = OsSchedRunqueue();
if (OS_INT_ACTIVE) {//中断正在进行
OsSchedRunqueuePendingSet();//将当前调度队列的schedFlag设置为INT_PEND_RESCH(阻止调度)
return;
}
//判断是否可抢占调度,如果不能调度,则将当前调度队列的schedFlag设置为INT_PEND_RESCH(阻止调度)
//根据rq->taskLockCnt == 0判断是否可抢占
if (!OsPreemptable()) {
return;
}
/*
* 任务中的触发器调度也将执行切片检查
* 如有必要,它将及时放弃时间片。
* 否则,没有其他副作用。
*/
SCHEDULER_LOCK(intSave);
/*
* 计时单位是Cycle,这是系统最小的计时单位。Cycle的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的Cycle * 数,对于216 MHz的CPU,1秒产生216000000个cycles。
* 获取当前时间cycle
* STATIC INLINE UINT64 OsGetCurrSchedTimeCycle(VOID)
* {
* return HalClockGetCycles();
* }
*/
//根据不同的调度算法,timeSliceUpdate分别对应的函数为 HPFTimeSliceUpdate, EDFTimeSliceUpdate, IdleTimeSliceUpdate
//更新任务调度开始的时间为当前时间
runTask->ops->timeSliceUpdate(rq, runTask, OsGetCurrSchedTimeCycle());
/* add run task back to ready queue */
//将运行的任务重新入队调度队列
runTask->ops->enqueue(rq, runTask);
/* reschedule to new thread */
//重新调度新的任务
OsSchedResched();
SCHEDULER_UNLOCK(intSave);
}
......
VOID OsSchedResched(VOID)
{
LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));
SchedRunqueue *rq = OsSchedRunqueue();
#ifdef LOSCFG_KERNEL_SMP
LOS_ASSERT(rq->taskLockCnt == 1);
#else
LOS_ASSERT(rq->taskLockCnt == 0);
#endif
rq->schedFlag &= ~INT_PEND_RESCH;
LosTaskCB *runTask = OsCurrTaskGet();
LosTaskCB *newTask = TopTaskGet(rq);
//如果当前运行的任务是调度队列栈顶的任务直接退出
if (runTask == newTask) {
return;
}
//如果当前运行的任务不是调度队列栈顶的任务,则切换新的栈顶任务运行
SchedTaskSwitch(rq, runTask, newTask);
}
......
STATIC INLINE VOID SchedSwitchCheck(LosTaskCB *runTask, LosTaskCB *newTask)
{
#ifdef LOSCFG_BASE_CORE_TSK_MONITOR
TaskStackCheck(runTask, newTask);
#endif /* LOSCFG_BASE_CORE_TSK_MONITOR */
OsHookCall(LOS_HOOK_TYPE_TASK_SWITCHEDIN, newTask, runTask);
}
STATIC VOID SchedTaskSwitch(SchedRunqueue *rq, LosTaskCB *runTask, LosTaskCB *newTask)
{
SchedSwitchCheck(runTask, newTask);
runTask->taskStatus &= ~OS_TASK_STATUS_RUNNING;
newTask->taskStatus |= OS_TASK_STATUS_RUNNING;
#ifdef LOSCFG_KERNEL_SMP
/* mask new running task's owner processor */
runTask->currCpu = OS_TASK_INVALID_CPUID;
newTask->currCpu = ArchCurrCpuid();
#endif
OsCurrTaskSet((VOID *)newTask);
#ifdef LOSCFG_KERNEL_VM
if (newTask->archMmu != runTask->archMmu) {
//切换进程时,对应的切换mmu上下文
LOS_ArchMmuContextSwitch((LosArchMmu *)newTask->archMmu);
}
#endif
#ifdef LOSCFG_KERNEL_CPUP
//结束当前任务的cpu使用分析,开始新任务的cpu使用分析
OsCpupCycleEndStart(runTask, newTask);
#endif
#ifdef LOSCFG_SCHED_HPF_DEBUG
UINT64 waitStartTime = newTask->startTime;
#endif
if (runTask->taskStatus & OS_TASK_STATUS_READY) {
/* When a thread enters the ready queue, its slice of time is updated */
newTask->startTime = runTask->startTime;
} else {
/* The currently running task is blocked */
newTask->startTime = OsGetCurrSchedTimeCycle();
/* The task is in a blocking state and needs to update its time slice before pend */
runTask->ops->timeSliceUpdate(rq, runTask, newTask->startTime);
if (runTask->taskStatus & (OS_TASK_STATUS_PEND_TIME | OS_TASK_STATUS_DELAY)) {
OsSchedTimeoutQueueAdd(runTask, runTask->ops->waitTimeGet(runTask));
}
}
UINT64 deadline = newTask->ops->deadlineGet(newTask);
SchedNextExpireTimeSet(newTask->taskID, deadline, runTask->taskID);
#ifdef LOSCFG_SCHED_HPF_DEBUG
newTask->schedStat.waitSchedTime += newTask->startTime - waitStartTime;
newTask->schedStat.waitSchedCount++;
runTask->schedStat.runTime = runTask->schedStat.allRuntime;
runTask->schedStat.switchCount++;
#endif
/* do the task context switch */
//切换任务上下文,OsTaskSchedule是一个汇编函数 见于 los_dispatch.s
OsTaskSchedule(newTask, runTask);
}