Linux 进程控制

news2024/9/20 14:53:41

🧑‍💻进程控制 🧑‍💻

文章目录

  • 🧑‍💻进程控制 🧑‍💻
  • 一、进程创建
    • 1. fork函数
    • 2. fork常规用法
    • 3. fork创建子进程操作系统都做了什么?
    • 4. 写时拷贝
    • 5. 父子进程代码的共享
    • 6. fork调用失败的原因
  • 二、进程中止
    • 1. 进程中止时,操作系统做了什么?
    • 2. 进程中止的常见方式
    • 3. 查看进程退出码的方式:
    • 4. 用代码如何中止一个进程
  • 三、进程等待
    • 1. 为什么要进行进程等待?
    • 2. 进程等待的方法
    • 3. 子进程的status
  • 四、进程替换
    • 1. 基本概念
    • 2. 如何进行进程替换
    • 3. 为什么要进行进程替换

一、进程创建

1. fork函数

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。 新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void); //pid_t是一个无符号整数
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

举例:

#include<stdio.h>                                                                           
#include<unistd.h>
int main()
{
	printf("我是父进程!\n");
	pid_t id = fork();
	if (id < 0)
	{
		printf("创建子进程失败!\n");
		return 1;
	}
	else if (id == 0)
	{
		while (1)
		{
			printf("我是子进程:pid:%d,ppid:%d\n", getpid(), getppid());
			sleep(1);
		}
	}
	else
	{
		while (1)
		{
			printf("我是父进程:pid:%d,ppid:%d\n", getpid(), getppid());
			sleep(1);
		}
	}
	return 0;	 
}

当在Linux中写入上述代码👆的文件时,会在命令框循环打印下面的文字👇
在这里插入图片描述

  • 除了使用系统调用函数getpid(),也使用系统命令查看进程,可以看出使用fork()使该进程生成子进程。
    在这里插入图片描述
  • 因为父子进程返回的id值不同,导致父子进程执行了不同的代码。
  • 两个循环也会同时执行。

2. fork常规用法

  1. 父子进程同时执行同一份代码的不同代码段。
  2. 一个进程要执行一个不同的程序。
  3. fork之后,谁先执行完全由调度器决定。

🌟进程 =内核数据结构+进程代码和数据

内核数据结构由OS来维护,包括PCB结构体、进程地址空间结构体、页表等等,进程代码和数据一般从磁盘中来,也就是C/C++程序加载后的结果

3. fork创建子进程操作系统都做了什么?

  1. fork 创建子进程就是系统中多了一个进程,先分配新的内存块和内核数据结构给子进程,再将父进程部分数据结构内容拷贝至子进程
  2. 将该进程的PCB结构体放到运行队列里,fork( )返回后等待操作系统或调度器来调度。
  3. 当CPU开始调度这个进程了,此时就可以通过该进程的虚拟地址空间和页表找到该进程的相关代码。
  4. 从上往下按照顺序在这个进程内部来执行这个进程内的代码并完成某种功能。

解释说明:

  • 创建子进程,给子进程分配对应的内核结构,必须子进程独有(独立性)。
  • 理论上 ,子进程也要有自己的代码和数据。
  • 可是一般而言,子进程没有自己的代码和数据,只能使用父进程的代码和数据。
  • 代码都是不可被写的,只能读取,所以父子共享并没有问题
  • 数据是可能被修改的,所以必须分离!操作系统使用写时拷贝技术将父子进程的数据分离。

4. 写时拷贝

在这里插入图片描述
写时拷贝原理:

  • fork之后,子进程的虚拟地址空间和页表以父进程为模板,把父进程的字段拷贝过来,指向的内容是完全一样的。
  • 当用户尝试对子进程中的数据进行修改的时候(页表只读权限去除),把内存当中曾经被父子进程共享的内存区域拷贝一份给子进程,然后修改页表中的页表项,再让子进程的页表指向新申请的地址。这样在数据层面上,父子进程就实现了分离。

为什么要使用写时拷贝:

  • 为了节省空间,创建子进程,不需要将不会被访问的或者只会读取的数据都拷贝一份,并且OS无法在代码执行前预知哪些空间会被访问,所以OS选择了操作系统选择了写时拷贝技术。

写时拷贝的优点:

  • 操作系统使用写时拷贝技术将父子进程分离,用的时候在给你分配,写时拷贝是一种延时拷贝申请技术,可以提高整机内存的使用率。
  • 因为有写时拷贝技术的存在,所以父子进程得以彻底分离,完成了进程独立性的 技术保证。

5. 父子进程代码的共享

  • 代码汇编之后会有很多行代码,而且每行代码加载到内存之后都有对应的地址。
  • 进程可能被中断,没有执行完,下次回来还要从刚才的位置继续运行就要求cpu必须记录下当前进程执行的位置,所以cpu有对应的寄存器(EIP / pc指针:程序计数器)数据用来记录当前进程的执行位置。
  • 寄存器在cpu内只有一份,但是寄存器中的数据(进程的上下文数据)是可以有很多份的。

💡所以:当fork时,寄存器中的数据也要给子进程,子进程认为自己的EIP起始值就是fork之后的代码! 但是实际上fork之后子进程可以看到全部的代码(包括fork执行前的代码)!

6. fork调用失败的原因

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

二、进程中止

1. 进程中止时,操作系统做了什么?

进程 =内核数据结构+进程代码和数据

  • 所以进程中止时,操作系统会释放进程申请的相关的内核数据结构和对应的数据和代码,本质就是释放系统资源(主要的是内存资源,还有cpu及其他资源)

2. 进程中止的常见方式

进程有三种常见的中止方式:

  1. 代码跑完,结果正确
  2. 代码跑完,结果不正确
  3. 代码没有跑完,程序崩溃(本质上就是操作系统通过发送信号的方式杀掉了该进程)

第一种和第二种进程中止方式主要的区别是结果是否正确,那么代码跑完结果是否正确应该怎么判定呢❓

💡答:代码跑完结果是否正确是由进程的退出码标识的

  • main函数的返回值叫做进程的退出码,返回给上一级进程,表示进程返回时结果是否正确,从而评判该进程执行的结果。常见的main函数返回值都是0,但是它也可以是其他值。
  • main函数返回值0:表示运行结果正确。
  • main函数返回值非0:表示运行结果不正确。
  • 进程崩溃时,退出码无意义,一般而言退出码对应的return语句没有被执行。

⭐ 不同的非零值就可以标识不同的错误原因,从而当我们的程序运行结束之后,退出码可以定位错误的原因。

3. 查看进程退出码的方式:

对话框输入命令echo $?获取最近一个进程执行完毕的退出码

当不知道退出码的含义时可以使用c语言提供的strerror函数将退出码转换成字符串描述退出码含义

# include<string.h>
	streror(退出码)

4. 用代码如何中止一个进程

正常终止:

  1. 从main函数 return 退出码中止进程

👀其他函数内部return叫函数返回,只有main函数内的return语句是进程退出

  1. 调用exit函数中止进程
#include <stdlib.h> 
void exit(int status);//参数:status 定义了进程的终止状态

exit 和 return区别:
(1)exit在代码的任何地方调用都是直接中止进程
(2)return是语句,exit是函数。

  1. 调用_exit函数中止进程
#include <unistd.h>
void _exit(int status);//参数:status 定义了进程的终止状态

_exit是系统调用接口,exit函数是c语言提供的库函数
在这里插入图片描述

exit 也会调用exit, 但在调用exit之前还会:

  • 执行用户通过定义的清理函数
  • 关闭所有打开的流,所有的缓存数据均被写入。(💡如果printf不加 \n 数据是保存在缓冲区中并由C标准库维护的)
  • 调用_exit

异常退出:

  • linux下在命令行窗口输入ctrl + c

三、进程等待

1. 为什么要进行进程等待?

  • 子进程退出,父进程如果不管不顾,就可能造成僵尸进程,进而造成内存泄漏。
  • 进程一旦变成僵尸状态,谁也没有办法杀死一个已经死去的进程。
  • 父进程有时需要知道派给子进程的任务完成的如何。

上述问题在系统中由进程等待解决:

⭐父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2. 进程等待的方法

wait方法

当子进程已经退出了,但是父进程还在运行,子进程就会变为僵尸进程,为了解决僵尸进程造成的内存泄露,需要采用wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status); //阻塞式等待
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL	

举例:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;      
        }       
        exit(0);
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        sleep(7);
        pid_t pid = wait(NULL); //阻塞式的等待!一般都是在内核中阻塞,等待被唤醒
        if (pid > 0)
        {
            printf("等待子进程成功, pid: %d\n", pid);
        }       
    }
}

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
	> 0,表示正常返回,waitpid返回收集到的子进程的进程ID
	= 0,如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
	< 0,如果调用中出错,则返回-1
参数:
	Pid =-1,等待任一个子进程,与wait等效
	Pid > 0,等待其进程ID与pid相等的子进程
status:
	输出型参数,查看进程是否是正常退出以及进程的退出码
options:
	 options:默认为0,代表阻塞等待,设置为WNOHANG代表父进程非阻塞等待。
	(系统提供的大写标记为其实就是宏,WAIT NO HANG,夯就是这个进程没有被CPU调度)
	 若等待成功但pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。

举例:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 5;
        while (cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1); 
            cnt--;          
        }
        exit(0);
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        int status = 0;
        // 只有子进程退出的时候,父进程才会执行waipid函数,进行返回。父进程依然还活着!
        // wait/waitpid可以在目前的情况下, 让进程退出有一定顺序性
        // 可以让父进程进行更多的收尾工作
        // id > 0,等待指定进程
        // id =-1,等待任意一个子进程退出,等价于wait接口(wait接口属于waitpid的子集)
        // options:默认为0,代表阻塞等待,设置为 WNOHANG代表父进程非阻塞等待
        pid_t ret = waitpid(id, &status, 0); //默认是在阻塞状态区等待子进程状态变为退出
        if (ret > 0)
        {
            // 0x7F -> 0000...000 0111 1111
            printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF); //0xff --> 0000...000 1111 1111       
            //上面的获取子进程收到信号编号和退出码的方式比较繁琐,可以采用系统提供的status宏:   
            if(WIFEXITED(status))
            {
             	 //子进程是正常退出的
             	 printf("子进程执行完毕,子进程退出码:%d\n",WEXITSTATUS(status));
            }
            else
            {
            	printf("子进程异常退出:%d\n",WIFEXITED(status));
            }
        }   
    }
}

💡wait(pid,NULL,0) == wait(NULL)

  • 虽然进程具有独立性,但是僵尸进程至少要保留该进程的PCB信息,task_struct里面至少要保留进程退出时的结果信息。
  • 本质上父进程执行wait/waipid就是读取子进程的tast_struct中的exit_code,exit_signal,父进程没有读取内核数据结构对象的权限,但是wait/waipid是系统调用,操作系统拥有这个权限。

3. 子进程的status

  • wait 和 waitpid,都有一个status参数,该参数是一个输出型参数
  • 如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
  • status 不能简单当作整形来看待,要按照比特位划分
  • 最低的7个比特位表示进程接受到的信号,次低8位表示进程退出的退出码
  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出,若为假,则子进程收到的信号编号)
  • WEXITSTATUS(status): 若WIFEXITED非零就提取子进程退出码。(查看进程的退出码)

父进程非阻塞等待执行案例

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

typedef void (*handler_t)(); //函数指针类型
std::vector<handler_t> handlers; //函数指针数组
void fun_one()
{
    printf("这是一个临时任务1\n");
}
void fun_two()
{
    printf("这是一个临时任务2\n");
}
// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!
void Load()
{
    handlers.push_back(fun_one);
    handlers.push_back(fun_two);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 子进程
        int cnt =  5;
        while(cnt)
        {
            printf("我是子进程: %d\n", cnt--);
            sleep(1);
        }
        exit(11); // 11 仅仅用来测试
    }
    else
    {
        int quit = 0;
        while(!quit)
        {
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
            if(res > 0)
            {
                //等待成功 && 子进程退出
                printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));//WEXITSTATUS(status)显示子进程退出码
                quit = 1;
            }
            else if(res == 0)
            {
                //等待成功 && 但子进程并未退出
                printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");
                if(handlers.empty()) 
                	Load();
                for(auto iter : handlers)
                {
                    //执行处理其他任务
                    iter();
                }
            }
            else
            {
                //等待失败
                printf("wait失败!\n");
                quit = 1;
            }
            sleep(1);
        }   
    }
}

四、进程替换

1. 基本概念

引言

fork之后,父子进程各自执行父进程代码的一部分,但是如果子进程就想执行一个全新的程序呢

💡答:用进程的程序替换来完成这个功能

概念

程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据)到调用进程的地址空间中,从而让子进程达到执行其他程序的目的。

进程替换的原理⭐

  1. 用操作系统提供的接口exec函数将新的磁盘上的程序加载到内存

  2. 与当前进程的页表重现建立映射

调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

2. 如何进行进程替换

所谓的exec函数,本质就是如何加载程序的函数,为了满足不同的调用场景,有六种以exec开头的函数:

头文件:
#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p会自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
  1. int execl(const char *path, const char *arg, ...);
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>

int main()
{
	pid_t id = fork();
	//如果不创建子进程,那么替换的只能是父进程,这样替换子进程而不影响父进程  
	//因为想让父进程聚焦再读取数据,解析数据,指派进程执行代码的功能
	if (id == 0)
	{
		//子进程---子进程加载新程序的时候,是写入,发生写时拷贝并将父子代码分离
		//父子进程再代码和数据上就彻底分开了
			
		//ls-a-l
		printf("子进程开始运行,pid:%d\n", getpid());
		sleep(3);
		execl("/user/bin/ls", "ls", "-a", "-l", NULL);
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }
  1. int execlp(const char *file, const char *arg, ...);
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>

int main()
{
	pid_t id = fork();
	if (id == 0)
	{			
		//ls-a-l
		printf("子进程开始运行,pid:%d\n", getpid());
		sleep(3);
		//execl("/user/bin/ls", "ls", "-a", "-l", NULL);
		execlp("ls", "ls", "-a", "-l", NULL);
		//第一个参数表示你要执行谁--找到程序
		//后面的参数表示你想怎么执行--传递选项
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }
  1. int execle(const char *path, const char *arg, ...,char *const envp[]);
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#define NUM 16

int main(int argc,int*argv[],int*env[])
{
	pid_t id = fork();
	if (id == 0)
	{	
		printf("子进程开始运行,pid:%d\n", getpid());
		//execl("/user/bin/ls", "ls", "-a", "-l", NULL);
		execle("ls", "ls", "-a", "-l", NULL, env);	
		//环境变量具有全局属性,可以被子进程继承
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }
  1. int execv(const char *path, char *const argv[]);
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>

#define NUM 16

int main()
{
	pid_t id = fork();
	//如果不创建子进程,那么替换的只能是父进程,这样替换子进程而不影响父进程  
	//因为想让父进程聚焦再读取数据,解析数据,指派进程执行代码的功能
	if (id == 0)
	{
		//子进程---子进程加载新程序的时候,是写入,发生写时拷贝并将父子代码分离
		//父子进程再代码和数据上就彻底分开了
		char* const _argv[NUM] = {
				(char*)"ls",
				(char*)"-a",
				(char*)"-l",
				NULL
		};
		
		printf("子进程开始运行,pid:%d\n", getpid());
		sleep(3);
		//execl("/user/bin/ls", "ls", "-a", "-l", NULL);
		execv("/user/bin/ls", _argv);//这个接口和execl只有传参的差别
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }
  1. int execvp(const char *file, char *const argv[]);
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#define NUM 16
int main()
{
	pid_t id = fork();
	if (id == 0)
	{		
		char* const _argv[NUM] = {
				(char*)"ls",
				(char*)"-a",
				(char*)"-l",
				NULL
		};
	
		printf("子进程开始运行,pid:%d\n", getpid());
		//execl("/user/bin/ls", "ls", "-a", "-l", NULL);
		execvp("ls", _argv);
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }
  1. int execvpe(const char*file, char*const argv[], char*const envp[])
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>

int main()
{
	pid_t id = fork();
	if (id == 0)
	{
		//ls-a-l
		printf("子进程开始运行,pid:%d\n", getpid());
		sleep(3);
		execvpe("ls", "ls", "-a", "-l", env);
		exit(1);
	}
	else
	{
		//父进程
		printf("父进程开始运行,pid:%d\n", getpid());
		int status = 0;
		pid_t id = waitpid(-1, &status, 0);//阻塞等待,一定是子进程先运行完毕,父进程获取之后才退出!
		if (id > 0)
		{
			printf("wait success ,exit code:%d\n", WEXITSTATUS(status));
		}	
	}	
	return 0;
 }

事实上,只有execve是真正的系统调用。为了满足不同的场景,其它六个都是系统提供的基本封装,最终都调用
int execve(const char*filename, char*const argv[], char*const envp[]

💡注意事项:

exex系列的程序就是加载器的底层接口
path—路径+目标文件名
*arg—传入的选项
... — 表示可变参数列表
char *const argv[] — 表示命令行参数的指针数组

  • 最后必须以NULL结尾

  • 也可以用来执行自己写的程序

  • exec系列的函数不需要返回值判定调用其他进程是否成功,因为一旦调用成功,exec代码也被替换了,没有能接收到exec返回值的变量。

  • 可以在后面加exit,如果调用失败,exit没被替换,就会执行exit退出程序。

3. 为什么要进行进程替换

进程替换和应用场景有关,有时候必须让子进程执行新的程序。

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

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

相关文章

String(二)————迭代器及相关接口使用

目录 string构造接口&#xff08;Construct string object&#xff09; string的元素访问&#xff08;读写&#xff09; 迭代器 string构造接口&#xff08;Construct string object&#xff09; string相比于C语言的字符数组要好用的多&#xff0c;无论是在初始化还是在读写…

排序5:直接选择排序

目录 排序思想&#xff1a; 演示图&#xff1a; 代码实现 总结&#xff1a; 排序思想&#xff1a; 在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素 若它不是这组元素中的最后一个(第一个)元素&#xff0c;则将它与这组元素中的最后一个&#xff08;第一个…

SpringMVC---->自我实现底层机制(吃透springMVC)

目录 配套代码在资源中&#xff08;免费&#xff09; maven环境搭配 注解注入的规范&#xff1a; 一.开发HongDisptcherServlet前端控制器 1.说明&#xff1a; 2.配置web.xml文件 3.检查前期工作是否成功 二.完成客户端/浏览器请求控制层 1.创建 自己的 Controller 和…

【HTML+CSS+JS】模仿QQ登录界面

目录前言简介布局思路相关代码颜色渐变动画头像表单区域JS相关总结前言 学了HTML、CSS和JS有了一个月了&#xff0c;JS还未学完&#xff0c;偷懒写一个小项目&#xff0c;用了一个下午&#xff0c;顺便巩固一下所学知识。&#xff08;内容比较简陋&#xff0c;适合新手&#x…

基于火鹰优化算法的函数寻优算法

文章目录一、理论基础1、火鹰优化算法2、FHO算法伪代码二、仿真实验与结果分析三、参考文献一、理论基础 1、火鹰优化算法 文献[1]提出了火鹰优化算法(Fire Hawk Optimizer, FHO)作为一种新的元启发式算法&#xff0c;该算法基于啸鸢、麻鹰和褐隼的觅食行为&#xff0c;这些鸟…

【网络原理】网络编程Socket套接字基础知识汇总

目录 1.网络初始&#xff1a; 2.网络编程&#xff1a; 3.UDP数据报套接字&#xff1a; 4.TCP流套接字&#xff1a; 1.网络初始&#xff1a; 局域网&#xff08;LAN&#xff09;广域网&#xff08;WAN&#xff09;IP地址用于定位主机的网络地址。端口号可以标识主机中发送数…

数据结构六:堆

前言&#xff1a;上一篇我们讲了二叉树&#xff0c;你知道吗&#xff1f;堆的底层是一棵完全二叉树。这样说会不会就会觉得熟悉了。 目录 1.堆的概念及存储方式 2&#xff1a;堆的创建 2.1:向下调整 3.堆的插入和删除 3.1&#xff1a;堆的插入 3.2&#xff1a;堆的删除 …

基于Web的商城后台管理系统的设计与实现

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Oracle和MySQL查询所有的表信息和字段信息

Oracle和MySQL查询所有的表信息和字段信息1. MySQL1.1 查询表1.2 查询字段1.2.1 方式1->SHOW FULL COLUMNS1.2.2 方式2->information_schema.COLUMNS1.3 查表和字段1.4 查表和字段-->转程Oracle需要的数据类型2. Oracle2.1 查表和字段的单表查询2.2 整理查表和字段的s…

超详细的JUnit单元测试介绍

前言 本文为JUnit单元测试相关知识&#xff0c;下边将对JUnit单元测试概念&#xff0c;JUnit优点&#xff0c;JUnit安装与使用&#xff0c;JUnit运行流程与常用注解&#xff0c;JUnit测试套件使用及参数化设置&#xff0c;JUnit断言等进行详尽介绍~ &#x1f4cc;博主主页&…

大数据Hadoop之——Apache Hudi 与 Presto/Trino集成

文章目录一、概述二、Trino 环境部署1&#xff09;安装JDK2&#xff09;安装python3&#xff09;安装Trino1、下载解压并配置环境变量2、修改配置3、启动服务4、测试验证三、在Hive中创建表关联Hudi表1&#xff09;添加jar包2&#xff09;创建库表关联Hudi四、Hudi 与 Trino集成…

SpringCloud Alibaba系列 Sentinel(三)

高并发下的微服务容错方案&#xff1f; 限流、熔断、降级 1&#xff1a;限流 在高并发系统中一定要用&#xff0c;高并发的所有请求进来&#xff0c;不是让每个请求都打到后台集群的&#xff0c;后台集群有它的消费能力&#xff0c;应该在它消费能力之内放行请求&#xff0c;…

Hadoop HA集群全是standBy解决办法

文章目录原理解决方案原理 hadoop集群配置HA后&#xff0c;会存在多个namenode&#xff0c;但是同一时间仅有一台NN为Active的状态&#xff0c;其他NN都是StandBy的状态。 上图是hadoop集群配置HA的原理图&#xff0c;从上图我们可以看到多个NN的状态切换&#xff0c;是依靠Z…

linux命令与makefile学习

linux命令与makefile学习文件权限通配符*常用命令makefilegcc与g区别&#xff1a;Linux上有一句话&#xff1a;一切皆文件 普通文件 “-” 目录文件 “d” &#xff08;directory&#xff09; 管道文件 “p” &#xff08;piping&#xff09; 链接文件“l” &#xff08;li…

SAP FICO银行账户余额查询表开发说明书(包括开发源代码、测试样例及FS)

程序说明 满足财务银行账户余额查询明细的需求; 支持财务实时查看银行余额数据。 筛选界面 序号 栏位标题 字段类型 是否必须 是否为范围

【pwn】2022 祥云杯 部分wp

【pwn】2022 祥云杯 部分wp 前言 又是一年的祥云杯&#xff0c;相比去年我啥也不会写&#xff0c;今年起码写了几个签到… 又被队友带飞咯 protool Google的Protobuf&#xff0c;参考学习连接 https://bbs.pediy.com/thread-270004.htm 发现了栈溢出&#xff0c;protobuf…

Unity技术手册-UGUI零基础详细教程-Toggle切换

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 本文约3千字&#xff0c;新手阅读需要7分钟&#xff0c;复习需要2分钟 【收藏随时查阅不再迷路】 &#x1f449;关于作者 众所周知&#…

2.6 Python 基本数据类型

1. 数据类型 类型是变量所指的内存中对象的类型. 内置的type()函数可以用来查询变量所指的对象类型。Python 3中有六个标准的数据类型: Numbers(数字), String(字符串), List(列表), Tuple(元组), Sets(集合), Dictionary(字典).2. Numbers 数字型 Python 有三种数字类型 in…

SpringMVC基本配置

小常规 springmvc的处理器对应的bean必须按照规范格式开发&#xff0c;为避免加入无效的bean可通过bean加载过滤器进行包含设定或排除设定&#xff0c;表现层bean标注通常设定为Controller在此发现图片没有加载出来回到程序去分析当发起一个请求以后DispatcherServlet配置拦截所…

【JVM技术专题】 深入分析class字节码指令方法调用详解「原理篇」

方法调用详解 ​ 调用目标在程序代码写好、编译器进行编译时就必须确定下来&#xff0c;这类方法的调用称为解析。 解析 ​ 在Java语言中符合**“编译期可知&#xff0c;运行期不可变”**这个要求的方法&#xff0c;主要包括静态方法和私有方法两大类&#xff0c;前者与类型…