鸿蒙fork()功能

embedded/2024/10/11 9:17:01/

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_forkret = __syscall(SYS_fork);
#elseret = __syscall(SYS_clone, SIGCHLD, 0);
#endifif (!ret) {pthread_t self = __pthread_self();self->tid = __syscall(SYS_gettid);
#ifdef __LITEOS_A__self->pid = __syscall(SYS_getpid);
#elseself->pid = self->tid;
#endifself->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();
#endifif (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_CONTAINERcloneFlag |= CLONE_NEWPID;LosProcessCB *curr = OsCurrProcessGet();//获取当前进程if (((flags & CLONE_NEWPID) != 0) && ((flags & (CLONE_PARENT | CLONE_THREAD)) != 0)) {return -LOS_EINVAL;}//判断当前进程的pid容器和其子进程是否一致,一致则为falseif (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_CONTAINERcloneFlag |= CLONE_NEWUTS;
#endif
#ifdef LOSCFG_MNT_CONTAINERcloneFlag |= CLONE_NEWNS;
#endif
#ifdef LOSCFG_IPC_CONTAINERcloneFlag |= CLONE_NEWIPC;if (((flags & CLONE_NEWIPC) != 0) && ((flags & CLONE_FILES) != 0)) {return -LOS_EINVAL;}
#endif
#ifdef LOSCFG_TIME_CONTAINERcloneFlag |= CLONE_NEWTIME;
#endif
#ifdef LOSCFG_USER_CONTAINERcloneFlag |= CLONE_NEWUSER;
#endif
#ifdef LOSCFG_NET_CONTAINERcloneFlag |= CLONE_NEWNET;
#endif
#endifif (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();//从进程池中申请一个进程控制块,鸿蒙进程池默认64if (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
#endifret = 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_MODEif (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_CAPABILITYOsInitCapability(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, &param);//子进程创建任务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, &param);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_LITEIPCif (run->ipcInfo != NULL) { //重新初始化IPC池child->ipcInfo = LiteIpcPoolReInit((const ProcIpcInfo *)(run->ipcInfo));//@note_good 将沿用用户态空间地址(即线性区地址)if (child->ipcInfo == NULL) {//因为整个进程虚拟空间都是拷贝的,ipc的用户态虚拟地址当然可以拷贝,但因进程不同了,所以需要重新申请ipc池和重新映射池中两个地址uvaddr和kvaddrreturn LOS_ENOMEM;}}
#endif#ifdef LOSCFG_SECURITY_CAPABILITYOsCopyCapability(run, child);//拷贝安全能力
#endifreturn LOS_OK;
}....../// 拷贝用户信息 直接用memcpy_s
STATIC UINT32 OsCopyUser(LosProcessCB *childCB, LosProcessCB *parentCB)
{
#ifdef LOSCFG_SECURITY_CAPABILITYUINT32 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);
#endifreturn 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);//虚拟空间cloneif (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_CONTAINERif (flags & CLONE_NEWIPC) {//记录当前进程任务(LosTaskCB)的cloneIpc为trueOsCurrTaskGet()->cloneIpc = TRUE;}
#endif  //如果flag不是CLONE_FILES,则重新分配申请分配一个文件,copy runProcessCB->files一些信息给到新申请的文件childProcessCB->files = dup_fd(runProcessCB->files);
#ifdef LOSCFG_IPC_CONTAINEROsCurrTaskGet()->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
#endifchildProcessCB->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);//通过虚拟地址拿到pageLosVmPage *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;//判断新旧两个虚拟内存空间是否为NULLif ((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++) {//计算每页的起始虚拟地址vaddrvaddr = newRegion->range.base + (i << PAGE_SHIFT);//获取对应物理地址paddr以及flagif (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寄存器的值变为0if (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 idUINT32 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_SMPLOS_ASSERT(rq->taskLockCnt == 1);
#elseLOS_ASSERT(rq->taskLockCnt == 0);
#endifrq->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_MONITORTaskStackCheck(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();
#endifOsCurrTaskSet((VOID *)newTask);
#ifdef LOSCFG_KERNEL_VMif (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_DEBUGUINT64 waitStartTime = newTask->startTime;
#endifif (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_DEBUGnewTask->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.sOsTaskSchedule(newTask, runTask);
}

http://www.ppmy.cn/embedded/125828.html

相关文章

ctf.bugku - SOURCE

题目来源&#xff1a; source - Bugku CTF 首先&#xff0c;访问页面&#xff0c; 得到的是假的 flag &#xff0c; 查看前端页面、代码、response返回&#xff1b; 没有有用信息&#xff1b; 查后端&#xff1a; git泄露 下载git文件 # wget -r http://114.67.175.224:156…

JAVA科技赋能共享台球室无人系统小程序源码

科技赋能共享台球室无人系统 —— 智慧台球新体验 &#x1f3b1; 科技引领&#xff0c;台球室迎来无人新纪元 在这个日新月异的科技时代&#xff0c;共享经济的浪潮席卷而来&#xff0c;为我们的生活带来了诸多便利。而今天&#xff0c;我要为大家介绍的&#xff0c;正是科技…

【物联网】JDY-31 蓝牙传输模块的使用、调试和传输

JDY-31蓝牙传输模块是一款基于蓝牙3.0 SPP设计的无线通信模块&#xff0c;支持Windows、Linux、Android数据透传。它工作在2.4GHz频段&#xff0c;采用GFSK调制方式&#xff0c;最大发射功率为8dBm&#xff0c;最大发射距离可达30米。该模块支持通过AT命令修改设备名、波特率等…

安卓使用.9图实现阴影效果box-shadow: 0 2px 6px 1px rgba(0,0,0,0.08);

1.安卓实现阴影效果有很多种&#xff0c;一般UX设计会给以H5参数box-shadow: 0 2px 6px 1px rgba(0,0,0,0.08);这种方式提供背景阴影效果&#xff0c;这里记录一下实现过程 2.界面xml源码 <?xml version"1.0" encoding"utf-8"?> <layout xmlns…

鸿蒙架构-系统架构师(七十八)

1信息加密是保证系统机密性的常用手段。使用哈希校验是保证数据完整性的常用方法。可用性保证合法用户对资源的正常访问&#xff0c;不会被不正当的拒绝。&#xff08;&#xff09;就是破坏系统的可用性。 A 跨站脚本攻击XSS B 拒绝服务攻击DoS C 跨站请求伪造攻击CSRF D 缓…

传智杯 第六届—C

题目描述&#xff1a; 输入两个字符串&#xff0c;从第一字符串中删除第二个字符串中所有的字符。例如&#xff1a;第一个字符串是"They are students."&#xff0c;第二个字符串是”aeiou"。删除之后的第一个字符串变成"Thy r stdnts."。保证两个字符…

使用AudioRelay+ VB-CABLE 实现手机无线麦克风及音响功能

我们有时会有这样的需求: 1、会议中,现场没有麦克风,有手机,有电脑,想直接用手机当用电脑的远程麦克风来使用 2、没有音响,但空间比较大、吵,电脑的声音不够大,要电脑的声音直接发到手机上播放. 这时 AudioRelay VB-CABLE 就可以满足&#xff0c;支持windows 以及macos 具体的…

Excel插件:成绩统计排名(三)

一、安装后如图 二、 功能介绍&#xff1a; &#xff08;三&#xff09;镇统计与排名 1、模板说明&#xff08;镇用&#xff09; 2、镇内批量三分四率统计 PS&#xff1a;可以设置界值&#xff0c;统计&#xff0c;如果你统计的“名堂”不是“特优”“优秀”也可以统计完成后&…