【从浅学到熟知Linux】进程控制上篇=>进程创建、进程终止与进程等待(含_exit与exit的区别、fork函数详解、wait与waitpid详解)

news2024/11/29 12:36:44

在这里插入图片描述

🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见

文章目录

  • 进程创建
    • fork函数
    • 写时拷贝
  • 进程退出
    • 进程退出操作系统做了什么?
    • 进程退出场景
    • 进程退出的常见方法
      • 正常终止与异常终止
      • 缓冲区的概念
      • exit与_exit的区别
  • 进程等待
    • 进程等待的必要性
    • 进程等待方法
      • wait方法
      • waitpid方法
    • wait/waitpid获取子进程信息原理


进程创建

fork函数

创建子进程时,需要使用系统调用函数fork。创建的新进程为子进程,而原进程为父进程。
在这里插入图片描述
该函数在子进程创建成功时,给父进程返回子进程pid,给子进程返回0;创建失败时,给父进程返回-1。
在这里插入图片描述
下面我们使用fork函数来创建子进程↓↓↓

#include <stdio.h>
#include <assert.h>
#include <unistd.h>

int main()
{
	printf("before fork, pid = %d\n", getpid());
	pid_t id = fork();
	assert(id != -1);
	(void)id;

	printf("after fork, pid = %d, fork return %d\n", getpid(), id);
	return 0;
}

在这里插入图片描述
上面代码执行路径如下下图所示↓↓↓
在这里插入图片描述
进程调用fork。将会从用户空间转换到内核空间,内核做了如下内容:
①分配新的内存块和内核数据结构给子进程
②将父进程部分数据结构内容拷贝给子进程
③添加子进程到系统进程列表中
④fork返回,开始调度器调度

当进程调用fork后,就会有二进制代码相同的的两个进程,它们均运行到相同的地方,但两个代码相互独立,各自执行各自的。上面代码中,两个进程虽然执行了同一份代码,但由于父子进程的id值、pid值不同,故打印的结果不同。同时,这里到底是父进程先打印还是子进程先打印,取决于调度器的调度,哪个进程先被调度是不一定的。

★ps:创建子进程时,给子进程分配对应的内核结构,必须是子进程独有的,因为进程具有独立性。理论上,子进程也要有自己的代码和数据,但在子进程刚创建时,它的代码和数据是与父进程共享的。只有当子进程进行程序替换(修改代码)或对数据做修改等操作时,会发生写时拷贝,为子进程创建独立的存储空间。

fork的应用常见有哪些呢?

  • 一个父进程希望复制自己,使父进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行另一个完全独立的程序,类似于shell。

fork调用失败的原因有哪些?

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

通常,父进程创建子进程后,在父进程不进行代码及数据的修改时,物理地址上的数据是父子进程共享的。

在这里插入图片描述
但当父进程或子进程尝试对代码或数据进行修改时,就会发生写时拷贝。若子进程尝试修改某个变量,此时发生写时拷贝,会给子进程开辟一个该变量的物理地址,子进程修改的结果保存在该物理地址上。
在这里插入图片描述
操作系统为什么选择写时拷贝呢?
①写时拷贝用的时候再分配空间,是一种延迟申请技术,是提高内存使用效率的表现,可以提高整机的内存使用效率;(不需要子进程复制父进程的全部数据,防止浪费空间)
②操作系统无法在代码执行前预知子进程哪些代码和父进程不同,因而无法预知子进程哪些代码和数据需要与父进程共享,哪些需要独立存储;
③因为写时拷贝的存在,父子进程不同的代码和数据得以分离,保证了进程的独立性。

进程退出

进程退出操作系统做了什么?

操作系统要释放进程申请的相关内核数据结构和对应的数据和代码(本质就是释放系统资源)。

进程退出场景

  • 代码执行完毕,结果正确
#include <stdio.h>
int Add(int from, int to)
{
	int sum = 0;
	for(int i = from; i <= to; i++)
	{
		sum += i;
	}
	return sum;
}
int main()
{
	printf("Add 1 to 100 is %d\n", Add(0, 100));
	return 0;
}

在这里插入图片描述

  • 代码运行完毕,结果不正确
#include <stdio.h>
int Add(int from, int to)
{
	int sum = 0;
	/*应该为i<=to*/
	for(int i = from; i < to; i++)
	{
		sum += i;
	}
	return sum;
}
int main()
{
	printf("Add 1 to 100 is %d\n", Add(0, 100));
	return 0;
}

在这里插入图片描述

  • 代码异常终止。即代码没有跑完,程序崩溃。
#include <stdio.h>

int main()
{
	int* p = NULL;
	*p = 100;//野指针异常
	return 0;
}

在这里插入图片描述

  • 在程序执行结束时,我们会使用return语句返回一个数值作为main函数的返回值。这个返回值是做什么用的呢?

【例子1】小明参与了一场考试后,回家后给老爹汇报成绩。
如果小明考了100分(满分100),那么他的老爹并不会关心他为什么考了100分;但当小明考了3分,他的老爹会关心他考3分的原因。如果做出如下约定,每个数字标识不同的原因:

状态码描述
1生病了导致没考好
2没好好学习导致没考好

在操作系统中,对于程序正常终止我们不做关心,但程序一旦出错,我们就需要知道程序出错的原因。在操作系统约定:如果进程返回的状态码为0,则标识进程正常结束;如果进程返回的状态码非0,则标识进程异常结束(不同的非零值标识不同的错误原因)。操作系统对于不同的状态码给了不同的错误描述信息,我们可以使用errno.h下的errno变量获取错误码,使用strerror(errno)获取错误码的错误描述。下面使用程序获取Linux的不同错误码及其描述↓↓↓

#include <stdio.h>
#include <string.h>

int main()
{
	for(int i = 0; i < 200; i++)
	{
		printf("[%d]->%s\n", i, strerror(i));
	}
	return 0;
}

在这里插入图片描述
★ps:我们自己写的程序可以不遵守操作系统提供的错误码与错误描述的对应关系,我们可以自定义错误码与错误码描述。

  • 那谁会关心当前进程的退出码呢?

子进程的退出码会返回给它的上一级进程,一般而言当前进程的父进程要关心当前进程的退出码,用于评判该子进程执行代码是否正确。例如我们登录Linux系统时使用的bash,它通过创建子进程程序来执行可执行文件(命令、用户程序等),它可以显示最近一次执行的子进程的退出码。我们可以使用echo $?来查看↓↓↓
在这里插入图片描述

  • 一旦程序异常终止,而不是正常终止的,此时的退出码还有意义吗?

【例子2】小明考试作弊
小明每门考试时都作弊考了100分。学校在颁发奖学金的时候会将小明计算在内吗?显然不会,因为小明的考试行为出现了异常,它的结果就不再被关心。

同理,如果程序出现了异常,此时的程序还来不及设置退出码,此时的退出码的数值是不确定的,故此时的退出码没有意义。

  • 程序出现异常,无法通过退出码评判程序异常原因,那要使用什么来查看程序的异常原因呢?

答案是信号。如果我们写出一个除0的错误程序,我们会看到什么的结果呢?

#include <stdio.h>

int main()
{
	int a = 8 / 0;
	return 0;
}

在这里插入图片描述

在该程序发生除0错误时,操作系统给执行该程序的进程发生了8号信号SIGFPE。我们可以使用kill -l查看所有的信号码与对应信号名↓↓↓
在这里插入图片描述
我们来验证一下,上面的程序就是收到8号SIGFPE才终止的。我们执行下面代码对应的可执行程序,并给对应的进程发送8号信号,验证上述进程确实收到了8号信号才异常终止↓↓↓

#include <stdio.h>

int main()
{
	while(1)
	{}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

进程退出的常见方法

正常终止与异常终止

正常终止

  • 从main函数返回,即return+退出码
  • 调用exit
  • 调用_exit

异常终止
由于程序内部逻辑错误导致出现异常时,操作系统会给当前进程发送信号来终止程序,这种方式就是异常终止。当然,用户也可以通过kill -[信号编号] [进程pid]来给对应进程发送信号,使其异常终止。

★ps:ctrl + C本质是给当前bash中正在执行的前台进程发送2号信号,其他热键如ctrl + Z等本质也是通过给进程发送信号来实现的。

正常终止中的return+退出码及异常终止方式在上文已经有介绍了,下面我们来介绍一下exit、_exit方式如何使用,及它们的区别。

缓冲区的概念

在介绍exit与_exit前,我们要先理解一个概念——缓冲区↓↓↓

#include <stdio.h>

int main()
{
	printf("jammingpro is coding...");
	while(1)
	{}
	return 0;
}

在这里插入图片描述
从上面代码可以发现,在printf执行结束后,并没有打印字符串内容。这时因为,当我们使用C语言向显示器打印信息时,打印内容会被放入一片缓冲区中,如果缓冲区满了,或遇到\n时才会刷新缓冲区(C语言是行缓冲的,所以遇到\n会刷新缓冲区),将内容刷新到显示器中。当然,程序正常执行结束后(return 0),缓冲区的内容也会被刷新。

如果我们想让内容马上刷新到显示器上,可以使用stdio.h下的fflush手动刷新缓冲区↓↓↓

#include <stdio.h>

int main()
{
	printf("jammingpro is coding...");
	fflush(stdout);
	while(1)
	{}
	return 0;
}

在这里插入图片描述

exit与_exit的区别

_exit函数
status定义了进程的退出状态,父进程可以通过wait来获取status(关于wait,将在下文介绍)。
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>

int main()
{
	printf("jammingpro");
	_exit(1);
	return 0;
}

在这里插入图片描述

从_exit的示例代码可以发现_exit执行并不会刷新缓冲区,故最后并没有打印"jammingpro"字符串。

exit函数
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

int main()
{
	printf("jammingpro");
	exit(1);
	return 0;
}

在这里插入图片描述
从_exit和exit对同一份代码的不同执行结果可以看出,_exit在退出时不会刷新缓冲区,而exit退出时会刷新缓冲区。

exit与_exit相同的是:它们两者均会调用内核的_exit;不同的是exit除了调用_exit,还做了其他工作:
①执行用户通过atexit或on_exit定义的清理函数
②关闭所有打开的流,所有的缓存数据均被写入(即刷新缓冲区)
③再调用_exit
在这里插入图片描述

★ps:main函数中,return语句就是终止进程,即return+退出码;如果return语句在main函数中,则标识进程结束;如果return语句位于非main函数的其他函数中,则表示该函数执行结束,而不是进程结束。而exit可以在代码的任何地方调用,均表示终止当前进程。

进程等待

进程等待的必要性

当一个进程fork出子进程后,父进程没有对子进程的进行回收时,子进程就会变成僵尸状态。此时的子进程无法被任何信号终止(包括kill -9),因为信号是用于终止进程的,而处于僵尸状态的进程已经处于终止状态,只是对应的系统资源尚未被回收,谁也无法杀死一个已经死去的进程。

子进程的僵尸只能由其父进程解决,父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息(因为父进程需要知道自己派给子进程运行的结果是否正确,子进程是否正常退出等)。

★ps:C/C++程序执行完后,空间会被系统回收,则不会发生内存泄漏。但如果类似服务器程序,它一直跑,不会结束,则会发生内存泄漏。因为当前进程未执行结束,则系统资源不会被回收,而由于内存没有释放,导致大量无用的内存空间无法被申请使用,故产生内存泄漏。

进程等待方法

wait方法

当等待子进程成功时,会返回等待成功的子进程的pid,失败则返回-1。而传入的参数status为输出型参数,如果需要回去子进程的退出状态,则需要传入status;若不关心,则可以填写NULL。
在这里插入图片描述

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

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! status = %d\n", ret, status);
	}
	return 0;
}

在这里插入图片描述
从上面代码可以发现,当子进程在执行时,父进程并不会执行wait之后的if判断语句,而是阻塞在pid_t ret = wait(&status);处等待子进程退出。故wait是阻塞等待的。

这里的status表示什么含义呢?其实,status并不是像上面代码那样使用的。下面介绍status如何使用↓↓↓

status是一个输出型参数,它的值在调用wait及接下来将要介绍的waitpid时,由操作系统自动填充。如果传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会通过该参数,将子进程的退出信息反馈给父进程。

status不能像上面的代码那样使用,即不能将status看作是整型,而要将status当作位图来看待。这里我们只研究status的低16位,它的低16位信息如下↓↓↓
①正常终止时,第8到第15位保存进程的退出状态
②异常终止时,第8到第15位保存进程的退出状态(但异常终止的退出状态不一定能正确标识进程的退出状态,这里的退出状态是无效的,直接忽略),第7位为coredump标志位,这里先忽略这一位,将于后面的文章中介绍,第0到7位存储的是当前进程收到的信号。
在这里插入图片描述
如果我们想获取子进程的退出码和退出信号,则上面的代码应该修改成下面这样↓↓↓

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

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}

在这里插入图片描述
如果父进程创建了多个子进程,则wait等待的是哪一个进程呢?wait等待任意一个进程,只要等待到某个进程,则父进程不再阻塞于当前的wait语句上。下面代码演示了创建5个子进程,并用父进程循环回收5个子进程↓↓↓

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

int main()
{
	int i = 0;
	for(; i < 5; i++)
	{
		pid_t id = fork();
		if(id == 0)
		{
			int cnt = 5;
			while(cnt)
			{
				printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
				cnt--;
				sleep(1);
			}
			exit(0);
		}
	}
	i = 0;
	while(; i < 5; i++)
	{
		int status = 0;
		pid_t ret = wait(&status);
		if(ret > 0)
		{
			printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
		}
	}
	return 0;
}

在这里插入图片描述

waitpid方法

  • waitpid与wait相同,如果等待子进程成功,则会返回该子进程的pid,如果失败则返回-1;且一样可以传入status获取子进程的退出状态信息(用法与wait相同)。

waitpid多了两个参数,即pid和options。

  • 当指定pid为某个子进程的pid时,waitpid只会等待该子进程的pid,而不会等待其他子进程;如果将pid指定为-1,则表示等待任意一个子进程。
  • options设置为0时,表示阻塞等待,这与wait相同;如果设置为WNOHANG表示非阻塞式等待。关于阻塞等待和非阻塞等待,将于下文介绍。

在这里插入图片描述

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

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if(ret == id)
	{
		printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}

在这里插入图片描述

下面我们来谈谈阻塞等待与非阻塞等待↓↓↓

【例子3】期末考前,临时抱佛脚的小明[阻塞等待篇]
因为明天要考操作系统,小明想要求助他们班的学霸李华,让他帮忙画重点并辅导一下,作为报酬,小明要请李华吃饭。小明来到李华家楼下,打电话给李华,李华告知他需要等一会儿,他还需要复习一会儿,需要50分钟左右。小明在李华家楼下不做其他事,他每1分钟给李华打电话,询问他是否可以出发去吃饭辅导了,知道李华下楼了,他才停止。

像这种循环等待,而不做其他事情,这种等待就称为阻塞式等待。上面的wait和waitpid的代码示例均是阻塞等待的,父进程在wait和waitpid函数上阻塞等待子进程退出,而不做其他事,等子进程执行结束才执行下面的if判断语句。

阻塞式等待可以及时回收子进程,但父进程花费大量时间阻塞等待子进程,只要CPU时间片轮到父进程,父进程就一直循环等待子进程,这样效率明显不高。

【例子4】考试前,临时抱佛脚的小明[非阻塞等待篇]
因为上次操作系统被李华辅导后,小明考了61分,至少没有挂科。操作系统考完的隔天要考数据结构,小明又找到李华帮他复习。小明来到李华家楼下,李华告诉他需要等待30分钟左右,这次小明每复习数据结构5分钟,才给李华打一次电话。

像这种再等待时,顺带做一下其他任务,这种称为非阻塞等待。非阻塞等待执行的其他任务不应花费过多时间,以免影响父进程对子进程的等待与回收(因为父进程等待子进程才是主要任务,执行其他代码只是顺带执行,不是重点)。

下面给出一个非阻塞式等待的代码↓↓↓

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

#define NUM 5

typedef void(*func_t)();
func_t funcSet[NUM];

void task1()
{
	printf("处理日志信息\n");
}

void task2()
{
	printf("安全检测任务\n");
}

void task3()
{
	printf("其他处理任务\n");
}

void loadTask()
{
	funcSet[0] = task1;
	funcSet[1] = task2;
	funcSet[2] = task3;
}

int main()
{
	loadTask();
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	while(1)
	{
		pid_t ret = waitpid(id, &status, WNOHANG);
		if(ret == id)
		{
			printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));
			break;
		}
		else
		{
			int i = 0;
			for(; i < 3; i++)
			{
				funcSet[i]();
			}
		}
		usleep(500000);
	}
	return 0;
}

在这里插入图片描述
★ps:进程等待时,要保证父进程最后退出。

★ps:waitpid等待错误的时候会返回-1,当等待的子进程pid不是该进程的子进程时,则会返回-1;如果等待进程还未执行结束,则会返回0;等待成功则返回子进程的pid。

★ps:除了使用((status >> 8) & 0xFF),系统还提供了WEXITSTATUS(status)用于获取子进程的退出码;提供了WIFEXITED(status)判断子进程是否等待成功,还提供了其他宏函数,请使用man手册查阅。下面给出上面两个宏函数使用的代码↓↓↓

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

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if(WIFEXITED(status))
	{
		printf("wait %d success! exitcode = %d\n", ret ,WEXITSTATUS(status));
	}
	return 0;
}

在这里插入图片描述

wait/waitpid获取子进程信息原理

进程具有独立性,子进程的退出码等不也是子进程的数据吗?父进程凭什么拿到呢?wait/waitpid究竟是如何拿到子进程的退出状态的信息的?

当子进程处于僵尸状态时,系统至少要保留子进程的PCB,PCB中保存着子进程的退出码、收到的信号等退出信息。

当父进程在CPU上执行waitpid时,则进入内核态。处于内核态的执行语句有操作系统的权限,因此它可以读取子进程的退出信息,并将该退出信息填写入status。
在这里插入图片描述

🎈欢迎进入从浅学到熟知Linux专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

openstack修改实例名称但是gnocchi监控数据中实例名称没有变更的问题处理

文章目录 一、问题描述二、调研过程1、变更实例名称2、查看grafana中的监控数据3、libvirt服务中的xml文件4、现有的监控数据流转架构 总结 一、问题描述 openstack修改实例名称但是gnocchi监控数据中实例名称没有变更的问题处理。 通过修改实例名称的功能修改了实例名称&…

大模型赋能:爬虫技术的全新革命

大模型加持下的爬虫技术革新&#xff1a;从BS4到提示工程的飞跃 在爬虫技术的演进历程中&#xff0c;内容解析一直是一个核心环节。传统的爬虫技术&#xff0c;如使用BeautifulSoup&#xff08;BS4&#xff09;等工具&#xff0c;需要逐个解析网页内容&#xff0c;通过XPath或C…

XILINX 7系列时钟资源

文章目录 前言一、时钟概要1.1、CC1.2、BUFR、BUFIO、BUFMR1.3、CMT1.4、BUFH1.5、BUFG 二、时钟路由资源三、CMT 前言 本文主要参考xilinx手册ug472 一、时钟概要 7系列FPGA时钟资源主要有CC、BUFR、BUFIO、BUFMR、CMT、BUFG、BUFH和GTE_COMMON 1.1、CC “CC”&#xff0…

OpenHarmony开发案例:【自定义通知】

介绍 本示例使用[ohos.notificationManager] 等接口&#xff0c;展示了如何初始化不同类型通知的通知内容以及通知的发布、取消及桌面角标的设置&#xff0c;通知类型包括基本类型、长文本类型、多行文本类型、图片类型、带按钮的通知、点击可跳转到应用的通知。 效果预览&am…

TensorFlow实战Google深度学习框架 PDF书籍分享

今天又来给大家推荐一本TensorFlow方面的书籍<TensorFlow实战Google深度学习框架>。本书适用于想要使用深度学习或TensorFlow的数据科学家、工程师&#xff0c;希望了解大数据平台工程师&#xff0c;对人工智能、深度学习感兴趣的计算机相关从业人员及在校学生等。 下载当…

【数据结构与算法】用两个栈实现一个队列

题目 用两个栈&#xff0c;实现一个队列功能 add delete length 队列 用数组可以实现队列&#xff0c;数组和队列的区别是&#xff1a;队列是逻辑结构是一个抽象模型&#xff0c;简单地可以用数组、链表实现&#xff0c;所以数组和链表是一个物理结构&#xff0c;队列是一个逻…

Cannot access ‘androidx.activity.FullyDrawnReporterOwner‘

Android Studio新建项目就报错&#xff1a; Cannot access ‘androidx.activity.FullyDrawnReporterOwner’ which is a supertype of ‘cn.dazhou.osddemo.MainActivity’. Check your module classpath for missing or conflicting dependencies 整个类都报错了。本来原来一直…

阿里面试:DDD中的实体、值对象有什么区别?

在领域驱动设计&#xff08;DDD&#xff09;中&#xff0c;有两个基础概念&#xff1a;实体&#xff08;Entity&#xff09;和值对象&#xff08;Value Object&#xff09;。 使用这些概念&#xff0c;我们可以把复杂的业务需求映射成简单、明确的数据模型。正确使用实体和值对…

【环境】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 一、培养项目系统性思维 1.1 系统性思维 1.2 系统性思维的价值 1.3 建模和推演&数字孪生 二、项目的复杂性和如何驾驭复杂性 2.1 复杂性的三个维度 2.2 如何驾驭复杂性 三、…

【御控物联】Java JSON结构转换(4):对象To对象——规则属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

谷歌pixel6/7pro等手机WiFi不能上网,显示网络连接受限

近期在项目中遇到一个机型出现的问题,先对项目代码进行排查,发现别的设备都能正常运行,就开始来排查机型的问题,特意写出来方便后续查看,也方便其它开发者来自查。 设备机型:Pixel 6a 设备安卓版本:13 该方法无需root,只需要电脑设备安装adb(即Android Debug Bridge…

GPT提示词分享 —— 解梦

&#x1f449; 对你描述的梦境进行解读。 我希望你能充当一个解梦者。我将给你描述我的梦&#xff0c;而你将根据梦中出现的符号和主题提供解释。不要提供关于梦者的个人意见或假设。只提供基于所给信息的事实性解释。 GPT3.5的回答 GPT3.5 &#x1f447; 感觉有点傻&#xf…

申请免费https证书

https证书是什么&#xff1a; https证书是指在http超文本传输协议的前提下安装部署了SSL/TLS证书后形成的全新协议&#xff0c;https安全超文本传输协议。在https证书部署完成后&#xff0c;服务器端和浏览器端进行的信息交互的过程中会有加密层保护&#xff0c;使得原本明文传…

群晖NAS安装Synology Office与Drive结合内网穿透实现本地文件公网分享与远程协作

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…

【解决】Spring Boot创建项目常见问题

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring学习之路&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 idea无maven选项 无效发行版17 类⽂件具有错误的版本 61.0, 应为 …

H5获取微信openid封装方法

H5获取微信openid封装方法 目录1、前置配置条件2、封装并新建getOpenid.js文件2.1&#xff1a;处理code方法2.2&#xff1a;第一次获取到openid后&#xff0c;再次进入无需再次获取&#xff1b;2.3&#xff1a;页面调用方法 3、往期回顾总结&#xff1a; 目录 接到需求&#xf…

腾讯EdgeOne产品测评体验—Web安全的攻与防:云端防护一体化

目录 简介接入准备EdgeOne购买及接入服务器环境配置添加测试站点关闭防护 安全性能测试XSS攻击sql注入 站点加速测试代码测试通过在线工具对比测试Ping检测tcping网站测速 HTTPS证书 操作步骤优点 总结EdgeOne的优缺点 简介 EdgeOne&#xff0c;作为腾讯云推出的全新CDN解决方…

使用icpc tool进行滚榜操作

前言 参加ACM的同学都知道&#xff0c;比赛非常有趣的环节就是赛后的滚榜环节&#xff0c;所以为了一个比赛的完整性&#xff0c;自己办比赛时也想要加入滚榜的操作&#xff0c;经过一段时间的研究学习&#xff0c;已经可以将滚榜程序与domjudege程序成功完成融合&#xff0c;…

node.jd版本降级/升级

第一步.先清空本地安装的node.js版本 按健winR弹出窗口&#xff0c;键盘输入cmd,然后敲回车&#xff08;或者鼠标直接点击电脑桌面最左下角的win窗口图标弹出&#xff0c;输入cmd再点击回车键&#xff09; 进入命令控制行窗口&#xff0c;输入where node&#xff0c;查看本地…

CentOS7.9下载及安装教程

1. 下载CentOS7.9 CentOS用的最多的是7.6&#xff0c;7.9是7里面最新的&#xff0c;至于8以上的版本听说没有维护和更新了&#xff0c;这里以7.9为例。 下载&#xff1a;https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/ 2. 新建虚拟机 新建虚拟机–>典型(推荐…