鸿蒙内核源码分析 (Fork 篇) | 一次调用,两次返回

第一次看到 fork 时,说是一次调用,两次返回,当时就懵圈了,多新鲜,真的很难理解。因为这足以颠覆了以往对函数的认知, 函数调用还能这么玩,父进程调用一次,父子进程各返回一次。而且只能通过返回值来判断是哪个进程的返回。所以一直有几个问题缠绕在脑海中.

  • fork 是什么?外部如何正确使用它.
  • 为什么要用 fork 这种设计?fork 的本质和好处是什么?
  • 怎么做到的?调用 fork () 使得父子进程各返回一次,怎么做到返回两次的,其中到底发生了什么?
  • 为什么 pid = 0 代表了是子进程的返回?为什么父进程不需要返回 0 ?

直到看了 linux 内核源码后才搞明白,但系列篇的定位是挖透鸿蒙的内核源码,所以本篇将深入 fork 函数,用鸿蒙内核源码去说明白这些问题。在看本篇之前建议要先看系列篇的其他篇幅。如 (任务切换篇,寄存器篇,工作模式篇,系统调用篇 等),有了这些基础,会很好理解 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;
}
  • pid < 0 fork 失败
  • pid == 0 fork 成功,是子进程的返回
  • pid > 0 fork 成功,是父进程的返回
  • fork 的返回值这样规定是有道理的。fork 在子进程中返回 0,子进程仍可以调用 getpid 函数得到自己的进程 id,也可以调用 getppid 函数得到父进程的 id。在父进程中用 getpid 可以得到自己的进程 id,然而要想得到子进程的 id,只有将 fork 的返回值记录下来,别无它法。
  • 子进程并没有真正执行 fork(),而是内核用了一个很巧妙的方法获得了返回值,并且将返回值硬生生的改写成了 0,这是笔者认为 fork 的实现最精彩的部分.

运行结果

$ ./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 child

这个程序的运行过程如下图所示。

解读

  • fork() 是一个系统调用,因此会切换到 SVC 模式运行。在 SVC 栈中父进程复制出一个子进程,父进程和子进程的 PCB 信息相同,用户态代码和数据也相同.

  • 从案例的执行上可以看出,fork 之后的代码父子进程都会执行,即代码段指向 (PC 寄存器) 是一样的。实际上 fork 只被父进程调用了一次,子进程并没有执行 fork 函数,但是却获得了一个返回值,pid == 0,这个非常重要。这是本篇说明的重点.

  • 从执行结果上看,父进程打印了三次 (This is the parent),因为 n = 3. 子进程打印了六次 (This is the child),因为 n = 6. 而子程序并没有执行以下代码:

        pid_t pid;char *message;int n;

子进程是从 pid = fork() 后开始执行的,按理它不会在新任务栈中出现这些变量,而实际上后面又能顺利的使用这些变量,说明父进程当前任务的用户态的数据也复制了一份给子进程的新任务栈中.

  • 被 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();//从进程池中申请一个进程控制块,鸿蒙进程池默认64if (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 ==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: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, const CHAR *name, UINTPTR sp, UINT32 size)
{UINT32 ret;LosProcessCB *run = OsCurrProcessGet();//获取当前进程ret = OsInitPCB(child, run->processMode, OS_PROCESS_PRIORITY_LOWEST, LOS_SCHED_RR, name);//初始化PCB信息,进程模式,优先级,调度方式,名称 == 信息if (ret != LOS_OK) {return ret;}ret = OsCopyParent(flags, child, run);//拷贝父亲大人的基因信息if (ret != LOS_OK) {return ret;}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_RRprocessCB->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/3751245if (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) {//这里直接获取物理页ttbPRINT_ERR("%s %d, alloc ttb or space failed\n", __FUNCTION__, __LINE__);(VOID)LOS_MemFree(m_aucSysMem0, space);return LOS_ENOMEM;}(VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);//内存清0retVal = OsUserVmSpaceInit(space, ttb);//初始化虚拟空间和进程mmuvmPage = OsVmVaddrToPage(ttb);//通过虚拟地址拿到pageif ((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);//释放物理页,4Kreturn LOS_EAGAIN;}processCB->vmSpace = space;//设为进程虚拟空间LOS_ListAdd(&processCB->vmSpace->archMmu.ptList, &(vmPage->node));//将空间映射页表挂在 空间的mmu L1页表, L1为表头} else {processCB->vmSpace = LOS_GetKVmSpace();//内核共用一个虚拟空间,内核进程 常驻内存}#ifdef LOSCFG_SECURITY_VIDstatus = VidMapListInit(processCB);if (status != LOS_OK) {PRINT_ERR("VidMapListInit failed!\n");return LOS_ENOMEM;}
#endif
#ifdef LOSCFG_SECURITY_CAPABILITYOsInitCapability(processCB);
#endifif (OsSetProcessName(processCB, name) != LOS_OK) {return LOS_ENOMEM;}return LOS_OK;
}

//拷贝一个Task过程
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);//初始化Task参数ret = LOS_TaskCreateOnly(&taskID, &childPara);//只创建任务,不调度if (ret != LOS_OK) {if (ret == LOS_ERRNO_TSK_TCB_UNAVAILABLE) {return LOS_EAGAIN;}return LOS_ENOMEM;}childTaskCB = OS_TCB_FROM_TID(taskID);//通过taskId获取task实体childTaskCB->taskStatus = OsCurrTaskGet()->taskStatus;//任务状态先同步,注意这里是赋值操作. ...01101001 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;//干净的TaskchildProcessCB->priority = OS_PROCESS_PRIORITY_LOWEST;//进程设为最低优先级}if (OsProcessIsUserMode(childProcessCB)) {//是否是用户进程SCHEDULER_LOCK(intSave);OsUserCloneParentStack(childTaskCB, OsCurrTaskGet());//拷贝当前任务上下文给新的任务SCHEDULER_UNLOCK(intSave);}OS_TASK_PRI_QUEUE_ENQUEUE(childProcessCB, childTaskCB);//将task加入子进程的就绪队列childTaskCB->taskStatus |= OS_TASK_STATUS_READY;//任务状态贴上就绪标签return LOS_OK;
}
//把父任务上下文克隆给子任务
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));//cloneStack指向 TaskContextLOS_ASSERT(parentTaskCB->taskStatus & OS_TASK_STATUS_RUNNING);//当前任务一定是正在运行的task(VOID)memcpy_s(childTaskCB->stackPointer, sizeof(TaskContext), cloneStack, sizeof(TaskContext));//直接把任务上下文拷贝了一份context->R[0] = 0;//R0寄存器为0,这个很重要, pid = fork()  pid == 0 是子进程返回.
}

解读

  • 系统调用是通过 CLONE_SIGHAND 的方式创建子进程的。具体有哪些创建方式如下:
      #define CLONE_VM       0x00000100	//子进程与父进程运行于相同的内存空间#define CLONE_FS       0x00000200	//子进程与父进程共享相同的文件系统,包括root、当前目录、umask#define CLONE_FILES    0x00000400	//子进程与父进程共享相同的文件描述符(file descriptor)表#define CLONE_SIGHAND  0x00000800	//子进程与父进程共享相同的信号处理(signal handler)表#define CLONE_PTRACE   0x00002000	//若父进程被trace,子进程也被trace#define CLONE_VFORK    0x00004000	//父进程被挂起,直至子进程释放虚拟内存资源#define CLONE_PARENT   0x00008000	//创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”#define CLONE_THREAD   0x00010000	//Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

此处不展开细说,进程之间发送信号用于异步通讯,系列篇有专门的篇幅说信号 (signal),请自行翻看.

  • 可以看出 fork 的主体函数是 OsCopyProcess,先申请一个干净的 PCB,相当于申请一个容器装资源.
  • 初始化这个容器 OsForkInitPCB, OsInitPCB 先把容器打扫干净,虚拟空间,地址映射表 (L1 表),各种链表初始化好,为接下来的内容拷贝做好准备.
  • OsCopyParent 把家族基因 / 关系传递给子进程,谁是你的老祖宗,你的七大姑八大姨是谁都得告诉你知道,这些都将挂到你已经初始化好的链表上.
  • 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;}

由此确保了这是子进程的返回。这是 fork() 最精彩的部分。一定要好好理解.OsCopyTask``OsUserCloneParentStack 的代码细节。会让你醍醐灌顶,永生难忘.

  • 父进程的返回是 processID = child->processID; 是子进程的 ID,任何子进程的 ID 是不可能等于 0 的,成功了只能是大于 0. 失败了就是负数 return -ret;
  • OsCopyProcessResources 用于赋值各种资源,包括拷贝虚拟空间内存,拷贝打开的文件列表,IPC 等等.
  • OsChildSetProcessGroupAndSched 设置子进程组和调度的准备工作,加入调度队列,准备调度.
  • LOS_MpSchedule 是个核间中断,给所有 CPU 发送调度信号,让所有 CPU 发生一次调度。由此父进程让出 CPU 使用权,因为子进程的调度优先级和父进程是平级,而同级情况下子进程的任务已经插到就绪队列的头部位置 OS_PROCESS_PRI_QUEUE_ENQUEUE 排在了父进程任务的前面,所以在没有比他们更高优先级的进程和任务出现之前,下一次被调度到的任务就是子进程的任务。也就是在本篇开头看到的
    $ ./a.out This is the childThis is the parentThis is the childThis is the parentThis is the childThis is the parentThis is the child$ This is the childThis is the child
  • 以上为 fork 在鸿蒙内核的整个实现过程,务必结合系列篇其他篇理解,一次理解透彻,终生不忘.

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/798421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记Postman参数化

因为需要在WEB页面上处理部分数据&#xff0c;手动操作太慢&#xff0c;所以考虑使用接口方式处理&#xff0c;因急于使用&#xff0c;用Python Request的方式&#xff0c;写代码也来得慢&#xff0c;故采用Postman加外部文件参数化方式来实现。 接口请求是Post方式&#xff0c…

电商平台混战之下,天猫破解品牌增长奥秘

行业共识是追上风&#xff0c;才有好生意&#xff0c;但风很多时候不会只有一个方向。 4月2日&#xff0c;上海&#xff0c;TopTalk 2024天猫超级品牌私享会举行。这个活动已举办数年&#xff0c;每一年天猫都会发布新一年度的品牌经营策略&#xff0c;只是与往年不同的是&…

YOLOv9改进策略 :原创自研 | 自研MSAM注意力,通道注意力升级,魔改CBAM

💡💡💡本文自研创新改进:MSAM(CBAM升级版):通道注意力具备多尺度性能,多分支深度卷积更好的提取多尺度特征,最后高效结合空间注意力 1)作为注意力MSAM使用; 推荐指数:五星 MSCA | 亲测在多个数据集能够实现涨点,对标CBAM。 改进1结构图如下: 《YOLOv…

linux安全加固

1.登录账号加固 /etc/login.defs 创建⽤户的默认设置⽂件 grep -Ev "^#|^$" /etc/login.defs /etc/login.defs ⽂件⽤于在创建⽤户时&#xff0c;对⽤户的⼀些基本属性做默认设置&#xff0c;例如指定⽤户 UID 和 GID 的范围&#xff0c;⽤户的过期时间&#xff0…

寻找排序数组中的最小值

题目描述 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,2]若旋转 7 次…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型+弹性布局)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

Linux 内核优化简笔 - 高并发的系统

简介 Linux 服务器在高并发场景下&#xff0c;默认的内核参数无法利用现有硬件&#xff0c;造成软件崩溃、卡顿、性能瓶颈。 当然&#xff0c;修改参数只是让Linux更好软件的去利用已有的硬件资源&#xff0c;如果硬件资源不够也无法解决问题的。而且当硬件资源不足的时候&am…

AcWing 788. 逆序对的数量——算法基础课题解

AcWing 788. 逆序对的数量 题目描述 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。…

Cute Background FX

Cute Background FX是环境背景粒子系统的集合。非常适合作为菜单的背景。 该包包括: -20个独特预制件+20个URP预制件 -5种独特的环境设计 -15种纹理 -2个自定义着色器+2个URP着色器 -共59项独特资产 -一个演示场景,您可以在其中概述所有内容。 所有纹理都是512x512分辨率的P…

基于SSM框架实现的在线心理评测与咨询系统(技术栈 spring+springmvc+mybatis+jsp+jquery+css)

一、项目简介 本项目是一套基于SSM框架实现的在线心理评测与咨询系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&am…

iOS 应用内网络请求设置代理

主要通过URLSessionConfiguration 的connectionProxyDictionary 属性 为了方便其他同学使用&#xff0c;我们可以通过界面来进行设定&#xff08;是否开启代理、服务端、端口&#xff09;&#xff0c;从而达到类似系统上的设定 具体链接参考&#xff1a;为 iOS 网络请求设置代理…

设计模式总结-桥接模式

桥接模式 模式动机模式定义模式结构模式分析桥接模式实例与解析实例一&#xff1a;模拟毛笔 模式优缺点 模式动机 设想如果要绘制矩形、圆形、椭圆、正方形&#xff0c;我们至少需要4个形状类&#xff0c;但是如果绘制的图形需要具有不同的颜色&#xff0c;如红色、绿色、蓝色…

xss.pwnfunction-Ugandan Knuckles

这个是把<>过滤掉了所以只能用js的事件 ?weya"onfocus"alert(1337)" autofocus"

基于springboot的社区医疗服务系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

数据结构面试题报错调试方法记录

栈和队列报错调试 1.用栈实现队列 232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; 此题解题思路如下&#xff1a; 先将数据放在pushst栈里面&#xff0c;popst栈为空再把pushst栈里面的数据放进popst栈里面去&#xff0c;不为空则不执行。不为空时候直接拿取栈…

【技术笔记】Ubuntu下VirtualBox不能识别USB解决办法(手把手解决)

环境说明 系统版本&#xff1a;Ubuntu 20.04 VirtualBox版本&#xff1a; 7.0.12 解决过程 扩展下载&#xff0c;进入VirtualBox 官方下载路径。选择本机安装版本&#xff0c;如下图所示&#xff0c;因笔者是7.0.x版本&#xff0c;因此点击第一条链接&#xff1b; 进入版本页…

机器学习(30)

文章目录 摘要一、文献阅读1. 题目2. abstract3. 网络架构3.1 Sequence Generative Adversarial Nets3.2 SeqGAN via Policy Gradient3.3 The Generative Model for Sequences3.4 The Discriminative Model for Sequences(CNN) 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过…

【Vue3源码学习】— CH2.7 Computed: Vue 3 计算属性深入解析

Computed: Vue 3 计算属性深入解析 1.计算属性的基本用法2. ComputedRefImpl 类深入解析JavaScript 中的 getter 函数 3. 计算属性的创建&#xff1a;computed 方法解析3.1 源码解析3.2 使用示例 4. 计算属性的工作原理5. 手动实现简化的计算属性6. 结语 在 Vue 3 的响应式系统…

【教程】VOC数据集制作

语义分割任务中VOC数据集的制作&#xff0c;任务中只有一种标签&#xff1a;gas 文章目录 1、由黑白图像识别为txt标签2、txt转json3、数据集转VOC格式 1、由黑白图像识别为txt标签 由于使用CycleGAN网络进行风格迁移学习&#xff0c;生成了大量伪标签图像&#xff0c;因此需…

【递归与递推】数的计算|数的划分|耐摔指数

1.数的计算 - 蓝桥云课 (lanqiao.cn) 思路&#xff1a; 1.dfs的变量>每一次递归什么在变&#xff1f; &#xff08;1&#xff09;当前数的大小一直在变&#xff1a;sum &#xff08;2&#xff09;最高位的数&#xff1a;k 2.递归出口&#xff1a;最高位数字为1 3.注意&#…